rkremap 0.3.0.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +68 -5
- data/example/henkan-super.rb +6 -11
- data/example/keylogger.rb +2 -2
- data/example/mac-shortcut.rb +41 -10
- data/example/tmtms.rb +74 -32
- data/lib/rkremap/evdev.rb +41 -83
- data/lib/rkremap/evdev_list.rb +84 -0
- data/lib/rkremap/event.rb +58 -0
- data/lib/rkremap/keycode.rb +81 -1
- data/lib/rkremap/uinput.rb +40 -0
- data/lib/rkremap/version.rb +1 -1
- data/lib/rkremap/winattr.rb +4 -1
- data/lib/rkremap.rb +103 -84
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 127371f8cab9cd5ff002b35b886b79a3a4424d779f4bf938b9006fb633917a51
|
4
|
+
data.tar.gz: a02bd1649a62f30916e125582409f9b9ab8d57c33caf10bdfc5f7e6392ece992
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 111e0d08dfaec939535f150eb8d2852c5bbda078d705191d8d03d0d035782088cf73f7c861029cc49b99860f8645be46c1ef557326bae1e17170d15116ee887d
|
7
|
+
data.tar.gz: a1124ae0dabd8f87286d7ae2cc375a0490ffc58a884c2fb0f9694f51cee9d97b309ea83842afeb46f86184a1523e96c06429688bf71aa05e1a6e9555d6f488cb
|
data/README.md
CHANGED
@@ -23,7 +23,9 @@ KERNEL=="uinput", GROUP="input"
|
|
23
23
|
という内容で作れば `/dev/uinput` のグループを `input` にできるっぽい。
|
24
24
|
|
25
25
|
|
26
|
-
|
26
|
+
## Rkremap
|
27
|
+
|
28
|
+
### Rkremap.new
|
27
29
|
|
28
30
|
引数を指定しなければ自動的にキーボードデバイスの検出を試みる。
|
29
31
|
|
@@ -40,9 +42,28 @@ sudo evtest /dev/input/eventX
|
|
40
42
|
|
41
43
|
ThinkPad 本体のキーボードは `/dev/input/event3` だった。
|
42
44
|
|
45
|
+
### Rkremap#exclude
|
46
|
+
|
47
|
+
無視するデバイス名を正規表現で指定できる。
|
48
|
+
|
49
|
+
例
|
50
|
+
```ruby
|
51
|
+
rkremap.exclude = /touchpad/i
|
52
|
+
```
|
53
|
+
|
54
|
+
### Rkremap#mouse
|
55
|
+
|
56
|
+
真を指定するとマウスも対象にする。
|
57
|
+
|
43
58
|
### Rkremap#grab
|
44
59
|
|
45
|
-
`true`
|
60
|
+
`true` を設定するとキーイベントを奪う。キーイベントを見るだけでいいなら `false` を設定する。
|
61
|
+
|
62
|
+
`:keyboard` を設定するとキーボードデバイスを grab する。
|
63
|
+
|
64
|
+
`:mouse` を設定するとマウスデバイスを grab する。
|
65
|
+
|
66
|
+
正規表現を設定するとデバイス名がマッチした場合にデバイスを grab する。
|
46
67
|
|
47
68
|
### Rkremap#x11
|
48
69
|
|
@@ -52,7 +73,7 @@ ThinkPad 本体のキーボードは `/dev/input/event3` だった。
|
|
52
73
|
|
53
74
|
修飾キーの一覧。これらのキーは単体で押されただけでは `start` ブロックを実行しない。
|
54
75
|
|
55
|
-
### auto_detect
|
76
|
+
### Rkremap#auto_detect
|
56
77
|
|
57
78
|
`true` の場合はキーボードデバイスの接続を自動検知する。
|
58
79
|
`new` 時にデバイスファイルが指定されない場合は `true` になる。
|
@@ -65,13 +86,27 @@ ThinkPad 本体のキーボードは `/dev/input/event3` だった。
|
|
65
86
|
|
66
87
|
### Rkremap#match
|
67
88
|
|
68
|
-
キーイベントが引数に適合するとブロックを実行する。ブロック引数は Event オブジェクト。
|
69
|
-
`Event#skip` するとこのイベントはスキップされる。
|
89
|
+
キーイベントが引数に適合するとブロックを実行する。ブロック引数は `Rkremap::Event` オブジェクト。
|
90
|
+
`Rkremap::Event#skip` するとこのイベントはスキップされる。
|
91
|
+
|
92
|
+
複数回指定した場合は記述順に実行される。
|
70
93
|
|
71
94
|
### Rkremap#key
|
72
95
|
|
73
96
|
キーを押したことにする。引数はキーコード(`Integer`)と修飾キー(`Hash`)。
|
74
97
|
|
98
|
+
修飾キーのハッシュのキーはキーコード(`KEY_LEFTCTRL` 等)、値は `true`, または `false`。
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
{
|
102
|
+
KEY_LEFTSHIFT => false,
|
103
|
+
KEY_LEFTCTRL => true,
|
104
|
+
}
|
105
|
+
```
|
106
|
+
|
107
|
+
と指定すると、左シフトは離した状態、左Ctrlは押した状態、それ以外の修飾キーはそのまま。
|
108
|
+
`#key()` 処理後は処理前の修飾キーの状態に戻る。
|
109
|
+
|
75
110
|
### Rkremap#event
|
76
111
|
|
77
112
|
キーイベントを発生させる。`code` 引数はキーコード(`Integer`)。`type` 引数はイベントタイプ(`Symbol`)で `:press`, `:release`。
|
@@ -84,6 +119,34 @@ ThinkPad 本体のキーボードは `/dev/input/event3` だった。
|
|
84
119
|
|
85
120
|
コードからキー名のシンボルに変換するための Hash。
|
86
121
|
|
122
|
+
## Rkremap::Event
|
123
|
+
|
124
|
+
### Rkremap::Event#code
|
125
|
+
|
126
|
+
キーコード。`Rkremap::KeyCode::KEY_*` に該当。
|
127
|
+
|
128
|
+
### Rkremap::Event#type
|
129
|
+
|
130
|
+
イベントタイプ。 `:press`, `:release`, `:repeat` のいずれか。
|
131
|
+
|
132
|
+
### Rkremap::Event#app
|
133
|
+
|
134
|
+
`Rkremap::App`。イベントが発生したアプリケーション。
|
135
|
+
|
136
|
+
### Rkremap::Event#skip
|
137
|
+
|
138
|
+
このイベントをこれ以降処理しない。
|
139
|
+
|
140
|
+
## Rkremap::App
|
141
|
+
|
142
|
+
### Rkremap::App#class_name
|
143
|
+
|
144
|
+
アプリケーションのクラス名。
|
145
|
+
|
146
|
+
### Rkremap::App#title
|
147
|
+
|
148
|
+
アプリケーションのタイトル。
|
149
|
+
|
87
150
|
## 参考
|
88
151
|
|
89
152
|
* [mooz/xkeysnail: Yet another keyboard remapping tool for X environment](https://github.com/mooz/xkeysnail)
|
data/example/henkan-super.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
# 変換/無変換キーを0.5秒以上押してると
|
2
|
-
# 0.5秒以内でも変換/無変換キーを押しながら他のキーを押すと
|
1
|
+
# 変換/無変換キーを0.5秒以上押してるとSUPERキーとして扱う。
|
2
|
+
# 0.5秒以内でも変換/無変換キーを押しながら他のキーを押すとSUPERキーとして扱う。
|
3
3
|
|
4
4
|
require 'rkremap'
|
5
|
-
require 'tmtms_timer'
|
5
|
+
require 'tmtms_timer'
|
6
6
|
|
7
7
|
include Rkremap::KeyCode
|
8
8
|
|
@@ -29,7 +29,7 @@ rk.match(code: [KEY_HENKAN, KEY_MUHENKAN]) do |event|
|
|
29
29
|
end
|
30
30
|
jobs[event.code] = job
|
31
31
|
elsif event.type == :release
|
32
|
-
if jobs[event.code]&.cancel
|
32
|
+
if jobs[event.code]&.cancel # 0.5秒以内に離された
|
33
33
|
rk.key(event.code)
|
34
34
|
else
|
35
35
|
rk.event(code: henkan_map[event.code], type: :release)
|
@@ -38,20 +38,15 @@ rk.match(code: [KEY_HENKAN, KEY_MUHENKAN]) do |event|
|
|
38
38
|
event.skip
|
39
39
|
end
|
40
40
|
|
41
|
+
# 何かキーが押されたときに HENKAN, MUHENKAN が押されてれば SUPER が押されてることにする
|
41
42
|
rk.match(type: :press) do
|
42
43
|
[KEY_HENKAN, KEY_MUHENKAN].each do |code|
|
43
|
-
if jobs[code]&.cancel
|
44
|
+
if jobs[code]&.cancel # 0.5秒以内
|
44
45
|
rk.event(code: henkan_map[code], type: :press)
|
45
46
|
end
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
49
|
-
def code2key(code)
|
50
|
-
Rkremap::CODE_KEY[code].to_s.sub(/\AKEY_/, '')
|
51
|
-
end
|
52
|
-
|
53
50
|
rk.start do |code, mod|
|
54
|
-
key = (mod.select{|_, v| v}.keys + [code]).map{|c| code2key(c)}.join('-')
|
55
|
-
puts key
|
56
51
|
rk.key(code, mod)
|
57
52
|
end
|
data/example/keylogger.rb
CHANGED
@@ -6,9 +6,9 @@ def code2key(code)
|
|
6
6
|
Rkremap::CODE_KEY[code].to_s.sub(/\AKEY_/, '')
|
7
7
|
end
|
8
8
|
|
9
|
-
rk = Rkremap.new
|
9
|
+
rk = Rkremap.new(*ARGV)
|
10
10
|
rk.grab = false
|
11
|
-
rk.x11 = true
|
11
|
+
rk.x11 = true if ENV['DISPLAY']
|
12
12
|
rk.start do |code, mod, app|
|
13
13
|
key = (mod.select{|_, v| v}.keys + [code]).map{|c| code2key(c)}.join('-')
|
14
14
|
key << " at #{app.title} [#{app.class_name}]" if rk.x11
|
data/example/mac-shortcut.rb
CHANGED
@@ -36,45 +36,76 @@ mod_disable_all = {}.merge(mod_disable_ctrl, mod_disable_shift, mod_disable_alt,
|
|
36
36
|
rk = Rkremap.new
|
37
37
|
rk.grab = true
|
38
38
|
rk.x11 = true
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
|
40
|
+
# Emacs や VirtualBox ではそのまま
|
41
|
+
rk.match(app: /Emacs|VirtualBox Machine/) do |event|
|
42
|
+
rk.event(code: event.code, type: event.type)
|
43
|
+
event.skip
|
44
|
+
end
|
45
|
+
|
46
|
+
# 端末
|
47
|
+
rk.match(app: /terminal/i) do |event, mod|
|
48
|
+
# Super+Ctrl はそのまま
|
49
|
+
if (mod[KEY_LEFTMETA] || mod[KEY_RIGHTMETA]) && (mod[KEY_LEFTCTRL] || mod[KEY_RIGHTCTRL])
|
50
|
+
rk.event(code: event.code, type: event.type)
|
51
|
+
end
|
52
|
+
# Super+KEY は Ctrl+Shift+KEY に変換
|
53
|
+
if (mod[KEY_LEFTMETA] || mod[KEY_RIGHTMETA]) && Rkremap::CODE_KEY[event.code] =~ /\AKEY_[A-Z]\z/
|
54
|
+
rk.with_modifier(mod_disable_meta.merge(KEY_LEFTCTRL => true, KEY_LEFTSHIFT => true)) do
|
55
|
+
rk.event(code: event.code, type: event.type)
|
56
|
+
end
|
57
|
+
else
|
58
|
+
rk.event(code: event.code, type: event.type)
|
59
|
+
end
|
60
|
+
event.skip
|
61
|
+
end
|
62
|
+
|
63
|
+
rk.start do |code, mod|
|
64
|
+
# Super + Ctrl + 何か はそのまま
|
65
|
+
if (mod[KEY_LEFTMETA] || mod[KEY_RIGHTMETA]) && (mod[KEY_LEFTCTRL] || mod[KEY_RIGHTCTRL])
|
42
66
|
rk.key(code, mod)
|
43
67
|
next
|
44
68
|
end
|
45
69
|
|
46
70
|
# SUPER+[A-Z]/Enter を Ctrl+[A-Z]/Enter に変換
|
47
71
|
if (mod[KEY_LEFTMETA] || mod[KEY_RIGHTMETA]) && (Rkremap::CODE_KEY[code] =~ /\AKEY_[A-Z]\z/ || code == KEY_ENTER)
|
48
|
-
mod.merge
|
49
|
-
mod[KEY_LEFTCTRL] = true
|
50
|
-
rk.key(code, mod)
|
72
|
+
rk.key(code, mod.merge(mod_disable_meta, KEY_LEFTCTRL => true))
|
51
73
|
next
|
52
74
|
end
|
53
75
|
|
54
76
|
# Alt + F, B は Ctrl + →, ← に変換
|
55
77
|
if (mod[KEY_LEFTALT] || mod[KEY_RIGHTALT]) && (code == KEY_F || code == KEY_B)
|
56
|
-
rk.key(code == KEY_F ? KEY_RIGHT : KEY_LEFT, mod.merge(mod_disable_alt,
|
78
|
+
rk.key(code == KEY_F ? KEY_RIGHT : KEY_LEFT, mod.merge(mod_disable_alt, KEY_LEFTCTRL => true))
|
57
79
|
next
|
58
80
|
end
|
59
81
|
|
60
82
|
# Ctrl は他のキーに変換
|
61
83
|
if mod[KEY_LEFTCTRL] || mod[KEY_RIGHTCTRL]
|
84
|
+
# Ctrl+I/O はそのまま
|
85
|
+
if code == KEY_I || code == KEY_O
|
86
|
+
rk.key(code, mod)
|
87
|
+
next
|
88
|
+
end
|
89
|
+
|
62
90
|
# Ctrl+K は行末まで削除
|
63
91
|
if code == KEY_K
|
64
|
-
rk.key(KEY_END, mod_disable_all.merge(
|
65
|
-
rk.key(KEY_X, mod_disable_all.merge(
|
92
|
+
rk.key(KEY_END, mod_disable_all.merge(KEY_LEFTSHIFT => true)) # SHIFT+END
|
93
|
+
rk.key(KEY_X, mod_disable_all.merge(KEY_LEFTCTRL => true)) # Ctrl+X
|
66
94
|
next
|
67
95
|
end
|
96
|
+
|
68
97
|
# 単純な変換
|
69
98
|
if ctrl_map[code]
|
70
|
-
rk.key(ctrl_map[code], mod_disable_ctrl)
|
99
|
+
rk.key(ctrl_map[code], mod.merge(mod_disable_ctrl))
|
71
100
|
next
|
72
101
|
end
|
102
|
+
|
73
103
|
# その他の Ctrl+[A-Z] は無視
|
74
104
|
if Rkremap::CODE_KEY[code] =~ /\AKEY_[A-Z]\z/
|
75
105
|
next
|
76
106
|
end
|
77
107
|
end
|
78
108
|
|
109
|
+
# それ以外はそのまま
|
79
110
|
rk.key(code, mod)
|
80
111
|
end
|
data/example/tmtms.rb
CHANGED
@@ -1,19 +1,26 @@
|
|
1
1
|
# tmtms 用
|
2
2
|
|
3
3
|
require 'rkremap'
|
4
|
-
require 'tmtms_timer'
|
4
|
+
require 'tmtms_timer'
|
5
5
|
|
6
6
|
include Rkremap::KeyCode
|
7
7
|
|
8
|
-
rk = Rkremap.new
|
8
|
+
rk = Rkremap.new(*ARGV, mouse: true, exclude: /touchpad/)
|
9
9
|
rk.grab = true
|
10
10
|
rk.x11 = true
|
11
11
|
|
12
|
+
until system "xinput | grep -q rkremap"
|
13
|
+
sleep 0.1
|
14
|
+
end
|
15
|
+
system "xinput set-prop pointer:rkremap 'libinput Scroll Method Enabled' 0 0 1"
|
16
|
+
|
17
|
+
# HENKAN, MUHENKAN を 0.5秒以上押したら SUPER とする
|
12
18
|
timer = Tmtms::Timer.new
|
13
19
|
jobs = {
|
14
20
|
KEY_HENKAN => nil,
|
15
21
|
KEY_MUHENKAN => nil,
|
16
22
|
}
|
23
|
+
|
17
24
|
henkan_map = {
|
18
25
|
KEY_HENKAN => KEY_RIGHTMETA,
|
19
26
|
KEY_MUHENKAN => KEY_RIGHTMETA, # LEFTMETA だとデスクトップ環境のメニューが開いたりするため
|
@@ -28,7 +35,7 @@ rk.match(code: [KEY_HENKAN, KEY_MUHENKAN]) do |event|
|
|
28
35
|
end
|
29
36
|
jobs[event.code] = job
|
30
37
|
elsif event.type == :release
|
31
|
-
if jobs[event.code]&.cancel
|
38
|
+
if jobs[event.code]&.cancel # 0.5秒以内に離された
|
32
39
|
rk.key(event.code)
|
33
40
|
else
|
34
41
|
rk.event(code: henkan_map[event.code], type: :release)
|
@@ -37,31 +44,26 @@ rk.match(code: [KEY_HENKAN, KEY_MUHENKAN]) do |event|
|
|
37
44
|
event.skip
|
38
45
|
end
|
39
46
|
|
47
|
+
# 何かキーが押されたときに HENKAN, MUHENKAN が押されてれば SUPER が押されてることにする
|
40
48
|
rk.match(type: :press) do
|
41
49
|
[KEY_HENKAN, KEY_MUHENKAN].each do |code|
|
42
|
-
if jobs[code]&.cancel
|
50
|
+
if jobs[code]&.cancel # 0.5秒以内
|
43
51
|
rk.event(code: henkan_map[code], type: :press)
|
44
52
|
end
|
45
53
|
end
|
46
54
|
end
|
47
55
|
|
56
|
+
# マウスボタンイベントはそのまま
|
57
|
+
rk.match(device: :mouse) do |event|
|
58
|
+
rk.event(code: event.code, type: event.type)
|
59
|
+
event.skip
|
60
|
+
end
|
61
|
+
|
48
62
|
# CAPSLOCK は CTRL に変換
|
49
63
|
rk.match(code: KEY_CAPSLOCK) do |event|
|
50
|
-
|
51
|
-
event.skip
|
64
|
+
event.code = KEY_LEFTCTRL
|
52
65
|
end
|
53
66
|
|
54
|
-
ctrl_map = {
|
55
|
-
KEY_A => KEY_HOME,
|
56
|
-
KEY_E => KEY_END,
|
57
|
-
KEY_B => KEY_LEFT,
|
58
|
-
KEY_F => KEY_RIGHT,
|
59
|
-
KEY_N => KEY_DOWN,
|
60
|
-
KEY_P => KEY_UP,
|
61
|
-
KEY_D => KEY_DELETE,
|
62
|
-
KEY_H => KEY_BACKSPACE,
|
63
|
-
KEY_I => KEY_TAB,
|
64
|
-
}
|
65
67
|
mod_disable_ctrl = {
|
66
68
|
KEY_LEFTCTRL => false,
|
67
69
|
KEY_RIGHTCTRL => false,
|
@@ -80,36 +82,72 @@ mod_disable_meta = {
|
|
80
82
|
}
|
81
83
|
mod_disable_all = {}.merge(mod_disable_ctrl, mod_disable_shift, mod_disable_alt, mod_disable_meta)
|
82
84
|
|
85
|
+
# JISキーボードを英語配列にしたときに一部キーを変える
|
83
86
|
key_map = {
|
84
87
|
KEY_YEN => KEY_BACKSLASH,
|
85
88
|
KEY_BACKSLASH => KEY_GRAVE,
|
86
89
|
KEY_GRAVE => KEY_ESC,
|
87
90
|
}
|
91
|
+
rk.match do |event, mod|
|
92
|
+
event.code = key_map[event.code] || event.code
|
93
|
+
# RO キーは「\」(SHIFTキーを押してる場合は「_」)
|
94
|
+
if event.code == KEY_RO
|
95
|
+
event.code = (mod[KEY_LEFTSHIFT] || mod[KEY_RIGHTSHIFT]) ? KEY_MINUS : KEY_BACKSLASH
|
96
|
+
end
|
97
|
+
end
|
88
98
|
|
89
|
-
|
90
|
-
|
91
|
-
code
|
92
|
-
|
93
|
-
|
99
|
+
# Emacs や VirtualBox ではそのまま
|
100
|
+
rk.match(app: /Emacs|VirtualBox Machine/) do |event|
|
101
|
+
rk.event(code: event.code, type: event.type)
|
102
|
+
event.skip
|
103
|
+
end
|
104
|
+
|
105
|
+
# 端末
|
106
|
+
rk.match(app: /terminal/i) do |event, mod|
|
107
|
+
# Super+Ctrl はそのまま
|
108
|
+
if (mod[KEY_LEFTMETA] || mod[KEY_RIGHTMETA]) && (mod[KEY_LEFTCTRL] || mod[KEY_RIGHTCTRL])
|
109
|
+
rk.event(code: event.code, type: event.type)
|
110
|
+
end
|
111
|
+
# Super+KEY は Ctrl+Shift+KEY に変換
|
112
|
+
if (mod[KEY_LEFTMETA] || mod[KEY_RIGHTMETA]) && Rkremap::CODE_KEY[event.code] =~ /\AKEY_[A-Z]\z/
|
113
|
+
rk.with_modifier(mod_disable_meta.merge(KEY_LEFTCTRL => true, KEY_LEFTSHIFT => true)) do
|
114
|
+
rk.event(code: event.code, type: event.type)
|
115
|
+
end
|
116
|
+
else
|
117
|
+
rk.event(code: event.code, type: event.type)
|
94
118
|
end
|
119
|
+
event.skip
|
120
|
+
end
|
95
121
|
|
96
|
-
|
97
|
-
|
122
|
+
# その他のウィンドウ
|
123
|
+
ctrl_map = {
|
124
|
+
KEY_A => KEY_HOME,
|
125
|
+
KEY_E => KEY_END,
|
126
|
+
KEY_B => KEY_LEFT,
|
127
|
+
KEY_F => KEY_RIGHT,
|
128
|
+
KEY_N => KEY_DOWN,
|
129
|
+
KEY_P => KEY_UP,
|
130
|
+
KEY_D => KEY_DELETE,
|
131
|
+
KEY_H => KEY_BACKSPACE,
|
132
|
+
KEY_I => KEY_TAB,
|
133
|
+
}
|
134
|
+
|
135
|
+
rk.start do |code, mod|
|
136
|
+
# Super + Ctrl + 何か はそのまま
|
137
|
+
if (mod[KEY_LEFTMETA] || mod[KEY_RIGHTMETA]) && (mod[KEY_LEFTCTRL] || mod[KEY_RIGHTCTRL])
|
98
138
|
rk.key(code, mod)
|
99
139
|
next
|
100
140
|
end
|
101
141
|
|
102
|
-
#
|
142
|
+
# Super+[A-Z]/Enter を Ctrl+[A-Z]/Enter に変換
|
103
143
|
if (mod[KEY_LEFTMETA] || mod[KEY_RIGHTMETA]) && (Rkremap::CODE_KEY[code] =~ /\AKEY_[A-Z]\z/ || code == KEY_ENTER)
|
104
|
-
mod.merge
|
105
|
-
mod[KEY_LEFTCTRL] = true
|
106
|
-
rk.key(code, mod)
|
144
|
+
rk.key(code, mod.merge(mod_disable_meta, KEY_LEFTCTRL => true))
|
107
145
|
next
|
108
146
|
end
|
109
147
|
|
110
148
|
# Alt + F, B は Ctrl + →, ← に変換
|
111
149
|
if (mod[KEY_LEFTALT] || mod[KEY_RIGHTALT]) && (code == KEY_F || code == KEY_B)
|
112
|
-
rk.key(code == KEY_F ? KEY_RIGHT : KEY_LEFT, mod.merge(mod_disable_alt,
|
150
|
+
rk.key(code == KEY_F ? KEY_RIGHT : KEY_LEFT, mod.merge(mod_disable_alt, KEY_LEFTCTRL => true))
|
113
151
|
next
|
114
152
|
end
|
115
153
|
|
@@ -120,22 +158,26 @@ rk.start do |code, mod, app|
|
|
120
158
|
rk.key(code, mod)
|
121
159
|
next
|
122
160
|
end
|
161
|
+
|
123
162
|
# Ctrl+K は行末まで削除
|
124
163
|
if code == KEY_K
|
125
|
-
rk.key(KEY_END, mod_disable_all.merge(
|
126
|
-
rk.key(KEY_X, mod_disable_all.merge(
|
164
|
+
rk.key(KEY_END, mod_disable_all.merge(KEY_LEFTSHIFT => true)) # SHIFT+END
|
165
|
+
rk.key(KEY_X, mod_disable_all.merge(KEY_LEFTCTRL => true)) # Ctrl+X
|
127
166
|
next
|
128
167
|
end
|
168
|
+
|
129
169
|
# 単純な変換
|
130
170
|
if ctrl_map[code]
|
131
|
-
rk.key(ctrl_map[code], mod_disable_ctrl)
|
171
|
+
rk.key(ctrl_map[code], mod.merge(mod_disable_ctrl))
|
132
172
|
next
|
133
173
|
end
|
174
|
+
|
134
175
|
# その他の Ctrl+[A-Z] は無視
|
135
176
|
if Rkremap::CODE_KEY[code] =~ /\AKEY_[A-Z]\z/
|
136
177
|
next
|
137
178
|
end
|
138
179
|
end
|
139
180
|
|
181
|
+
# それ以外はそのまま
|
140
182
|
rk.key(code, mod)
|
141
183
|
end
|
data/lib/rkremap/evdev.rb
CHANGED
@@ -1,53 +1,11 @@
|
|
1
1
|
class Rkremap
|
2
|
-
EV_KEY = 1
|
3
|
-
EV_SYN = 0
|
4
|
-
SYN_REPORT = 0
|
5
|
-
|
6
2
|
# https://www.kernel.org/doc/html/v4.12/input/input_uapi.html
|
7
3
|
class Evdev
|
8
4
|
EVIOCGBIT_ANY = 2147566880 # EVIOCGBIT(0, 1)
|
9
|
-
EVIOCGBIT_EV_KEY = 2153792801 # EVIOCGBIT(EV_KEY, (
|
5
|
+
EVIOCGBIT_EV_KEY = 2153792801 # EVIOCGBIT(EV_KEY, (KEY_CNT-1)/8+1)
|
10
6
|
EVIOCGRAB = 1074021776
|
11
7
|
EVIOCGNAME = 2164278534 # EVIOCGNAME(256)
|
12
8
|
|
13
|
-
@io2evdev = {}
|
14
|
-
def self.io2evdev
|
15
|
-
@io2evdev
|
16
|
-
end
|
17
|
-
|
18
|
-
@device_list = {}
|
19
|
-
|
20
|
-
# @return [Array<Evdev>]
|
21
|
-
def self.detect
|
22
|
-
devs = []
|
23
|
-
new_devs = Dir.children("/dev/input").map do |dev|
|
24
|
-
if dev =~ /\Aevent\d+\z/
|
25
|
-
path = "/dev/input/#{dev}"
|
26
|
-
devs.push path
|
27
|
-
next if @device_list[path]
|
28
|
-
@device_list[path] = true
|
29
|
-
Evdev.new(path)
|
30
|
-
end
|
31
|
-
end.compact
|
32
|
-
(@device_list.keys - devs).each do |path|
|
33
|
-
@device_list.delete path
|
34
|
-
end
|
35
|
-
new_devs
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.remove_device(evdev)
|
39
|
-
@io2evdev.delete evdev.io
|
40
|
-
@device_list.delete evdev.path
|
41
|
-
end
|
42
|
-
|
43
|
-
# @param inputs [Array<Evdev>]
|
44
|
-
# @param timeout [Numeric]
|
45
|
-
# @return [Evdev]
|
46
|
-
def self.select(inputs, timeout)
|
47
|
-
r, = IO.select(inputs.map(&:io), nil, nil, timeout)
|
48
|
-
r && @io2evdev[r[0]]
|
49
|
-
end
|
50
|
-
|
51
9
|
# @return [String]
|
52
10
|
attr_reader :path
|
53
11
|
|
@@ -57,6 +15,7 @@ class Rkremap
|
|
57
15
|
@io = nil
|
58
16
|
@capa = nil
|
59
17
|
@uinput = nil
|
18
|
+
@grab = false
|
60
19
|
end
|
61
20
|
|
62
21
|
def eql?(other)
|
@@ -67,13 +26,18 @@ class Rkremap
|
|
67
26
|
def io
|
68
27
|
return @io if @io
|
69
28
|
@io = File.open(@path)
|
70
|
-
|
71
|
-
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String]
|
32
|
+
def name
|
33
|
+
return @name if @name
|
34
|
+
buf = ''
|
35
|
+
io.ioctl(EVIOCGNAME, buf)
|
36
|
+
@name = buf.sub(/\0+$/, '')
|
72
37
|
end
|
73
38
|
|
74
39
|
def close
|
75
40
|
@io&.close
|
76
|
-
self.class.remove_device(self)
|
77
41
|
end
|
78
42
|
|
79
43
|
# @param key [Integer]
|
@@ -89,18 +53,37 @@ class Rkremap
|
|
89
53
|
|
90
54
|
# @return [Boolean]
|
91
55
|
def keyboard?
|
92
|
-
|
93
|
-
|
94
|
-
return false if
|
56
|
+
return @is_keyboard unless @is_keyboard.nil?
|
57
|
+
@is_keyboard = false
|
58
|
+
return false if name =~ /\Arkremap\0*\z/
|
95
59
|
|
60
|
+
buf = ''
|
96
61
|
io.ioctl(EVIOCGBIT_ANY, buf)
|
97
62
|
return false if buf.unpack1('C')[EV_KEY] == 0
|
98
|
-
capable?(KeyCode::KEY_0) && capable?(KeyCode::KEY_9) &&
|
99
|
-
|
63
|
+
@is_keyboard = capable?(KeyCode::KEY_0) && capable?(KeyCode::KEY_9) &&
|
64
|
+
capable?(KeyCode::KEY_A) && capable?(KeyCode::KEY_Z) && capable?(KeyCode::KEY_SPACE)
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Boolean]
|
68
|
+
def mouse?
|
69
|
+
return @is_mouse unless @is_mouse.nil?
|
70
|
+
@is_mouse = false
|
71
|
+
return false if name =~ /\Arkremap\0*\z/
|
72
|
+
|
73
|
+
buf = ''
|
74
|
+
io.ioctl(EVIOCGBIT_ANY, buf)
|
75
|
+
return false if buf.unpack1('C')[EV_KEY] == 0
|
76
|
+
@is_mouse = capable?(KeyCode::BTN_MOUSE)
|
100
77
|
end
|
101
78
|
|
102
79
|
def grab
|
103
80
|
io.ioctl(EVIOCGRAB, 1)
|
81
|
+
@grab = true
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [Boolean]
|
85
|
+
def grab?
|
86
|
+
@grab
|
104
87
|
end
|
105
88
|
|
106
89
|
# struct input_event {
|
@@ -116,39 +99,14 @@ class Rkremap
|
|
116
99
|
time = Time.at(sec, usec)
|
117
100
|
return time, type, code, value
|
118
101
|
end
|
119
|
-
end
|
120
|
-
|
121
|
-
# https://www.kernel.org/doc/html/v4.12/input/uinput.html
|
122
|
-
class Uinput
|
123
|
-
UI_SET_EVBIT = 1074025828
|
124
|
-
UI_SET_KEYBIT = 1074025829
|
125
|
-
UI_DEV_SETUP = 1079792899
|
126
|
-
UI_DEV_CREATE = 21761
|
127
|
-
|
128
|
-
def initialize
|
129
|
-
@dev = File.open('/dev/uinput', 'w')
|
130
|
-
setup
|
131
|
-
end
|
132
|
-
|
133
|
-
def setup
|
134
|
-
@dev.ioctl(UI_SET_EVBIT, EV_KEY)
|
135
|
-
KeyCode::KEY_CNT.times{|k| @dev.ioctl(UI_SET_KEYBIT, k)}
|
136
|
-
bustype = 0x03 # BUS_USB
|
137
|
-
vendor = 0x1234 # てきとー
|
138
|
-
product = 0x5678 # てきとー
|
139
|
-
version = 1 # てきとー
|
140
|
-
name = 'rkremap'
|
141
|
-
ff_efects_max = 0
|
142
|
-
setup = [bustype, vendor, product, version, name, ff_efects_max].pack("SSSSZ80L") # struct uinput_setup
|
143
|
-
@dev.ioctl(UI_DEV_SETUP, setup)
|
144
|
-
@dev.ioctl(UI_DEV_CREATE)
|
145
|
-
end
|
146
102
|
|
147
|
-
# @param
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
103
|
+
# @param pattern [Symbol, Regexp]
|
104
|
+
def match?(pattern)
|
105
|
+
return true if pattern == true
|
106
|
+
return true if pattern == :keyboard && keyboard?
|
107
|
+
return true if pattern == :mouse && mouse?
|
108
|
+
return true if pattern.is_a?(Regexp) && pattern =~ name
|
109
|
+
return false
|
152
110
|
end
|
153
111
|
end
|
154
112
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'tmtms/timer'
|
2
|
+
|
3
|
+
class Rkremap
|
4
|
+
class EvdevList
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_reader :device_list
|
8
|
+
attr_accessor :grab
|
9
|
+
|
10
|
+
# @param device_files [Array<String>] path list (/dev/input/event*)
|
11
|
+
# @param auto_detect [Boolean]
|
12
|
+
# @param detect_mouse [Boolean]
|
13
|
+
# @param exclude [Regexp]
|
14
|
+
def initialize(device_files, auto_detect: false, detect_mouse: false, exclude: nil)
|
15
|
+
@device_files = device_files
|
16
|
+
@auto_detect = auto_detect
|
17
|
+
@detect_mouse = detect_mouse
|
18
|
+
@exclude = exclude
|
19
|
+
@io2evdev = {} # {IO => Evdev}
|
20
|
+
@device_list = {} # {path => Evdev}
|
21
|
+
@prev_devices = []
|
22
|
+
end
|
23
|
+
|
24
|
+
def each(&block)
|
25
|
+
@device_list.each_value do |evdev|
|
26
|
+
block.call evdev
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def detect_loop
|
31
|
+
detect
|
32
|
+
timer = Tmtms::Timer.new
|
33
|
+
timer.repeat(3) do
|
34
|
+
detect
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def detect
|
39
|
+
devs = Dir.glob("/dev/input/event*")
|
40
|
+
(@device_list.keys - devs).each{|path| remove_device(path)}
|
41
|
+
new_devs = devs - @prev_devices
|
42
|
+
@prev_devices = devs
|
43
|
+
new_devs &= @device_files unless @auto_detect
|
44
|
+
new_devs -= @device_list.keys
|
45
|
+
new_devs.each do |path|
|
46
|
+
ev = Evdev.new(path)
|
47
|
+
next if @exclude && ev.name =~ @exclude
|
48
|
+
if ev.keyboard? || (@detect_mouse && ev.mouse?)
|
49
|
+
ev.grab if ev.match?(@grab)
|
50
|
+
puts "detect: #{ev.name} (#{ev.path}) #{ev.grab? ? 'grab' : 'not grab'}" if $VERBOSE
|
51
|
+
@io2evdev[ev.io] = ev
|
52
|
+
@device_list[path] = ev
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param evdev [path]
|
58
|
+
def remove_device(path)
|
59
|
+
evdev = @device_list[path]
|
60
|
+
@io2evdev.delete evdev.io
|
61
|
+
evdev.close
|
62
|
+
@device_list.delete path
|
63
|
+
end
|
64
|
+
|
65
|
+
# @param timeout [Numeric]
|
66
|
+
# @return [Evdev]
|
67
|
+
def select(timeout)
|
68
|
+
r, = IO.select(@device_list.values.map(&:io), nil, nil, timeout)
|
69
|
+
r && @io2evdev[r[0]]
|
70
|
+
end
|
71
|
+
|
72
|
+
def read_event
|
73
|
+
while true
|
74
|
+
evdev = select(3)
|
75
|
+
next unless evdev
|
76
|
+
begin
|
77
|
+
return Event.new(evdev)
|
78
|
+
rescue Errno::ENODEV
|
79
|
+
remove_device(evdev.path)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class Rkremap
|
2
|
+
class Event
|
3
|
+
Raw = Struct.new(:time, :type, :code, :value)
|
4
|
+
|
5
|
+
attr_accessor :app
|
6
|
+
attr_reader :raw, :ev_type
|
7
|
+
attr_writer :code
|
8
|
+
|
9
|
+
# @param evdev [Rkremap::Evdev]
|
10
|
+
# @param app [Rkremap::App]
|
11
|
+
def initialize(evdev)
|
12
|
+
@evdev = evdev
|
13
|
+
@raw = []
|
14
|
+
while true
|
15
|
+
s = evdev.read_event
|
16
|
+
@raw.push Raw.new(*s)
|
17
|
+
break if s[1] == EV_SYN
|
18
|
+
end
|
19
|
+
@ev_type = (@raw.find{_1[1] != EV_MSC && _1[1] != EV_SYN} || @raw.first)[1]
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Rkremap::Event::Raw]
|
23
|
+
def key
|
24
|
+
@key ||= @raw.find{_1.type == EV_KEY}
|
25
|
+
end
|
26
|
+
|
27
|
+
def device
|
28
|
+
@evdev
|
29
|
+
end
|
30
|
+
|
31
|
+
def code
|
32
|
+
@code ||= key.code
|
33
|
+
end
|
34
|
+
|
35
|
+
def value
|
36
|
+
key.value
|
37
|
+
end
|
38
|
+
|
39
|
+
def type
|
40
|
+
@type ||= value == 1 ? :press : value == 0 ? :release : :repeat
|
41
|
+
end
|
42
|
+
|
43
|
+
def match?(device:, code:, type:, app:)
|
44
|
+
(device.nil? || self.device.match?(device)) &&
|
45
|
+
(code.nil? || Array(code).include?(self.code)) &&
|
46
|
+
(type.nil? || Array(type).include?(self.type)) &&
|
47
|
+
(app.nil? || @app.nil? || @app.match?(app))
|
48
|
+
end
|
49
|
+
|
50
|
+
def skip
|
51
|
+
@skipped = true
|
52
|
+
end
|
53
|
+
|
54
|
+
def skipped?
|
55
|
+
@skipped
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/rkremap/keycode.rb
CHANGED
@@ -249,6 +249,87 @@ class Rkremap
|
|
249
249
|
KEY_WIMAX = KEY_WWAN
|
250
250
|
KEY_RFKILL = 247 # Key that controls all radios
|
251
251
|
KEY_MICMUTE = 248 # Mute / unmute the microphone
|
252
|
+
|
253
|
+
BTN_MISC = 0x100
|
254
|
+
BTN_0 = 0x100
|
255
|
+
BTN_1 = 0x101
|
256
|
+
BTN_2 = 0x102
|
257
|
+
BTN_3 = 0x103
|
258
|
+
BTN_4 = 0x104
|
259
|
+
BTN_5 = 0x105
|
260
|
+
BTN_6 = 0x106
|
261
|
+
BTN_7 = 0x107
|
262
|
+
BTN_8 = 0x108
|
263
|
+
BTN_9 = 0x109
|
264
|
+
|
265
|
+
BTN_LEFT = 0x110
|
266
|
+
BTN_MOUSE = 0x110
|
267
|
+
BTN_RIGHT = 0x111
|
268
|
+
BTN_MIDDLE = 0x112
|
269
|
+
BTN_SIDE = 0x113
|
270
|
+
BTN_EXTRA = 0x114
|
271
|
+
BTN_FORWARD = 0x115
|
272
|
+
BTN_BACK = 0x116
|
273
|
+
BTN_TASK = 0x117
|
274
|
+
|
275
|
+
BTN_JOYSTICK = 0x120
|
276
|
+
BTN_TRIGGER = 0x120
|
277
|
+
BTN_THUMB = 0x121
|
278
|
+
BTN_THUMB2 = 0x122
|
279
|
+
BTN_TOP = 0x123
|
280
|
+
BTN_TOP2 = 0x124
|
281
|
+
BTN_PINKIE = 0x125
|
282
|
+
BTN_BASE = 0x126
|
283
|
+
BTN_BASE2 = 0x127
|
284
|
+
BTN_BASE3 = 0x128
|
285
|
+
BTN_BASE4 = 0x129
|
286
|
+
BTN_BASE5 = 0x12a
|
287
|
+
BTN_BASE6 = 0x12b
|
288
|
+
BTN_DEAD = 0x12f
|
289
|
+
|
290
|
+
BTN_GAMEPAD = 0x130
|
291
|
+
BTN_SOUTH = 0x130
|
292
|
+
BTN_A = BTN_SOUTH
|
293
|
+
BTN_EAST = 0x131
|
294
|
+
BTN_B = BTN_EAST
|
295
|
+
BTN_C = 0x132
|
296
|
+
BTN_NORTH = 0x133
|
297
|
+
BTN_X = BTN_NORTH
|
298
|
+
BTN_WEST = 0x134
|
299
|
+
BTN_Y = BTN_WEST
|
300
|
+
BTN_Z = 0x135
|
301
|
+
BTN_TL = 0x136
|
302
|
+
BTN_TR = 0x137
|
303
|
+
BTN_TL2 = 0x138
|
304
|
+
BTN_TR2 = 0x139
|
305
|
+
BTN_SELECT = 0x13a
|
306
|
+
BTN_START = 0x13b
|
307
|
+
BTN_MODE = 0x13c
|
308
|
+
BTN_THUMBL = 0x13d
|
309
|
+
BTN_THUMBR = 0x13e
|
310
|
+
|
311
|
+
BTN_DIGI = 0x140
|
312
|
+
BTN_TOOL_PEN = 0x140
|
313
|
+
BTN_TOOL_RUBBER = 0x141
|
314
|
+
BTN_TOOL_BRUSH = 0x142
|
315
|
+
BTN_TOOL_PENCIL = 0x143
|
316
|
+
BTN_TOOL_AIRBRUSH = 0x144
|
317
|
+
BTN_TOOL_FINGER = 0x145
|
318
|
+
BTN_TOOL_MOUSE = 0x146
|
319
|
+
BTN_TOOL_LENS = 0x147
|
320
|
+
BTN_TOOL_QUINTTAP = 0x148 # Five fingers on trackpad
|
321
|
+
BTN_STYLUS3 = 0x149
|
322
|
+
BTN_TOUCH = 0x14a
|
323
|
+
BTN_STYLUS = 0x14b
|
324
|
+
BTN_STYLUS2 = 0x14c
|
325
|
+
BTN_TOOL_DOUBLETAP = 0x14d
|
326
|
+
BTN_TOOL_TRIPLETAP = 0x14e
|
327
|
+
BTN_TOOL_QUADTAP = 0x14f # Four fingers on trackpad
|
328
|
+
|
329
|
+
BTN_WHEEL = 0x150
|
330
|
+
BTN_GEAR_DOWN = 0x150
|
331
|
+
BTN_GEAR_UP = 0x151
|
332
|
+
|
252
333
|
KEY_OK = 0x160
|
253
334
|
KEY_SELECT = 0x161
|
254
335
|
KEY_GOTO = 0x162
|
@@ -502,7 +583,6 @@ class Rkremap
|
|
502
583
|
|
503
584
|
CODE_KEY = {}
|
504
585
|
KeyCode.constants.each do |k|
|
505
|
-
next unless k =~ /\AKEY_/
|
506
586
|
code = KeyCode.const_get(k)
|
507
587
|
CODE_KEY[code] = k unless CODE_KEY[code]
|
508
588
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Rkremap
|
2
|
+
# https://www.kernel.org/doc/html/v4.12/input/uinput.html
|
3
|
+
class Uinput
|
4
|
+
UI_SET_EVBIT = 1074025828
|
5
|
+
UI_SET_KEYBIT = 1074025829
|
6
|
+
UI_SET_RELBIT = 1074025830
|
7
|
+
UI_DEV_SETUP = 1079792899
|
8
|
+
UI_DEV_CREATE = 21761
|
9
|
+
|
10
|
+
REL_CNT = 0x10
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@dev = File.open('/dev/uinput', 'w')
|
14
|
+
setup
|
15
|
+
end
|
16
|
+
|
17
|
+
def setup
|
18
|
+
@dev.ioctl(UI_SET_EVBIT, EV_KEY)
|
19
|
+
KeyCode::KEY_CNT.times{|k| @dev.ioctl(UI_SET_KEYBIT, k)}
|
20
|
+
@dev.ioctl(UI_SET_EVBIT, EV_REL)
|
21
|
+
REL_CNT.times{|r| @dev.ioctl(UI_SET_RELBIT, r)}
|
22
|
+
bustype = 0x03 # BUS_USB
|
23
|
+
vendor = 0x1234 # てきとー
|
24
|
+
product = 0x5678 # てきとー
|
25
|
+
version = 1 # てきとー
|
26
|
+
name = 'rkremap'
|
27
|
+
ff_efects_max = 0
|
28
|
+
setup = [bustype, vendor, product, version, name, ff_efects_max].pack("SSSSZ80L") # struct uinput_setup
|
29
|
+
@dev.ioctl(UI_DEV_SETUP, setup)
|
30
|
+
@dev.ioctl(UI_DEV_CREATE)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param type [Integer] EV_KEY/ EV_SYN
|
34
|
+
# @param code [Integer] キーコード / SYN_REPORT
|
35
|
+
# @param value [Integer] 0:release / 1:press / 2:repeat
|
36
|
+
def write_event(type, code, value)
|
37
|
+
@dev.syswrite(['', type, code, value].pack('a16SSl'))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/rkremap/version.rb
CHANGED
data/lib/rkremap/winattr.rb
CHANGED
@@ -46,11 +46,14 @@ class Rkremap
|
|
46
46
|
# @return [String]
|
47
47
|
def app_title(window)
|
48
48
|
win = app_win(window)[0]
|
49
|
+
return '' if win == 0
|
49
50
|
prop = X11::XTextProperty.malloc(Fiddle::RUBY_FREE)
|
50
51
|
text_list = X11::Pointer.malloc(Fiddle::RUBY_FREE)
|
51
52
|
X11.XGetWMName(@display, win, prop)
|
52
53
|
X11.Xutf8TextPropertyToTextList(@display, prop, text_list, @buf)
|
53
|
-
|
54
|
+
ptr = text_list.ptr
|
55
|
+
return '' if ptr.null?
|
56
|
+
title = ptr.ptr.to_s.force_encoding('utf-8')
|
54
57
|
X11.XFreeStringList(text_list.ptr)
|
55
58
|
title
|
56
59
|
end
|
data/lib/rkremap.rb
CHANGED
@@ -1,13 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative 'rkremap/version'
|
4
4
|
require_relative 'rkremap/keycode'
|
5
5
|
require_relative 'rkremap/winattr'
|
6
|
+
require_relative 'rkremap/evdev_list'
|
6
7
|
require_relative 'rkremap/evdev'
|
8
|
+
require_relative 'rkremap/event'
|
9
|
+
require_relative 'rkremap/uinput'
|
7
10
|
|
8
11
|
class Rkremap
|
9
12
|
include KeyCode
|
10
13
|
|
14
|
+
# /usr/include/linux/input-event-codes.h
|
15
|
+
EV_SYN = 0
|
16
|
+
EV_KEY = 1
|
17
|
+
EV_REL = 2
|
18
|
+
EV_ABS = 3
|
19
|
+
EV_MSC = 4
|
20
|
+
SYN_REPORT = 0
|
21
|
+
|
11
22
|
EVENT_TYPE_VALUE = {
|
12
23
|
release: 0,
|
13
24
|
press: 1,
|
@@ -18,17 +29,21 @@ class Rkremap
|
|
18
29
|
attr_accessor :modifiers
|
19
30
|
attr_accessor :x11
|
20
31
|
attr_accessor :auto_detect
|
32
|
+
attr_accessor :exclude
|
33
|
+
attr_accessor :mouse
|
21
34
|
|
22
35
|
# @param devices [Array<String>, String]
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
else
|
36
|
+
# @param exclude [Regexp]
|
37
|
+
# @param mouse [Boolean]
|
38
|
+
def initialize(devices=[], exclude: nil, mouse: false)
|
39
|
+
if devices.empty?
|
28
40
|
@auto_detect = true
|
29
|
-
|
41
|
+
else
|
42
|
+
devices = Array(devices)
|
30
43
|
end
|
31
|
-
|
44
|
+
@devices = devices
|
45
|
+
@exclude = exclude
|
46
|
+
@mouse = mouse
|
32
47
|
@uinput = Uinput.new
|
33
48
|
@grab = false
|
34
49
|
@x11 = false
|
@@ -38,60 +53,45 @@ class Rkremap
|
|
38
53
|
KEY_LEFTALT, KEY_RIGHTALT,
|
39
54
|
KEY_LEFTMETA, KEY_RIGHTMETA,
|
40
55
|
]
|
41
|
-
@
|
56
|
+
@event_procs = []
|
42
57
|
@mutex = Mutex.new
|
43
58
|
end
|
44
59
|
|
60
|
+
# @param device [Symbol, Regexp]
|
45
61
|
# @param code [Integer]
|
46
62
|
# @param type [Symbol] :press / :release / :repeat
|
47
|
-
|
48
|
-
|
63
|
+
# @param app [Regexp, String]
|
64
|
+
def match(device: nil, code: nil, type: nil, app: nil, &block)
|
65
|
+
@event_procs.push [{device: device, code: code, type: type, app: app}, block]
|
49
66
|
end
|
50
67
|
|
51
68
|
def start(&block)
|
52
|
-
|
69
|
+
@evdev_list = EvdevList.new(@devices, auto_detect: @auto_detect, exclude: @exclude, detect_mouse: @mouse)
|
70
|
+
@evdev_list.grab = @grab
|
71
|
+
@evdev_list.detect_loop
|
53
72
|
@mod_state = @modifiers.map.to_h{|m| [m, false]}
|
54
|
-
|
55
|
-
@inputs.each(&:grab) if @grab
|
73
|
+
winattr = WinAttr.new if @x11
|
56
74
|
while true
|
57
75
|
@keys = []
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
next if event.skipped
|
66
|
-
synchronize{ proc_event(code, value, event) }
|
67
|
-
@keys.each do |c, mod, app|
|
68
|
-
synchronize{ block.call(c, mod, app) } if block
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def detect_device_loop
|
74
|
-
Thread.new do
|
75
|
-
while true
|
76
|
-
sleep 3
|
77
|
-
new_devs = Evdev.detect.select(&:keyboard?)
|
78
|
-
unless new_devs.empty?
|
79
|
-
new_devs.each(&:grab) if @grab
|
80
|
-
@inputs += new_devs
|
76
|
+
event = @evdev_list.read_event
|
77
|
+
if event.ev_type != EV_KEY
|
78
|
+
if event.device.grab?
|
79
|
+
event.raw.each do |r|
|
80
|
+
@uinput.write_event(r.type, r.code, r.value)
|
81
|
+
end
|
81
82
|
end
|
83
|
+
next
|
82
84
|
end
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
input.close rescue nil
|
94
|
-
@inputs.delete input
|
85
|
+
app = App.new(winattr) if winattr
|
86
|
+
event.app = app
|
87
|
+
@event_procs.each do |cond, b|
|
88
|
+
synchronize{ b.call(event, @mod_state.dup, app) } if event.match?(**cond)
|
89
|
+
break if event.skipped?
|
90
|
+
end
|
91
|
+
next if event.skipped?
|
92
|
+
synchronize{ proc_event(event, app) }
|
93
|
+
@keys.each do |c, mod, app_|
|
94
|
+
synchronize{ block.call(c, mod, app_) } if block
|
95
95
|
end
|
96
96
|
end
|
97
97
|
end
|
@@ -102,20 +102,27 @@ class Rkremap
|
|
102
102
|
|
103
103
|
# @param code [Integer]
|
104
104
|
# @param type [Symbol] :press / :release / :repeat
|
105
|
-
|
106
|
-
def event(code: nil, type: nil, event: nil)
|
105
|
+
def event(code: nil, type: nil)
|
107
106
|
value = EVENT_TYPE_VALUE[type] or raise "invalid type: #{type.inspect}"
|
108
|
-
|
107
|
+
update_modifiers(code, value)
|
108
|
+
write_event(code, value)
|
109
109
|
end
|
110
110
|
|
111
111
|
# @param code [Integer]
|
112
112
|
# @param mod [Hash] {MOD_KEY => true, ...}
|
113
113
|
def key(code, mod={})
|
114
|
+
with_modifier(mod) do
|
115
|
+
write_event(code, 1)
|
116
|
+
write_event(code, 0)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# @param mod [Hash] {MOD_KEY => true, ...}
|
121
|
+
def with_modifier(mod, &block)
|
114
122
|
mod_diff(mod).each do |mcode, state|
|
115
123
|
write_event(mcode, state ? 1 : 0)
|
116
124
|
end
|
117
|
-
|
118
|
-
write_event(code, 0)
|
125
|
+
block.call
|
119
126
|
mod_diff(mod).each do |mcode, _|
|
120
127
|
write_event(mcode, @mod_state[mcode] ? 1 : 0)
|
121
128
|
end
|
@@ -123,12 +130,23 @@ class Rkremap
|
|
123
130
|
|
124
131
|
private
|
125
132
|
|
126
|
-
def
|
133
|
+
def update_modifiers(code, value)
|
127
134
|
if @mod_state.include?(code)
|
128
135
|
@mod_state[code] = value != 0
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# @param event [Rkremap::Event]
|
140
|
+
# @param app [Rkremap::App]
|
141
|
+
def proc_event(event, app)
|
142
|
+
code, value = event.code, event.value
|
143
|
+
if @mod_state.include?(code)
|
144
|
+
write_event(code, value)
|
145
|
+
update_modifiers(code, value)
|
146
|
+
elsif value == 0
|
129
147
|
write_event(code, value)
|
130
|
-
|
131
|
-
@keys.push [code, @mod_state.dup,
|
148
|
+
else
|
149
|
+
@keys.push [code, @mod_state.dup, app]
|
132
150
|
end
|
133
151
|
end
|
134
152
|
|
@@ -143,43 +161,44 @@ class Rkremap
|
|
143
161
|
@uinput.write_event(EV_SYN, SYN_REPORT, 0)
|
144
162
|
end
|
145
163
|
|
146
|
-
class Event
|
147
|
-
attr_reader :time
|
148
|
-
attr_reader :code
|
149
|
-
attr_reader :type
|
150
|
-
attr_reader :win
|
151
|
-
attr_reader :skipped
|
152
|
-
|
153
|
-
def initialize(time, code, value, winattr)
|
154
|
-
@time = time
|
155
|
-
@code = code
|
156
|
-
@value = value
|
157
|
-
@type = value == 1 ? :press : value == 0 ? :release : :repeat
|
158
|
-
@win = winattr&.focus_win
|
159
|
-
@skipped = false
|
160
|
-
end
|
161
|
-
|
162
|
-
def match?(code: nil, type: nil)
|
163
|
-
(code.nil? || Array(code).include?(@code)) && (type.nil? || Array(type).include?(@type))
|
164
|
-
end
|
165
|
-
|
166
|
-
def skip
|
167
|
-
@skipped = true
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
164
|
class App
|
172
|
-
|
173
|
-
|
165
|
+
# @param winattr [Rkremap::WinAttr]
|
166
|
+
def initialize(winattr)
|
174
167
|
@winattr = winattr
|
168
|
+
@win = winattr.focus_win
|
175
169
|
end
|
176
170
|
|
171
|
+
# @return [String, nil]
|
177
172
|
def class_name
|
178
173
|
@winattr.app_win(@win)[2] if @winattr && @win
|
179
174
|
end
|
180
175
|
|
176
|
+
# @return [String, nil]
|
181
177
|
def title
|
182
178
|
@winattr.app_title(@win) if @winattr && @win
|
183
179
|
end
|
180
|
+
|
181
|
+
# @param app [Array, Hash, String, Regexp]
|
182
|
+
# @return [Boolean]
|
183
|
+
def match?(app)
|
184
|
+
Array(app).each do |a|
|
185
|
+
a = {class_name: a, title: a} unless a.is_a? Hash
|
186
|
+
return true if match_sub(a[:class_name], class_name) || match_sub(a[:title], title)
|
187
|
+
end
|
188
|
+
false
|
189
|
+
end
|
190
|
+
|
191
|
+
# @param a [String, Regexp]
|
192
|
+
# @param b [String]
|
193
|
+
def match_sub(a, b)
|
194
|
+
case a
|
195
|
+
when String
|
196
|
+
a == b
|
197
|
+
when Regexp
|
198
|
+
a =~ b
|
199
|
+
else
|
200
|
+
false
|
201
|
+
end
|
202
|
+
end
|
184
203
|
end
|
185
204
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rkremap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- TOMITA Masahiro
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
12
|
-
dependencies:
|
11
|
+
date: 2022-08-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: tmtms_timer
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.3'
|
13
27
|
description: key remapper
|
14
28
|
email:
|
15
29
|
- tommy@tmtm.org
|
@@ -24,7 +38,10 @@ files:
|
|
24
38
|
- example/tmtms.rb
|
25
39
|
- lib/rkremap.rb
|
26
40
|
- lib/rkremap/evdev.rb
|
41
|
+
- lib/rkremap/evdev_list.rb
|
42
|
+
- lib/rkremap/event.rb
|
27
43
|
- lib/rkremap/keycode.rb
|
44
|
+
- lib/rkremap/uinput.rb
|
28
45
|
- lib/rkremap/version.rb
|
29
46
|
- lib/rkremap/winattr.rb
|
30
47
|
homepage: https://gitlab.com/tmtms/rkremap
|