rkremap 0.2.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 +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
|