rkremap 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +76 -4
- data/example/henkan-super.rb +52 -0
- data/example/keylogger.rb +2 -2
- data/example/mac-shortcut.rb +48 -11
- data/example/tmtms.rb +98 -100
- data/lib/rkremap/evdev.rb +49 -61
- 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 +108 -57
- metadata +25 -8
- data/example/henkan-alt.rb +0 -93
data/lib/rkremap/evdev.rb
CHANGED
@@ -1,33 +1,13 @@
|
|
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
|
7
|
+
EVIOCGNAME = 2164278534 # EVIOCGNAME(256)
|
11
8
|
|
12
|
-
@
|
13
|
-
|
14
|
-
@io2evdev
|
15
|
-
end
|
16
|
-
|
17
|
-
# @return [Array<Evdev>]
|
18
|
-
def self.devices
|
19
|
-
Dir.children("/dev/input").map do |dev|
|
20
|
-
dev =~ /\Aevent\d+\z/ ? Evdev.new("/dev/input/#{dev}") : nil
|
21
|
-
end.compact
|
22
|
-
end
|
23
|
-
|
24
|
-
# @param inputs [Array<Evdev>]
|
25
|
-
# @return [Evdev]
|
26
|
-
def self.select(inputs)
|
27
|
-
h = inputs.map{|ev| [ev.io, ev]}.to_h
|
28
|
-
r, = IO.select(inputs.map(&:io), nil, nil, nil)
|
29
|
-
@io2evdev[r[0]]
|
30
|
-
end
|
9
|
+
# @return [String]
|
10
|
+
attr_reader :path
|
31
11
|
|
32
12
|
# @param path [String]
|
33
13
|
def initialize(path)
|
@@ -35,14 +15,25 @@ class Rkremap
|
|
35
15
|
@io = nil
|
36
16
|
@capa = nil
|
37
17
|
@uinput = nil
|
18
|
+
@grab = false
|
19
|
+
end
|
20
|
+
|
21
|
+
def eql?(other)
|
22
|
+
other.is_a?(Evdev) && self.path == other.path
|
38
23
|
end
|
39
24
|
|
40
25
|
# @return [IO]
|
41
26
|
def io
|
42
27
|
return @io if @io
|
43
28
|
@io = File.open(@path)
|
44
|
-
|
45
|
-
|
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+$/, '')
|
46
37
|
end
|
47
38
|
|
48
39
|
def close
|
@@ -62,15 +53,37 @@ class Rkremap
|
|
62
53
|
|
63
54
|
# @return [Boolean]
|
64
55
|
def keyboard?
|
65
|
-
|
56
|
+
return @is_keyboard unless @is_keyboard.nil?
|
57
|
+
@is_keyboard = false
|
58
|
+
return false if name =~ /\Arkremap\0*\z/
|
59
|
+
|
60
|
+
buf = ''
|
61
|
+
io.ioctl(EVIOCGBIT_ANY, buf)
|
62
|
+
return false if buf.unpack1('C')[EV_KEY] == 0
|
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 = ''
|
66
74
|
io.ioctl(EVIOCGBIT_ANY, buf)
|
67
75
|
return false if buf.unpack1('C')[EV_KEY] == 0
|
68
|
-
|
69
|
-
capable?(KeyCode::KEY_A) && capable?(KeyCode::KEY_Z) && capable?(KeyCode::KEY_SPACE)
|
76
|
+
@is_mouse = capable?(KeyCode::BTN_MOUSE)
|
70
77
|
end
|
71
78
|
|
72
79
|
def grab
|
73
80
|
io.ioctl(EVIOCGRAB, 1)
|
81
|
+
@grab = true
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [Boolean]
|
85
|
+
def grab?
|
86
|
+
@grab
|
74
87
|
end
|
75
88
|
|
76
89
|
# struct input_event {
|
@@ -86,39 +99,14 @@ class Rkremap
|
|
86
99
|
time = Time.at(sec, usec)
|
87
100
|
return time, type, code, value
|
88
101
|
end
|
89
|
-
end
|
90
|
-
|
91
|
-
# https://www.kernel.org/doc/html/v4.12/input/uinput.html
|
92
|
-
class Uinput
|
93
|
-
UI_SET_EVBIT = 1074025828
|
94
|
-
UI_SET_KEYBIT = 1074025829
|
95
|
-
UI_DEV_SETUP = 1079792899
|
96
|
-
UI_DEV_CREATE = 21761
|
97
|
-
|
98
|
-
def initialize
|
99
|
-
@dev = File.open('/dev/uinput', 'w')
|
100
|
-
setup
|
101
|
-
end
|
102
|
-
|
103
|
-
def setup
|
104
|
-
@dev.ioctl(UI_SET_EVBIT, EV_KEY)
|
105
|
-
KeyCode::KEY_CNT.times{|k| @dev.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
|
-
@dev.ioctl(UI_DEV_SETUP, setup)
|
114
|
-
@dev.ioctl(UI_DEV_CREATE)
|
115
|
-
end
|
116
102
|
|
117
|
-
# @param
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
122
110
|
end
|
123
111
|
end
|
124
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,
|
@@ -17,12 +28,22 @@ class Rkremap
|
|
17
28
|
attr_accessor :grab
|
18
29
|
attr_accessor :modifiers
|
19
30
|
attr_accessor :x11
|
31
|
+
attr_accessor :auto_detect
|
32
|
+
attr_accessor :exclude
|
33
|
+
attr_accessor :mouse
|
20
34
|
|
21
35
|
# @param devices [Array<String>, String]
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
36
|
+
# @param exclude [Regexp]
|
37
|
+
# @param mouse [Boolean]
|
38
|
+
def initialize(devices=[], exclude: nil, mouse: false)
|
39
|
+
if devices.empty?
|
40
|
+
@auto_detect = true
|
41
|
+
else
|
42
|
+
devices = Array(devices)
|
43
|
+
end
|
44
|
+
@devices = devices
|
45
|
+
@exclude = exclude
|
46
|
+
@mouse = mouse
|
26
47
|
@uinput = Uinput.new
|
27
48
|
@grab = false
|
28
49
|
@x11 = false
|
@@ -32,34 +53,45 @@ class Rkremap
|
|
32
53
|
KEY_LEFTALT, KEY_RIGHTALT,
|
33
54
|
KEY_LEFTMETA, KEY_RIGHTMETA,
|
34
55
|
]
|
35
|
-
@
|
56
|
+
@event_procs = []
|
36
57
|
@mutex = Mutex.new
|
37
58
|
end
|
38
59
|
|
60
|
+
# @param device [Symbol, Regexp]
|
39
61
|
# @param code [Integer]
|
40
62
|
# @param type [Symbol] :press / :release / :repeat
|
41
|
-
|
42
|
-
|
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]
|
43
66
|
end
|
44
67
|
|
45
68
|
def start(&block)
|
46
|
-
@
|
47
|
-
@
|
48
|
-
@
|
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
|
72
|
+
@mod_state = @modifiers.map.to_h{|m| [m, false]}
|
73
|
+
winattr = WinAttr.new if @x11
|
49
74
|
while true
|
50
75
|
@keys = []
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
82
|
+
end
|
83
|
+
next
|
58
84
|
end
|
59
|
-
|
60
|
-
|
61
|
-
@
|
62
|
-
synchronize{
|
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
|
63
95
|
end
|
64
96
|
end
|
65
97
|
end
|
@@ -70,20 +102,27 @@ class Rkremap
|
|
70
102
|
|
71
103
|
# @param code [Integer]
|
72
104
|
# @param type [Symbol] :press / :release / :repeat
|
73
|
-
|
74
|
-
def event(code: nil, type: nil, event: nil)
|
105
|
+
def event(code: nil, type: nil)
|
75
106
|
value = EVENT_TYPE_VALUE[type] or raise "invalid type: #{type.inspect}"
|
76
|
-
|
107
|
+
update_modifiers(code, value)
|
108
|
+
write_event(code, value)
|
77
109
|
end
|
78
110
|
|
79
111
|
# @param code [Integer]
|
80
112
|
# @param mod [Hash] {MOD_KEY => true, ...}
|
81
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)
|
82
122
|
mod_diff(mod).each do |mcode, state|
|
83
123
|
write_event(mcode, state ? 1 : 0)
|
84
124
|
end
|
85
|
-
|
86
|
-
write_event(code, 0)
|
125
|
+
block.call
|
87
126
|
mod_diff(mod).each do |mcode, _|
|
88
127
|
write_event(mcode, @mod_state[mcode] ? 1 : 0)
|
89
128
|
end
|
@@ -91,12 +130,23 @@ class Rkremap
|
|
91
130
|
|
92
131
|
private
|
93
132
|
|
94
|
-
def
|
133
|
+
def update_modifiers(code, value)
|
95
134
|
if @mod_state.include?(code)
|
96
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
|
97
147
|
write_event(code, value)
|
98
|
-
|
99
|
-
@keys.push [code, @mod_state.dup,
|
148
|
+
else
|
149
|
+
@keys.push [code, @mod_state.dup, app]
|
100
150
|
end
|
101
151
|
end
|
102
152
|
|
@@ -111,43 +161,44 @@ class Rkremap
|
|
111
161
|
@uinput.write_event(EV_SYN, SYN_REPORT, 0)
|
112
162
|
end
|
113
163
|
|
114
|
-
class Event
|
115
|
-
attr_reader :time
|
116
|
-
attr_reader :code
|
117
|
-
attr_reader :type
|
118
|
-
attr_reader :win
|
119
|
-
attr_reader :skipped
|
120
|
-
|
121
|
-
def initialize(time, code, value, winattr)
|
122
|
-
@time = time
|
123
|
-
@code = code
|
124
|
-
@value = value
|
125
|
-
@type = value == 1 ? :press : value == 0 ? :release : :repeat
|
126
|
-
@win = winattr&.focus_win
|
127
|
-
@skipped = false
|
128
|
-
end
|
129
|
-
|
130
|
-
def match?(code: nil, type: nil)
|
131
|
-
(code.nil? || Array(code).include?(@code)) && (type.nil? || Array(type).include?(@type))
|
132
|
-
end
|
133
|
-
|
134
|
-
def skip
|
135
|
-
@skipped = true
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
164
|
class App
|
140
|
-
|
141
|
-
|
165
|
+
# @param winattr [Rkremap::WinAttr]
|
166
|
+
def initialize(winattr)
|
142
167
|
@winattr = winattr
|
168
|
+
@win = winattr.focus_win
|
143
169
|
end
|
144
170
|
|
171
|
+
# @return [String, nil]
|
145
172
|
def class_name
|
146
173
|
@winattr.app_win(@win)[2] if @winattr && @win
|
147
174
|
end
|
148
175
|
|
176
|
+
# @return [String, nil]
|
149
177
|
def title
|
150
178
|
@winattr.app_title(@win) if @winattr && @win
|
151
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
|
152
203
|
end
|
153
204
|
end
|