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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55d8872f73bd984afd69fdf3c17e53f39de16a4f8feef40bc851b6527e57aa17
4
- data.tar.gz: 99c6a99b3fdd44e206d416eab361ca1ab4c9e4defc0d96bfa2fc0e563a5a1e4c
3
+ metadata.gz: 127371f8cab9cd5ff002b35b886b79a3a4424d779f4bf938b9006fb633917a51
4
+ data.tar.gz: a02bd1649a62f30916e125582409f9b9ab8d57c33caf10bdfc5f7e6392ece992
5
5
  SHA512:
6
- metadata.gz: c9ad9908d2a602e2740748924e5cd83c1f6526eda164c389cde0b22d1b4462d74c7f5eba3809f2c353396500efe5a5447cb22302a0c8db9aaf4fbfa739f1c6a9
7
- data.tar.gz: 936cef85543d1089d81323ddd4f11a7b4da2afb44d6f7d7c9921132182e300e963a73ad8e51f8e80a6cf879251caad0c5235cddc0cc73a51dc315f2b5a487744
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
- ### `Rkremap.new`
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` に設定するとキーイベントを奪う。キーイベントを見るだけでいいなら `false` を設定する。
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)
@@ -1,8 +1,8 @@
1
- # 変換/無変換キーを0.5秒以上押してるとALTキーとして扱う。
2
- # 0.5秒以内でも変換/無変換キーを押しながら他のキーを押すとALTキーとして扱う。
1
+ # 変換/無変換キーを0.5秒以上押してるとSUPERキーとして扱う。
2
+ # 0.5秒以内でも変換/無変換キーを押しながら他のキーを押すとSUPERキーとして扱う。
3
3
 
4
4
  require 'rkremap'
5
- require 'tmtms_timer' # gem install 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
@@ -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
- rk.start do |code, mod, app|
40
- # Emacs や端末等ではそのまま
41
- if app.class_name == 'Emacs' || app.class_name =~ /terminal/i || app.class_name == 'VirtualBox Machine'
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!(mod_disable_meta, {KEY_LEFTCTRL => true})
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, {KEY_LEFTCTRL => true}))
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({KEY_LEFTSHIFT => true}))
65
- rk.key(KEY_X, mod_disable_all.merge({KEY_LEFTCTRL => true}))
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' # gem install 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
- rk.event(code: KEY_LEFTCTRL, type: event.type)
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
- rk.start do |code, mod, app|
90
- # JISキーボードを英語配列にしたときに一部キーを変える
91
- code = key_map[code] || code
92
- if code == KEY_RO
93
- code = mod[KEY_LEFTSHIFT] || mod[KEY_RIGHTSHIFT] ? KEY_MINUS : KEY_BACKSLASH
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
- # Emacs や端末等ではそのまま
97
- if app.class_name == 'Emacs' || app.class_name =~ /terminal/i || app.class_name == 'VirtualBox Machine'
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
- # SUPER+[A-Z]/Enter を Ctrl+[A-Z]/Enter に変換
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!(mod_disable_meta, {KEY_LEFTCTRL => true})
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, {KEY_LEFTCTRL => true}))
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({KEY_LEFTSHIFT => true}))
126
- rk.key(KEY_X, mod_disable_all.merge({KEY_LEFTCTRL => true}))
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, (KEY_MAX-1)/8+1)
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
- self.class.io2evdev[@io] = self
71
- @io
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
- buf = ' '
93
- io.ioctl(EVIOCGNAME, buf)
94
- return false if buf =~ /\Arkremap\0*\z/
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
- capable?(KeyCode::KEY_A) && capable?(KeyCode::KEY_Z) && capable?(KeyCode::KEY_SPACE)
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 type [Integer] EV_KEY/ EV_SYN
148
- # @param code [Integer] キーコード / SYN_REPORT
149
- # @param value [Integer] 0:release / 1:press / 2:repeat
150
- def write_event(type, code, value)
151
- @dev.syswrite(['', type, code, value].pack('a16SSl'))
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Rkremap
4
- VERSION = "0.3.0.1"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -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
- title = text_list.ptr.ptr.to_s.force_encoding('utf-8')
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 "rkremap/version"
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
- def initialize(devices=nil)
24
- if devices
25
- devices = Array(devices)
26
- @inputs = devices.map{|d| Evdev.new(d)}
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
- @inputs = Evdev.detect.select(&:keyboard?)
41
+ else
42
+ devices = Array(devices)
30
43
  end
31
- raise 'Unable to detect keyboard device' if @inputs.empty?
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
- @events = []
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
- def match(code: nil, type: nil, &block)
48
- @events.push [{code: code, type: type}, block]
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
- detect_device_loop if @auto_detect
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
- @winattr = WinAttr.new if @x11
55
- @inputs.each(&:grab) if @grab
73
+ winattr = WinAttr.new if @x11
56
74
  while true
57
75
  @keys = []
58
- time, type, code, value = read_event
59
- next if type != EV_KEY
60
- event = Event.new(time, code, value, @winattr)
61
- @events.each do |cond, b|
62
- synchronize{ b.call event } if event.match?(**cond)
63
- break if event.skipped
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
- end
84
- end
85
-
86
- def read_event
87
- while true
88
- begin
89
- input = Evdev.select(@inputs, 3)
90
- next unless input
91
- return input.read_event
92
- rescue Errno::ENODEV
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
- # @param ev [Rkmap::Event]
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
- proc_event(code, value, event)
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
- write_event(code, 1)
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 proc_event(code, value, event)
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
- elsif value != 0
131
- @keys.push [code, @mod_state.dup, App.new(event&.win, @winattr)]
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
- def initialize(win, winattr)
173
- @win = win
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.3.0.1
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-07-18 00:00:00.000000000 Z
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