rkremap 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 945483dcd11240d5c54b359a15055219532f2f311275633503e7d485aa68917b
4
+ data.tar.gz: d594c9b549696f964cde750094169ba7198c0ac9ab5f5dfc59a7936d9610a09d
5
+ SHA512:
6
+ metadata.gz: a1412a373a0673172a9eba0449613ab9a76debf400b622977887031c294f8381a58a7c060ef6451e6264703690974aeddb305ce35619c07958a2b5a1239ecf12
7
+ data.tar.gz: 619ee1de0814c2542c2a4f347dca7c48e640590a01ff58f2b7362ed80ef447acaf1576df2a2a659eaac95c9a7ec0a8f52d732d8024356613fbef43f055b16dd2
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # Rkremap
2
+
3
+ Linux でキー割り当てを変更するプログラムを作るためのライブラリ。
4
+
5
+ ## Installation
6
+
7
+ ```
8
+ % gem install rkremap
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ライブラリなのでプログラムを自分で作る必要がある。具体的な使い方は `example/*.rb` を見ればよい。
14
+
15
+ `/dev/input/event*` と `/dev/uinput` に対する権限が必要。`sudo` 等で `root` 権限で動かすのが良いかも。
16
+
17
+ ユーザーを `input` グループに所属させると `/dev/input/event*` を読み取る権限をつけられるけど、そのユーザーはすべての入力デバイスの入力を読み取ることができるようになってしまうので、セキュリティ的にはビミョーなところ。
18
+
19
+ `input` グループでも `/dev/uinput` に権限はないけど、`/etc/udev/rules.d/input.rules` ファイルを
20
+ ```
21
+ KERNEL=="uinput", GROUP="input"
22
+ ```
23
+ という内容で作れば `/dev/uinput` のグループを `input` にできるっぽい。
24
+
25
+
26
+ ### `Rkremap.new`
27
+
28
+ 引数に `/dev/input/event*` を指定する。キーボードデバイス以外を指定してもたぶん無意味。
29
+ キーボードのデバイスファイル名を調べるには
30
+ ```
31
+ cat /proc/bus/input/devices
32
+ ```
33
+ とかすればわかるかもしれない。または
34
+ ```
35
+ sudo evtest /dev/input/eventX
36
+ ```
37
+ とかして何かキーを押して出力があればそれ。
38
+
39
+ ThinkPad 本体のキーボードは `/dev/input/event3` だった。
40
+
41
+ ### Rkremap#grab
42
+
43
+ `true` に設定するとキーイベントを奪う。キーイベントを見るだけでいいなら `false` を設定する。
44
+
45
+ ### Rkremap#x11
46
+
47
+ `true` に設定すると X のアプリ名等を取得できる。X 環境下でないなら `false` を設定する。
48
+
49
+ ### Rkremap#start
50
+
51
+ なにかキーを押されたらブロックを実行する。修飾キー単体ではブロックは実行されない。
52
+ ブロックの引数はキーコード(`Integer`)と修飾キー(`Hash`)と `Rkremap::App`。
53
+ `Rkremap::App#class_name` でアプリのクラス名、`Rkremap::App#title` でアプリのタイトルを取得できる。
54
+
55
+ ### Rkremap#match
56
+
57
+ キーイベントが引数に適合するとブロックを実行する。ブロック引数は Event オブジェクト。
58
+ `Event#skip` するとこのイベントはスキップされる。
59
+
60
+ ### Rkremap#key
61
+
62
+ キーを押したことにする。引数はキーコード(`Integer`)と修飾キー(`Hash`)。
63
+
64
+ ### Rkremap#event
65
+
66
+ キーイベントを発生させる。`code` 引数はキーコード(`Integer`)。`type` 引数はイベントタイプ(`Symbol`)で `:press`, `:release`。
67
+
68
+ ### Rkremap#synchronize
69
+
70
+ `start` や `match` のブロックは同時に実行されないようにするため Mutex を使用している。このメソッドで同じ Mutex を用いた排他制御を行う。
71
+
72
+ ### Rkremap::CODE_KEY
73
+
74
+ コードからキー名のシンボルに変換するための Hash。
75
+
76
+ ## 参考
77
+
78
+ * [mooz/xkeysnail: Yet another keyboard remapping tool for X environment](https://github.com/mooz/xkeysnail)
79
+ * [xkeysnail - もうひとつの Linux 向けキーリマッパ - Qiita](https://qiita.com/mooz@github/items/c5f25f27847333dd0b37)
80
+ * [k0kubun/xremap: Dynamic key remapper for X11 and Wayland](https://github.com/k0kubun/xremap)
81
+ * [Linux向けの最強のキーリマッパーを作った - k0kubun's blog](https://k0kubun.hatenablog.com/entry/xkremap)
82
+ * [kui/rbindkeys: key remap with ruby](https://github.com/kui/rbindkeys)
83
+ * [Ruby で設定できる Linux 環境向けキーリマッパー作った — 電卓片手に](http://k-ui.jp/blog/2012/06/06/rbindkeys-configurable-key-remapper-in-ruby/)
84
+ * [Linux の入力デバイスをカスタマイズ - Qiita](https://qiita.com/propella/items/a73fc333c95d14d06835)
85
+ * [Linux Input Subsystemの使い方](http://www.tatapa.org/~takuo/input_subsystem/input_subsystem.html)
86
+ * [1. Linux Input Subsystem userspace API — The Linux Kernel documentation](https://www.kernel.org/doc/html/v4.12/input/input_uapi.html)
87
+
88
+ ## License
89
+
90
+ MIT license
@@ -0,0 +1,93 @@
1
+ # 変換/無変換キーを0.5秒以上押してるとALTキーとして扱う。
2
+ # 0.5秒以内でも変換/無変換キーを押しながら他のキーを押すとALTキーとして扱う。
3
+
4
+ require 'rkremap'
5
+ include Rkremap::KeyCode
6
+
7
+ rk = Rkremap.new('/dev/input/event3')
8
+ rk.grab = true
9
+
10
+ class State
11
+ attr_accessor :converted
12
+
13
+ def initialize
14
+ @press = nil
15
+ @converted = false
16
+ @thread = nil
17
+ end
18
+
19
+ def press(thread)
20
+ @press = true
21
+ @converted = false
22
+ @thread = thread
23
+ end
24
+
25
+ def release
26
+ @press = false
27
+ @converted = false
28
+ @thread.kill
29
+ @thread = nil
30
+ end
31
+
32
+ def pressed?
33
+ @press
34
+ end
35
+ end
36
+
37
+ state = {
38
+ KEY_HENKAN => State.new,
39
+ KEY_MUHENKAN => State.new,
40
+ }
41
+
42
+ MAP = {
43
+ KEY_HENKAN => KEY_RIGHTALT,
44
+ KEY_MUHENKAN => KEY_LEFTALT,
45
+ }
46
+
47
+ rk.match(code: [KEY_HENKAN, KEY_MUHENKAN], type: :press) do |event|
48
+ thr = Thread.new(event.code) do |code|
49
+ sleep 0.5
50
+ rk.synchronize do
51
+ if state[code].pressed?
52
+ rk.event(code: MAP[code], type: :press)
53
+ state[code].converted = true
54
+ end
55
+ end
56
+ end
57
+ state[event.code].press(thr)
58
+ event.skip
59
+ end
60
+
61
+ rk.match(code: [KEY_HENKAN, KEY_MUHENKAN], type: :release) do |event|
62
+ if state[event.code].converted
63
+ rk.event(code: MAP[event.code], type: :release)
64
+ event.skip
65
+ else
66
+ rk.event(code: event.code, type: :press)
67
+ rk.event(code: event.code, type: :release)
68
+ end
69
+ state[event.code].release
70
+ end
71
+
72
+ rk.match(code: [KEY_HENKAN, KEY_MUHENKAN], type: :repeat) do |event|
73
+ event.skip
74
+ end
75
+
76
+ rk.match(type: :press) do
77
+ [KEY_HENKAN, KEY_MUHENKAN].each do |code|
78
+ if state[code].pressed?
79
+ rk.event(code: MAP[code], type: :press)
80
+ state[code].converted = true
81
+ end
82
+ end
83
+ end
84
+
85
+ def code2key(code)
86
+ Rkremap::CODE_KEY[code].to_s.sub(/\AKEY_/, '')
87
+ end
88
+
89
+ rk.start do |code, mod|
90
+ key = (mod.select{|_, v| v}.keys + [code]).map{|c| code2key(c)}.join('-')
91
+ puts key
92
+ rk.key(code, mod)
93
+ end
@@ -0,0 +1,16 @@
1
+ # 押されたキーを記録
2
+
3
+ require 'rkremap'
4
+
5
+ def code2key(code)
6
+ Rkremap::CODE_KEY[code].to_s.sub(/\AKEY_/, '')
7
+ end
8
+
9
+ rk = Rkremap.new('/dev/input/event3')
10
+ rk.grab = false
11
+ rk.x11 = true
12
+ rk.start do |code, mod, app|
13
+ key = (mod.select{|_, v| v}.keys + [code]).map{|c| code2key(c)}.join('-')
14
+ key << " at #{app.title} [#{app.class_name}]" if rk.x11
15
+ puts key
16
+ end
@@ -0,0 +1,74 @@
1
+ # Mac のショートカット風
2
+
3
+ require 'rkremap'
4
+
5
+ include Rkremap::KeyCode
6
+
7
+ ctrl_map = {
8
+ KEY_A => KEY_HOME,
9
+ KEY_E => KEY_END,
10
+ KEY_B => KEY_LEFT,
11
+ KEY_F => KEY_RIGHT,
12
+ KEY_N => KEY_DOWN,
13
+ KEY_P => KEY_UP,
14
+ KEY_D => KEY_DELETE,
15
+ KEY_H => KEY_BACKSPACE,
16
+ KEY_I => KEY_TAB,
17
+ }
18
+ mod_disable_ctrl = {
19
+ KEY_LEFTCTRL => false,
20
+ KEY_RIGHTCTRL => false,
21
+ }
22
+ mod_disable_shift = {
23
+ KEY_LEFTSHIFT => false,
24
+ KEY_RIGHTSHIFT => false,
25
+ }
26
+ mod_disable_alt = {
27
+ KEY_LEFTALT => false,
28
+ KEY_RIGHTALT => false,
29
+ }
30
+ mod_disable_meta = {
31
+ KEY_LEFTMETA => false,
32
+ KEY_RIGHTMETA => false,
33
+ }
34
+ mod_disable_all = {}.merge(mod_disable_ctrl, mod_disable_shift, mod_disable_alt, mod_disable_meta)
35
+
36
+ rk = Rkremap.new("/dev/input/event3")
37
+ rk.grab = true
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'
42
+ rk.key(code, mod)
43
+ next
44
+ end
45
+
46
+ # ALT+[A-Z]/Enter を Ctrl+[A-Z]/Enter に変換
47
+ if (mod[KEY_LEFTALT] || mod[KEY_RIGHTALT]) && (Rkremap::CODE_KEY[code] =~ /\AKEY_[A-Z]\z/ || code == KEY_ENTER)
48
+ mod.merge!(mod_disable_alt, {KEY_LEFTCTRL => true})
49
+ mod[KEY_LEFTCTRL] = true
50
+ rk.key(code, mod)
51
+ next
52
+ end
53
+
54
+ # Ctrl は他のキーに変換
55
+ if mod[KEY_LEFTCTRL] || mod[KEY_RIGHTCTRL]
56
+ # Ctrl+K は行末まで削除
57
+ if code == KEY_K
58
+ rk.key(KEY_END, mod_disable_all.merge({KEY_LEFTSHIFT => true}))
59
+ rk.key(KEY_X, mod_disable_all.merge({KEY_LEFTCTRL => true}))
60
+ next
61
+ end
62
+ # 単純な変換
63
+ if ctrl_map[code]
64
+ rk.key(ctrl_map[code], mod_disable_ctrl)
65
+ next
66
+ end
67
+ # その他の Ctrl+[A-Z] は無視
68
+ if Rkremap::CODE_KEY[code] =~ /\AKEY_[A-Z]\z/
69
+ next
70
+ end
71
+ end
72
+
73
+ rk.key(code, mod)
74
+ end
data/example/tmtms.rb ADDED
@@ -0,0 +1,180 @@
1
+ # tmtms 用
2
+ #
3
+ # fcitx が有効な場合に /tmp/fcitx-enabled ファイルを作るスクリプトを動かしておく:
4
+ # while :; do
5
+ # if [ $(fcitx-remote) -eq 2 ]; then
6
+ # touch /tmp/fcitx-enabled
7
+ # else
8
+ # rm -f /tmp/fcitx-enabled
9
+ # fi
10
+ # sleep 0.1
11
+ # done
12
+
13
+ require 'rkremap'
14
+ include Rkremap::KeyCode
15
+
16
+ class State
17
+ attr_accessor :converted
18
+
19
+ def initialize
20
+ @press = nil
21
+ @converted = false
22
+ @thread = nil
23
+ end
24
+
25
+ def press(thread)
26
+ @press = true
27
+ @converted = false
28
+ @thread = thread
29
+ end
30
+
31
+ def release
32
+ @press = false
33
+ @converted = false
34
+ @thread.kill
35
+ @thread = nil
36
+ end
37
+
38
+ def pressed?
39
+ @press
40
+ end
41
+ end
42
+
43
+ rk = Rkremap.new("/dev/input/event3")
44
+ rk.grab = true
45
+ rk.x11 = true
46
+
47
+ # 変換/無変換キーを0.5秒以上押してるとALTキーとして扱う。
48
+ # 0.5秒以内でも変換/無変換キーを押しながら他のキーを押すとALTキーとして扱う。
49
+
50
+ state = {
51
+ KEY_HENKAN => State.new,
52
+ KEY_MUHENKAN => State.new,
53
+ }
54
+
55
+ henkan_map = {
56
+ KEY_HENKAN => KEY_RIGHTALT,
57
+ KEY_MUHENKAN => KEY_LEFTALT,
58
+ }
59
+
60
+ rk.match(code: [KEY_HENKAN, KEY_MUHENKAN], type: :press) do |event|
61
+ thr = Thread.new(event.code) do |code|
62
+ sleep 0.5
63
+ rk.synchronize do
64
+ if state[code].pressed?
65
+ rk.event(code: henkan_map[code], type: :press)
66
+ state[code].converted = true
67
+ end
68
+ end
69
+ end
70
+ state[event.code].press(thr)
71
+ event.skip
72
+ end
73
+
74
+ rk.match(code: [KEY_HENKAN, KEY_MUHENKAN], type: :release) do |event|
75
+ if state[event.code].converted
76
+ rk.event(code: henkan_map[event.code], type: :release)
77
+ event.skip
78
+ else
79
+ rk.event(code: event.code, type: :press)
80
+ rk.event(code: event.code, type: :release)
81
+ end
82
+ state[event.code].release
83
+ end
84
+
85
+ rk.match(code: [KEY_HENKAN, KEY_MUHENKAN], type: :repeat) do |event|
86
+ event.skip
87
+ end
88
+
89
+ rk.match(type: :press) do
90
+ [KEY_HENKAN, KEY_MUHENKAN].each do |code|
91
+ if state[code].pressed?
92
+ rk.event(code: henkan_map[code], type: :press)
93
+ state[code].converted = true
94
+ end
95
+ end
96
+ end
97
+
98
+ # CAPSLOCK は CTRL に変換
99
+ rk.match(code: KEY_CAPSLOCK) do |event|
100
+ rk.event(code: KEY_LEFTCTRL, type: event.type)
101
+ event.skip
102
+ end
103
+
104
+ ctrl_map = {
105
+ KEY_A => KEY_HOME,
106
+ KEY_E => KEY_END,
107
+ KEY_B => KEY_LEFT,
108
+ KEY_F => KEY_RIGHT,
109
+ KEY_N => KEY_DOWN,
110
+ KEY_P => KEY_UP,
111
+ KEY_D => KEY_DELETE,
112
+ KEY_H => KEY_BACKSPACE,
113
+ KEY_I => KEY_TAB,
114
+ }
115
+ mod_disable_ctrl = {
116
+ KEY_LEFTCTRL => false,
117
+ KEY_RIGHTCTRL => false,
118
+ }
119
+ mod_disable_shift = {
120
+ KEY_LEFTSHIFT => false,
121
+ KEY_RIGHTSHIFT => false,
122
+ }
123
+ mod_disable_alt = {
124
+ KEY_LEFTALT => false,
125
+ KEY_RIGHTALT => false,
126
+ }
127
+ mod_disable_meta = {
128
+ KEY_LEFTMETA => false,
129
+ KEY_RIGHTMETA => false,
130
+ }
131
+ mod_disable_all = {}.merge(mod_disable_ctrl, mod_disable_shift, mod_disable_alt, mod_disable_meta)
132
+
133
+ key_map = {
134
+ KEY_YEN => KEY_BACKSLASH,
135
+ KEY_BACKSLASH => KEY_GRAVE,
136
+ KEY_GRAVE => KEY_ESC,
137
+ }
138
+
139
+ rk.start do |code, mod, app|
140
+ # JISキーボードを英語配列にしたときに一部キーを変える
141
+ code = key_map[code] || code
142
+ if code == KEY_RO
143
+ code = mod[KEY_LEFTSHIFT] || mod[KEY_RIGHTSHIFT] ? KEY_MINUS : KEY_BACKSLASH
144
+ end
145
+
146
+ # Emacs や端末等ではそのまま
147
+ if app.class_name == 'Emacs' || app.class_name =~ /terminal/i || app.class_name == 'VirtualBox Machine'
148
+ rk.key(code, mod)
149
+ next
150
+ end
151
+
152
+ # ALT+[A-Z]/Enter を Ctrl+[A-Z]/Enter に変換
153
+ if (mod[KEY_LEFTALT] || mod[KEY_RIGHTALT]) && (Rkremap::CODE_KEY[code] =~ /\AKEY_[A-Z]\z/ || code == KEY_ENTER)
154
+ mod.merge!(mod_disable_alt, {KEY_LEFTCTRL => true})
155
+ mod[KEY_LEFTCTRL] = true
156
+ rk.key(code, mod)
157
+ next
158
+ end
159
+
160
+ # Ctrl は他のキーに変換
161
+ if mod[KEY_LEFTCTRL] || mod[KEY_RIGHTCTRL]
162
+ # Ctrl+K は行末まで削除 (日本語変換時はそのまま)
163
+ if code == KEY_K && !File.exist?('/tmp/fcitx-enabled')
164
+ rk.key(KEY_END, mod_disable_all.merge({KEY_LEFTSHIFT => true}))
165
+ rk.key(KEY_X, mod_disable_all.merge({KEY_LEFTCTRL => true}))
166
+ next
167
+ end
168
+ # 単純な変換
169
+ if ctrl_map[code]
170
+ rk.key(ctrl_map[code], mod_disable_ctrl)
171
+ next
172
+ end
173
+ # その他の Ctrl+[A-Z] は無視
174
+ if Rkremap::CODE_KEY[code] =~ /\AKEY_[A-Z]\z/
175
+ next
176
+ end
177
+ end
178
+
179
+ rk.key(code, mod)
180
+ end
@@ -0,0 +1,509 @@
1
+ class Rkremap
2
+ module KeyCode
3
+ # generated from /usr/include/linux/input-event-codes.h
4
+ KEY_RESERVED = 0
5
+ KEY_ESC = 1
6
+ KEY_1 = 2
7
+ KEY_2 = 3
8
+ KEY_3 = 4
9
+ KEY_4 = 5
10
+ KEY_5 = 6
11
+ KEY_6 = 7
12
+ KEY_7 = 8
13
+ KEY_8 = 9
14
+ KEY_9 = 10
15
+ KEY_0 = 11
16
+ KEY_MINUS = 12
17
+ KEY_EQUAL = 13
18
+ KEY_BACKSPACE = 14
19
+ KEY_TAB = 15
20
+ KEY_Q = 16
21
+ KEY_W = 17
22
+ KEY_E = 18
23
+ KEY_R = 19
24
+ KEY_T = 20
25
+ KEY_Y = 21
26
+ KEY_U = 22
27
+ KEY_I = 23
28
+ KEY_O = 24
29
+ KEY_P = 25
30
+ KEY_LEFTBRACE = 26
31
+ KEY_RIGHTBRACE = 27
32
+ KEY_ENTER = 28
33
+ KEY_LEFTCTRL = 29
34
+ KEY_A = 30
35
+ KEY_S = 31
36
+ KEY_D = 32
37
+ KEY_F = 33
38
+ KEY_G = 34
39
+ KEY_H = 35
40
+ KEY_J = 36
41
+ KEY_K = 37
42
+ KEY_L = 38
43
+ KEY_SEMICOLON = 39
44
+ KEY_APOSTROPHE = 40
45
+ KEY_GRAVE = 41
46
+ KEY_LEFTSHIFT = 42
47
+ KEY_BACKSLASH = 43
48
+ KEY_Z = 44
49
+ KEY_X = 45
50
+ KEY_C = 46
51
+ KEY_V = 47
52
+ KEY_B = 48
53
+ KEY_N = 49
54
+ KEY_M = 50
55
+ KEY_COMMA = 51
56
+ KEY_DOT = 52
57
+ KEY_SLASH = 53
58
+ KEY_RIGHTSHIFT = 54
59
+ KEY_KPASTERISK = 55
60
+ KEY_LEFTALT = 56
61
+ KEY_SPACE = 57
62
+ KEY_CAPSLOCK = 58
63
+ KEY_F1 = 59
64
+ KEY_F2 = 60
65
+ KEY_F3 = 61
66
+ KEY_F4 = 62
67
+ KEY_F5 = 63
68
+ KEY_F6 = 64
69
+ KEY_F7 = 65
70
+ KEY_F8 = 66
71
+ KEY_F9 = 67
72
+ KEY_F10 = 68
73
+ KEY_NUMLOCK = 69
74
+ KEY_SCROLLLOCK = 70
75
+ KEY_KP7 = 71
76
+ KEY_KP8 = 72
77
+ KEY_KP9 = 73
78
+ KEY_KPMINUS = 74
79
+ KEY_KP4 = 75
80
+ KEY_KP5 = 76
81
+ KEY_KP6 = 77
82
+ KEY_KPPLUS = 78
83
+ KEY_KP1 = 79
84
+ KEY_KP2 = 80
85
+ KEY_KP3 = 81
86
+ KEY_KP0 = 82
87
+ KEY_KPDOT = 83
88
+ KEY_ZENKAKUHANKAKU = 85
89
+ KEY_102ND = 86
90
+ KEY_F11 = 87
91
+ KEY_F12 = 88
92
+ KEY_RO = 89
93
+ KEY_KATAKANA = 90
94
+ KEY_HIRAGANA = 91
95
+ KEY_HENKAN = 92
96
+ KEY_KATAKANAHIRAGANA = 93
97
+ KEY_MUHENKAN = 94
98
+ KEY_KPJPCOMMA = 95
99
+ KEY_KPENTER = 96
100
+ KEY_RIGHTCTRL = 97
101
+ KEY_KPSLASH = 98
102
+ KEY_SYSRQ = 99
103
+ KEY_RIGHTALT = 100
104
+ KEY_LINEFEED = 101
105
+ KEY_HOME = 102
106
+ KEY_UP = 103
107
+ KEY_PAGEUP = 104
108
+ KEY_LEFT = 105
109
+ KEY_RIGHT = 106
110
+ KEY_END = 107
111
+ KEY_DOWN = 108
112
+ KEY_PAGEDOWN = 109
113
+ KEY_INSERT = 110
114
+ KEY_DELETE = 111
115
+ KEY_MACRO = 112
116
+ KEY_MUTE = 113
117
+ KEY_VOLUMEDOWN = 114
118
+ KEY_VOLUMEUP = 115
119
+ KEY_POWER = 116 # SC System Power Down
120
+ KEY_KPEQUAL = 117
121
+ KEY_KPPLUSMINUS = 118
122
+ KEY_PAUSE = 119
123
+ KEY_SCALE = 120 # AL Compiz Scale (Expose)
124
+ KEY_KPCOMMA = 121
125
+ KEY_HANGEUL = 122
126
+ KEY_HANGUEL = KEY_HANGEUL
127
+ KEY_HANJA = 123
128
+ KEY_YEN = 124
129
+ KEY_LEFTMETA = 125
130
+ KEY_RIGHTMETA = 126
131
+ KEY_COMPOSE = 127
132
+ KEY_STOP = 128 # AC Stop
133
+ KEY_AGAIN = 129
134
+ KEY_PROPS = 130 # AC Properties
135
+ KEY_UNDO = 131 # AC Undo
136
+ KEY_FRONT = 132
137
+ KEY_COPY = 133 # AC Copy
138
+ KEY_OPEN = 134 # AC Open
139
+ KEY_PASTE = 135 # AC Paste
140
+ KEY_FIND = 136 # AC Search
141
+ KEY_CUT = 137 # AC Cut
142
+ KEY_HELP = 138 # AL Integrated Help Center
143
+ KEY_MENU = 139 # Menu (show menu)
144
+ KEY_CALC = 140 # AL Calculator
145
+ KEY_SETUP = 141
146
+ KEY_SLEEP = 142 # SC System Sleep
147
+ KEY_WAKEUP = 143 # System Wake Up
148
+ KEY_FILE = 144 # AL Local Machine Browser
149
+ KEY_SENDFILE = 145
150
+ KEY_DELETEFILE = 146
151
+ KEY_XFER = 147
152
+ KEY_PROG1 = 148
153
+ KEY_PROG2 = 149
154
+ KEY_WWW = 150 # AL Internet Browser
155
+ KEY_MSDOS = 151
156
+ KEY_COFFEE = 152 # AL Terminal Lock/Screensaver
157
+ KEY_SCREENLOCK = KEY_COFFEE
158
+ KEY_ROTATE_DISPLAY = 153 # Display orientation for e.g. tablets
159
+ KEY_DIRECTION = KEY_ROTATE_DISPLAY
160
+ KEY_CYCLEWINDOWS = 154
161
+ KEY_MAIL = 155
162
+ KEY_BOOKMARKS = 156 # AC Bookmarks
163
+ KEY_COMPUTER = 157
164
+ KEY_BACK = 158 # AC Back
165
+ KEY_FORWARD = 159 # AC Forward
166
+ KEY_CLOSECD = 160
167
+ KEY_EJECTCD = 161
168
+ KEY_EJECTCLOSECD = 162
169
+ KEY_NEXTSONG = 163
170
+ KEY_PLAYPAUSE = 164
171
+ KEY_PREVIOUSSONG = 165
172
+ KEY_STOPCD = 166
173
+ KEY_RECORD = 167
174
+ KEY_REWIND = 168
175
+ KEY_PHONE = 169 # Media Select Telephone
176
+ KEY_ISO = 170
177
+ KEY_CONFIG = 171 # AL Consumer Control Configuration
178
+ KEY_HOMEPAGE = 172 # AC Home
179
+ KEY_REFRESH = 173 # AC Refresh
180
+ KEY_EXIT = 174 # AC Exit
181
+ KEY_MOVE = 175
182
+ KEY_EDIT = 176
183
+ KEY_SCROLLUP = 177
184
+ KEY_SCROLLDOWN = 178
185
+ KEY_KPLEFTPAREN = 179
186
+ KEY_KPRIGHTPAREN = 180
187
+ KEY_NEW = 181 # AC New
188
+ KEY_REDO = 182 # AC Redo/Repeat
189
+ KEY_F13 = 183
190
+ KEY_F14 = 184
191
+ KEY_F15 = 185
192
+ KEY_F16 = 186
193
+ KEY_F17 = 187
194
+ KEY_F18 = 188
195
+ KEY_F19 = 189
196
+ KEY_F20 = 190
197
+ KEY_F21 = 191
198
+ KEY_F22 = 192
199
+ KEY_F23 = 193
200
+ KEY_F24 = 194
201
+ KEY_PLAYCD = 200
202
+ KEY_PAUSECD = 201
203
+ KEY_PROG3 = 202
204
+ KEY_PROG4 = 203
205
+ KEY_DASHBOARD = 204 # AL Dashboard
206
+ KEY_SUSPEND = 205
207
+ KEY_CLOSE = 206 # AC Close
208
+ KEY_PLAY = 207
209
+ KEY_FASTFORWARD = 208
210
+ KEY_BASSBOOST = 209
211
+ KEY_PRINT = 210 # AC Print
212
+ KEY_HP = 211
213
+ KEY_CAMERA = 212
214
+ KEY_SOUND = 213
215
+ KEY_QUESTION = 214
216
+ KEY_EMAIL = 215
217
+ KEY_CHAT = 216
218
+ KEY_SEARCH = 217
219
+ KEY_CONNECT = 218
220
+ KEY_FINANCE = 219 # AL Checkbook/Finance
221
+ KEY_SPORT = 220
222
+ KEY_SHOP = 221
223
+ KEY_ALTERASE = 222
224
+ KEY_CANCEL = 223 # AC Cancel
225
+ KEY_BRIGHTNESSDOWN = 224
226
+ KEY_BRIGHTNESSUP = 225
227
+ KEY_MEDIA = 226
228
+ KEY_SWITCHVIDEOMODE = 227 # Cycle between available video
229
+ KEY_KBDILLUMTOGGLE = 228
230
+ KEY_KBDILLUMDOWN = 229
231
+ KEY_KBDILLUMUP = 230
232
+ KEY_SEND = 231 # AC Send
233
+ KEY_REPLY = 232 # AC Reply
234
+ KEY_FORWARDMAIL = 233 # AC Forward Msg
235
+ KEY_SAVE = 234 # AC Save
236
+ KEY_DOCUMENTS = 235
237
+ KEY_BATTERY = 236
238
+ KEY_BLUETOOTH = 237
239
+ KEY_WLAN = 238
240
+ KEY_UWB = 239
241
+ KEY_UNKNOWN = 240
242
+ KEY_VIDEO_NEXT = 241 # drive next video source
243
+ KEY_VIDEO_PREV = 242 # drive previous video source
244
+ KEY_BRIGHTNESS_CYCLE = 243 # brightness up, after max is min
245
+ KEY_BRIGHTNESS_AUTO = 244 # Set Auto Brightness: manual
246
+ KEY_BRIGHTNESS_ZERO = KEY_BRIGHTNESS_AUTO
247
+ KEY_DISPLAY_OFF = 245 # display device to off state
248
+ KEY_WWAN = 246 # Wireless WAN (LTE, UMTS, GSM, etc.)
249
+ KEY_WIMAX = KEY_WWAN
250
+ KEY_RFKILL = 247 # Key that controls all radios
251
+ KEY_MICMUTE = 248 # Mute / unmute the microphone
252
+ KEY_OK = 0x160
253
+ KEY_SELECT = 0x161
254
+ KEY_GOTO = 0x162
255
+ KEY_CLEAR = 0x163
256
+ KEY_POWER2 = 0x164
257
+ KEY_OPTION = 0x165
258
+ KEY_INFO = 0x166 # AL OEM Features/Tips/Tutorial
259
+ KEY_TIME = 0x167
260
+ KEY_VENDOR = 0x168
261
+ KEY_ARCHIVE = 0x169
262
+ KEY_PROGRAM = 0x16a # Media Select Program Guide
263
+ KEY_CHANNEL = 0x16b
264
+ KEY_FAVORITES = 0x16c
265
+ KEY_EPG = 0x16d
266
+ KEY_PVR = 0x16e # Media Select Home
267
+ KEY_MHP = 0x16f
268
+ KEY_LANGUAGE = 0x170
269
+ KEY_TITLE = 0x171
270
+ KEY_SUBTITLE = 0x172
271
+ KEY_ANGLE = 0x173
272
+ KEY_FULL_SCREEN = 0x174 # AC View Toggle
273
+ KEY_ZOOM = KEY_FULL_SCREEN
274
+ KEY_MODE = 0x175
275
+ KEY_KEYBOARD = 0x176
276
+ KEY_ASPECT_RATIO = 0x177 # HUTRR37: Aspect
277
+ KEY_SCREEN = KEY_ASPECT_RATIO
278
+ KEY_PC = 0x178 # Media Select Computer
279
+ KEY_TV = 0x179 # Media Select TV
280
+ KEY_TV2 = 0x17a # Media Select Cable
281
+ KEY_VCR = 0x17b # Media Select VCR
282
+ KEY_VCR2 = 0x17c # VCR Plus
283
+ KEY_SAT = 0x17d # Media Select Satellite
284
+ KEY_SAT2 = 0x17e
285
+ KEY_CD = 0x17f # Media Select CD
286
+ KEY_TAPE = 0x180 # Media Select Tape
287
+ KEY_RADIO = 0x181
288
+ KEY_TUNER = 0x182 # Media Select Tuner
289
+ KEY_PLAYER = 0x183
290
+ KEY_TEXT = 0x184
291
+ KEY_DVD = 0x185 # Media Select DVD
292
+ KEY_AUX = 0x186
293
+ KEY_MP3 = 0x187
294
+ KEY_AUDIO = 0x188 # AL Audio Browser
295
+ KEY_VIDEO = 0x189 # AL Movie Browser
296
+ KEY_DIRECTORY = 0x18a
297
+ KEY_LIST = 0x18b
298
+ KEY_MEMO = 0x18c # Media Select Messages
299
+ KEY_CALENDAR = 0x18d
300
+ KEY_RED = 0x18e
301
+ KEY_GREEN = 0x18f
302
+ KEY_YELLOW = 0x190
303
+ KEY_BLUE = 0x191
304
+ KEY_CHANNELUP = 0x192 # Channel Increment
305
+ KEY_CHANNELDOWN = 0x193 # Channel Decrement
306
+ KEY_FIRST = 0x194
307
+ KEY_LAST = 0x195 # Recall Last
308
+ KEY_AB = 0x196
309
+ KEY_NEXT = 0x197
310
+ KEY_RESTART = 0x198
311
+ KEY_SLOW = 0x199
312
+ KEY_SHUFFLE = 0x19a
313
+ KEY_BREAK = 0x19b
314
+ KEY_PREVIOUS = 0x19c
315
+ KEY_DIGITS = 0x19d
316
+ KEY_TEEN = 0x19e
317
+ KEY_TWEN = 0x19f
318
+ KEY_VIDEOPHONE = 0x1a0 # Media Select Video Phone
319
+ KEY_GAMES = 0x1a1 # Media Select Games
320
+ KEY_ZOOMIN = 0x1a2 # AC Zoom In
321
+ KEY_ZOOMOUT = 0x1a3 # AC Zoom Out
322
+ KEY_ZOOMRESET = 0x1a4 # AC Zoom
323
+ KEY_WORDPROCESSOR = 0x1a5 # AL Word Processor
324
+ KEY_EDITOR = 0x1a6 # AL Text Editor
325
+ KEY_SPREADSHEET = 0x1a7 # AL Spreadsheet
326
+ KEY_GRAPHICSEDITOR = 0x1a8 # AL Graphics Editor
327
+ KEY_PRESENTATION = 0x1a9 # AL Presentation App
328
+ KEY_DATABASE = 0x1aa # AL Database App
329
+ KEY_NEWS = 0x1ab # AL Newsreader
330
+ KEY_VOICEMAIL = 0x1ac # AL Voicemail
331
+ KEY_ADDRESSBOOK = 0x1ad # AL Contacts/Address Book
332
+ KEY_MESSENGER = 0x1ae # AL Instant Messaging
333
+ KEY_DISPLAYTOGGLE = 0x1af # Turn display (LCD) on and off
334
+ KEY_BRIGHTNESS_TOGGLE = KEY_DISPLAYTOGGLE
335
+ KEY_SPELLCHECK = 0x1b0 # AL Spell Check
336
+ KEY_LOGOFF = 0x1b1 # AL Logoff
337
+ KEY_DOLLAR = 0x1b2
338
+ KEY_EURO = 0x1b3
339
+ KEY_FRAMEBACK = 0x1b4 # Consumer - transport controls
340
+ KEY_FRAMEFORWARD = 0x1b5
341
+ KEY_CONTEXT_MENU = 0x1b6 # GenDesc - system context menu
342
+ KEY_MEDIA_REPEAT = 0x1b7 # Consumer - transport control
343
+ KEY_10CHANNELSUP = 0x1b8 # 10 channels up (10+)
344
+ KEY_10CHANNELSDOWN = 0x1b9 # 10 channels down (10-)
345
+ KEY_IMAGES = 0x1ba # AL Image Browser
346
+ KEY_NOTIFICATION_CENTER = 0x1bc # Show/hide the notification center
347
+ KEY_PICKUP_PHONE = 0x1bd # Answer incoming call
348
+ KEY_HANGUP_PHONE = 0x1be # Decline incoming call
349
+ KEY_DEL_EOL = 0x1c0
350
+ KEY_DEL_EOS = 0x1c1
351
+ KEY_INS_LINE = 0x1c2
352
+ KEY_DEL_LINE = 0x1c3
353
+ KEY_FN = 0x1d0
354
+ KEY_FN_ESC = 0x1d1
355
+ KEY_FN_F1 = 0x1d2
356
+ KEY_FN_F2 = 0x1d3
357
+ KEY_FN_F3 = 0x1d4
358
+ KEY_FN_F4 = 0x1d5
359
+ KEY_FN_F5 = 0x1d6
360
+ KEY_FN_F6 = 0x1d7
361
+ KEY_FN_F7 = 0x1d8
362
+ KEY_FN_F8 = 0x1d9
363
+ KEY_FN_F9 = 0x1da
364
+ KEY_FN_F10 = 0x1db
365
+ KEY_FN_F11 = 0x1dc
366
+ KEY_FN_F12 = 0x1dd
367
+ KEY_FN_1 = 0x1de
368
+ KEY_FN_2 = 0x1df
369
+ KEY_FN_D = 0x1e0
370
+ KEY_FN_E = 0x1e1
371
+ KEY_FN_F = 0x1e2
372
+ KEY_FN_S = 0x1e3
373
+ KEY_FN_B = 0x1e4
374
+ KEY_FN_RIGHT_SHIFT = 0x1e5
375
+ KEY_BRL_DOT1 = 0x1f1
376
+ KEY_BRL_DOT2 = 0x1f2
377
+ KEY_BRL_DOT3 = 0x1f3
378
+ KEY_BRL_DOT4 = 0x1f4
379
+ KEY_BRL_DOT5 = 0x1f5
380
+ KEY_BRL_DOT6 = 0x1f6
381
+ KEY_BRL_DOT7 = 0x1f7
382
+ KEY_BRL_DOT8 = 0x1f8
383
+ KEY_BRL_DOT9 = 0x1f9
384
+ KEY_BRL_DOT10 = 0x1fa
385
+ KEY_NUMERIC_0 = 0x200 # used by phones, remote controls,
386
+ KEY_NUMERIC_1 = 0x201 # and other keypads
387
+ KEY_NUMERIC_2 = 0x202
388
+ KEY_NUMERIC_3 = 0x203
389
+ KEY_NUMERIC_4 = 0x204
390
+ KEY_NUMERIC_5 = 0x205
391
+ KEY_NUMERIC_6 = 0x206
392
+ KEY_NUMERIC_7 = 0x207
393
+ KEY_NUMERIC_8 = 0x208
394
+ KEY_NUMERIC_9 = 0x209
395
+ KEY_NUMERIC_STAR = 0x20a
396
+ KEY_NUMERIC_POUND = 0x20b
397
+ KEY_NUMERIC_A = 0x20c # Phone key A - HUT Telephony 0xb9
398
+ KEY_NUMERIC_B = 0x20d
399
+ KEY_NUMERIC_C = 0x20e
400
+ KEY_NUMERIC_D = 0x20f
401
+ KEY_CAMERA_FOCUS = 0x210
402
+ KEY_WPS_BUTTON = 0x211 # WiFi Protected Setup key
403
+ KEY_TOUCHPAD_TOGGLE = 0x212 # Request switch touchpad on or off
404
+ KEY_TOUCHPAD_ON = 0x213
405
+ KEY_TOUCHPAD_OFF = 0x214
406
+ KEY_CAMERA_ZOOMIN = 0x215
407
+ KEY_CAMERA_ZOOMOUT = 0x216
408
+ KEY_CAMERA_UP = 0x217
409
+ KEY_CAMERA_DOWN = 0x218
410
+ KEY_CAMERA_LEFT = 0x219
411
+ KEY_CAMERA_RIGHT = 0x21a
412
+ KEY_ATTENDANT_ON = 0x21b
413
+ KEY_ATTENDANT_OFF = 0x21c
414
+ KEY_ATTENDANT_TOGGLE = 0x21d # Attendant call on or off
415
+ KEY_LIGHTS_TOGGLE = 0x21e # Reading light on or off
416
+ KEY_ALS_TOGGLE = 0x230 # Ambient light sensor
417
+ KEY_ROTATE_LOCK_TOGGLE = 0x231 # Display rotation lock
418
+ KEY_BUTTONCONFIG = 0x240 # AL Button Configuration
419
+ KEY_TASKMANAGER = 0x241 # AL Task/Project Manager
420
+ KEY_JOURNAL = 0x242 # AL Log/Journal/Timecard
421
+ KEY_CONTROLPANEL = 0x243 # AL Control Panel
422
+ KEY_APPSELECT = 0x244 # AL Select Task/Application
423
+ KEY_SCREENSAVER = 0x245 # AL Screen Saver
424
+ KEY_VOICECOMMAND = 0x246 # Listening Voice Command
425
+ KEY_ASSISTANT = 0x247 # AL Context-aware desktop assistant
426
+ KEY_KBD_LAYOUT_NEXT = 0x248 # AC Next Keyboard Layout Select
427
+ KEY_EMOJI_PICKER = 0x249 # Show/hide emoji picker (HUTRR101)
428
+ KEY_BRIGHTNESS_MIN = 0x250 # Set Brightness to Minimum
429
+ KEY_BRIGHTNESS_MAX = 0x251 # Set Brightness to Maximum
430
+ KEY_KBDINPUTASSIST_PREV = 0x260
431
+ KEY_KBDINPUTASSIST_NEXT = 0x261
432
+ KEY_KBDINPUTASSIST_PREVGROUP = 0x262
433
+ KEY_KBDINPUTASSIST_NEXTGROUP = 0x263
434
+ KEY_KBDINPUTASSIST_ACCEPT = 0x264
435
+ KEY_KBDINPUTASSIST_CANCEL = 0x265
436
+ KEY_RIGHT_UP = 0x266
437
+ KEY_RIGHT_DOWN = 0x267
438
+ KEY_LEFT_UP = 0x268
439
+ KEY_LEFT_DOWN = 0x269
440
+ KEY_ROOT_MENU = 0x26a # Show Device's Root Menu
441
+ KEY_MEDIA_TOP_MENU = 0x26b
442
+ KEY_NUMERIC_11 = 0x26c
443
+ KEY_NUMERIC_12 = 0x26d
444
+ KEY_AUDIO_DESC = 0x26e
445
+ KEY_3D_MODE = 0x26f
446
+ KEY_NEXT_FAVORITE = 0x270
447
+ KEY_STOP_RECORD = 0x271
448
+ KEY_PAUSE_RECORD = 0x272
449
+ KEY_VOD = 0x273 # Video on Demand
450
+ KEY_UNMUTE = 0x274
451
+ KEY_FASTREVERSE = 0x275
452
+ KEY_SLOWREVERSE = 0x276
453
+ KEY_DATA = 0x277
454
+ KEY_ONSCREEN_KEYBOARD = 0x278
455
+ KEY_PRIVACY_SCREEN_TOGGLE = 0x279
456
+ KEY_SELECTIVE_SCREENSHOT = 0x27a
457
+ KEY_MACRO1 = 0x290
458
+ KEY_MACRO2 = 0x291
459
+ KEY_MACRO3 = 0x292
460
+ KEY_MACRO4 = 0x293
461
+ KEY_MACRO5 = 0x294
462
+ KEY_MACRO6 = 0x295
463
+ KEY_MACRO7 = 0x296
464
+ KEY_MACRO8 = 0x297
465
+ KEY_MACRO9 = 0x298
466
+ KEY_MACRO10 = 0x299
467
+ KEY_MACRO11 = 0x29a
468
+ KEY_MACRO12 = 0x29b
469
+ KEY_MACRO13 = 0x29c
470
+ KEY_MACRO14 = 0x29d
471
+ KEY_MACRO15 = 0x29e
472
+ KEY_MACRO16 = 0x29f
473
+ KEY_MACRO17 = 0x2a0
474
+ KEY_MACRO18 = 0x2a1
475
+ KEY_MACRO19 = 0x2a2
476
+ KEY_MACRO20 = 0x2a3
477
+ KEY_MACRO21 = 0x2a4
478
+ KEY_MACRO22 = 0x2a5
479
+ KEY_MACRO23 = 0x2a6
480
+ KEY_MACRO24 = 0x2a7
481
+ KEY_MACRO25 = 0x2a8
482
+ KEY_MACRO26 = 0x2a9
483
+ KEY_MACRO27 = 0x2aa
484
+ KEY_MACRO28 = 0x2ab
485
+ KEY_MACRO29 = 0x2ac
486
+ KEY_MACRO30 = 0x2ad
487
+ KEY_MACRO_RECORD_START = 0x2b0
488
+ KEY_MACRO_RECORD_STOP = 0x2b1
489
+ KEY_MACRO_PRESET_CYCLE = 0x2b2
490
+ KEY_MACRO_PRESET1 = 0x2b3
491
+ KEY_MACRO_PRESET2 = 0x2b4
492
+ KEY_MACRO_PRESET3 = 0x2b5
493
+ KEY_KBD_LCD_MENU1 = 0x2b8
494
+ KEY_KBD_LCD_MENU2 = 0x2b9
495
+ KEY_KBD_LCD_MENU3 = 0x2ba
496
+ KEY_KBD_LCD_MENU4 = 0x2bb
497
+ KEY_KBD_LCD_MENU5 = 0x2bc
498
+ KEY_MIN_INTERESTING = KEY_MUTE
499
+ KEY_MAX = 0x2ff
500
+ KEY_CNT = (KEY_MAX+1)
501
+ end
502
+
503
+ CODE_KEY = {}
504
+ KeyCode.constants.each do |k|
505
+ next unless k =~ /\AKEY_/
506
+ code = KeyCode.const_get(k)
507
+ CODE_KEY[code] = k unless CODE_KEY[code]
508
+ end
509
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Rkremap
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,95 @@
1
+ require 'ffi'
2
+
3
+ module FFI
4
+ typedef :pointer, :Display
5
+ typedef :ulong, :XID
6
+ typedef :XID, :Window
7
+ typedef :int, :Status
8
+ typedef :ulong, :Atom
9
+ end
10
+
11
+ class Rkremap
12
+ # アプリの name, class, title を取得する
13
+ class WinAttr
14
+ def initialize
15
+ @app_win = {}
16
+ @display = X11.XOpenDisplay(nil) or raise "Cannot open display: #{ENV['DISPLAY']}"
17
+ end
18
+
19
+ def focus_win
20
+ win = FFI::MemoryPointer.new(:Window)
21
+ revert_to = FFI::MemoryPointer.new(:int)
22
+ X11.XGetInputFocus(@display, win, revert_to)
23
+ win.read(:Window)
24
+ end
25
+
26
+ # @return [Array<window, name, class>]
27
+ def app_win(window)
28
+ return @app_win[window] if @app_win[window]
29
+ class_hint = XClassHint.new
30
+ root = FFI::MemoryPointer.new(:Window)
31
+ parent = FFI::MemoryPointer.new(:Window)
32
+ children = FFI::MemoryPointer.new(:pointer)
33
+ nchildren = FFI::MemoryPointer.new(:int)
34
+ win = window
35
+ while win > 0
36
+ X11.XGetClassHint(@display, win, class_hint)
37
+ X11.XQueryTree(@display, win, root, parent, children, nchildren)
38
+ X11.XFree(children.read_pointer)
39
+ break unless class_hint[:name].null? && class_hint[:class].null?
40
+ win = parent.read(:Window)
41
+ end
42
+ unless class_hint[:name].null?
43
+ win_name = class_hint[:name].read_string
44
+ X11.XFree(class_hint[:name])
45
+ end
46
+ unless class_hint[:class].null?
47
+ win_class = class_hint[:class].read_string
48
+ X11.XFree(class_hint[:class])
49
+ end
50
+ @app_win[window] = [win, win_name, win_class]
51
+ end
52
+
53
+ # @return [String]
54
+ def app_title(window)
55
+ win = app_win(window)[0]
56
+ prop = XTextProperty.new
57
+ text_list = FFI::MemoryPointer.new(:pointer)
58
+ count = FFI::MemoryPointer.new(:int)
59
+ X11.XGetWMName(@display, win, prop)
60
+ X11.Xutf8TextPropertyToTextList(@display, prop, text_list, count)
61
+ title = text_list.read(:pointer).read(:string)&.force_encoding('utf-8')
62
+ X11.XFreeStringList(text_list.read(:pointer))
63
+ title
64
+ end
65
+ end
66
+
67
+ class XClassHint < FFI::Struct
68
+ layout(
69
+ :name, :pointer,
70
+ :class, :pointer,
71
+ )
72
+ end
73
+
74
+ class XTextProperty < FFI::Struct
75
+ layout(
76
+ :value, :pointer,
77
+ :encoding, :Atom,
78
+ :format, :int,
79
+ :nitems, :ulong,
80
+ )
81
+ end
82
+
83
+ module X11
84
+ extend FFI::Library
85
+ ffi_lib 'X11'
86
+ attach_function :XOpenDisplay, [:string], :Display
87
+ attach_function :XGetInputFocus, [:Display, :pointer, :pointer], :int
88
+ attach_function :XGetClassHint, [:Display, :Window, XClassHint.by_ref], :int
89
+ attach_function :XQueryTree, [:Display, :Window, :pointer, :pointer, :pointer, :pointer], :Status
90
+ attach_function :XGetWMName, [:Display, :Window, XTextProperty.by_ref], :void
91
+ attach_function :Xutf8TextPropertyToTextList, [:Display, XTextProperty.by_ref, :pointer, :pointer], :int
92
+ attach_function :XFree, [:pointer], :int
93
+ attach_function :XFreeStringList, [:pointer], :void
94
+ end
95
+ end
data/lib/rkremap.rb ADDED
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rkremap/version"
4
+ require_relative 'rkremap/keycode'
5
+ require_relative 'rkremap/winattr'
6
+
7
+ class Rkremap
8
+ include KeyCode
9
+
10
+ # ioctl constants
11
+ EVIOCGRAB = 1074021776
12
+ UI_SET_EVBIT = 1074025828
13
+ UI_SET_KEYBIT = 1074025829
14
+ UI_DEV_SETUP = 1079792899
15
+ UI_DEV_CREATE = 21761
16
+ EV_KEY = 1
17
+ EV_SYN = 0
18
+ SYN_REPORT = 0
19
+
20
+ EVENT_TYPE_VALUE = {
21
+ release: 0,
22
+ press: 1,
23
+ repeat: 2,
24
+ }
25
+
26
+ attr_accessor :grab
27
+ attr_accessor :modifiers
28
+ attr_accessor :x11
29
+
30
+ # @param device [String]
31
+ def initialize(device)
32
+ @input = File.open(device)
33
+ @uinput = File.open('/dev/uinput', 'w')
34
+ setup_uinput
35
+ @grab = false
36
+ @x11 = false
37
+ @modifiers = [
38
+ KEY_LEFTCTRL, KEY_RIGHTCTRL,
39
+ KEY_LEFTSHIFT, KEY_RIGHTSHIFT,
40
+ KEY_LEFTALT, KEY_RIGHTALT,
41
+ KEY_LEFTMETA, KEY_RIGHTMETA,
42
+ ]
43
+ @events = []
44
+ @mutex = Mutex.new
45
+ end
46
+
47
+ # @param code [Integer]
48
+ # @param type [Symbol] :press / :release / :repeat
49
+ def match(code: nil, type: nil, &block)
50
+ @events.push [{code: code, type: type}, block]
51
+ end
52
+
53
+ def start(&block)
54
+ @mod_state = @modifiers.map{|m| [m, false]}.to_h
55
+ @winattr = WinAttr.new if @x11
56
+ @input.ioctl(EVIOCGRAB, 1) if @grab
57
+ while true
58
+ @keys = []
59
+ raw = @input.sysread(24)
60
+ sec, usec, type, code, value = raw.unpack('Q!Q!SSl')
61
+ next if type != EV_KEY
62
+ time = Time.at(sec, usec)
63
+ event = Event.new(time, code, value, @winattr)
64
+ @events.each do |cond, b|
65
+ synchronize{ b.call event } if event.match?(**cond)
66
+ break if event.skipped
67
+ end
68
+ next if event.skipped
69
+ synchronize{ proc_event(code, value, event) }
70
+ @keys.each do |c, mod, app|
71
+ synchronize{ block.call(c, mod, app) } if block
72
+ end
73
+ end
74
+ end
75
+
76
+ def synchronize(&block)
77
+ @mutex.synchronize(&block)
78
+ end
79
+
80
+ # @param code [Integer]
81
+ # @param type [Symbol] :press / :release / :repeat
82
+ # @param ev [Rkmap::Event]
83
+ def event(code: nil, type: nil, event: nil)
84
+ value = EVENT_TYPE_VALUE[type] or raise "invalid type: #{type.inspect}"
85
+ proc_event(code, value, event)
86
+ end
87
+
88
+ # @param code [Integer]
89
+ # @param mod [Hash] {MOD_KEY => true, ...}
90
+ def key(code, mod={})
91
+ mod_diff(mod).each do |mcode, state|
92
+ write_event(mcode, state ? 1 : 0)
93
+ end
94
+ write_event(code, 1)
95
+ write_event(code, 0)
96
+ mod_diff(mod).each do |mcode, _|
97
+ write_event(mcode, @mod_state[mcode] ? 1 : 0)
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def setup_uinput
104
+ @uinput.ioctl(UI_SET_EVBIT, EV_KEY)
105
+ KEY_CNT.times{|k| @uinput.ioctl(UI_SET_KEYBIT, k)}
106
+ bustype = 0x03 # BUS_USB
107
+ vendor = 0x1234 # てきとー
108
+ product = 0x5678 # てきとー
109
+ version = 1 # てきとー
110
+ name = 'rkremap'
111
+ ff_efects_max = 0
112
+ setup = [bustype, vendor, product, version, name, ff_efects_max].pack("SSSSZ80L") # struct uinput_setup
113
+ @uinput.ioctl(UI_DEV_SETUP, setup)
114
+ @uinput.ioctl(UI_DEV_CREATE)
115
+ end
116
+
117
+ def proc_event(code, value, event)
118
+ if @mod_state.include?(code)
119
+ @mod_state[code] = value != 0
120
+ write_event(code, value)
121
+ elsif value != 0
122
+ @keys.push [code, @mod_state.dup, App.new(event&.win, @winattr)]
123
+ end
124
+ end
125
+
126
+ def mod_diff(mod)
127
+ mod.select{|k, v| @mod_state[k] != v}
128
+ end
129
+
130
+ # @param code [Integer]
131
+ # @param value [Integer] 0:release / 1:press / 2:repeat
132
+ def write_event(code, value)
133
+ @uinput.syswrite(['', EV_KEY, code, value].pack('a16SSl'))
134
+ @uinput.syswrite(['', EV_SYN, SYN_REPORT, 0].pack('a16SSl'))
135
+ end
136
+
137
+ class Event
138
+ attr_reader :time
139
+ attr_reader :code
140
+ attr_reader :type
141
+ attr_reader :win
142
+ attr_reader :skipped
143
+
144
+ def initialize(time, code, value, winattr)
145
+ @time = time
146
+ @code = code
147
+ @value = value
148
+ @type = value == 1 ? :press : value == 0 ? :release : :repeat
149
+ @win = winattr&.focus_win
150
+ @skipped = false
151
+ end
152
+
153
+ def match?(code: nil, type: nil)
154
+ (code.nil? || Array(code).include?(@code)) && (type.nil? || Array(type).include?(@type))
155
+ end
156
+
157
+ def skip
158
+ @skipped = true
159
+ end
160
+ end
161
+
162
+ class App
163
+ def initialize(win, winattr)
164
+ @win = win
165
+ @winattr = winattr
166
+ end
167
+
168
+ def class_name
169
+ @winattr.app_win(@win)[2] if @winattr && @win
170
+ end
171
+
172
+ def title
173
+ @winattr.app_title(@win) if @winattr && @win
174
+ end
175
+ end
176
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rkremap
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - TOMITA Masahiro
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-01-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ description: key remapper
28
+ email:
29
+ - tommy@tmtm.org
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README.md
35
+ - example/henkan-alt.rb
36
+ - example/keylogger.rb
37
+ - example/mac-shortcut.rb
38
+ - example/tmtms.rb
39
+ - lib/rkremap.rb
40
+ - lib/rkremap/keycode.rb
41
+ - lib/rkremap/version.rb
42
+ - lib/rkremap/winattr.rb
43
+ homepage: https://github.com/tmtm/rkremap
44
+ licenses:
45
+ - MIT
46
+ metadata:
47
+ homepage_uri: https://github.com/tmtm/rkremap
48
+ source_code_uri: https://github.com/tmtm/rkremap
49
+ documentation_uri: https://www.rubydoc.info/gems/rkremap
50
+ rubygems_mfa_required: 'true'
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 2.6.0
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubygems_version: 3.3.3
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: key remapper
70
+ test_files: []