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 +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
|