rkremap 0.3.0.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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