rkremap 0.3.0.1 → 0.5.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 +107 -5
- data/example/henkan-super.rb +9 -41
- data/example/keylogger.rb +2 -2
- data/example/mac-shortcut.rb +49 -12
- data/example/tmtms.rb +74 -68
- data/lib/rkremap/evdev.rb +47 -85
- data/lib/rkremap/evdev_list.rb +90 -0
- data/lib/rkremap/event.rb +48 -0
- data/lib/rkremap/keycode.rb +81 -1
- data/lib/rkremap/remap.rb +105 -0
- 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 +113 -85
- metadata +21 -3
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,11 @@ class Rkremap
|
|
57
15
|
@io = nil
|
58
16
|
@capa = nil
|
59
17
|
@uinput = nil
|
18
|
+
@grab = false
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
"#<Rkremap::Evdev: #{name} (#{path})>"
|
60
23
|
end
|
61
24
|
|
62
25
|
def eql?(other)
|
@@ -67,13 +30,18 @@ class Rkremap
|
|
67
30
|
def io
|
68
31
|
return @io if @io
|
69
32
|
@io = File.open(@path)
|
70
|
-
|
71
|
-
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [String]
|
36
|
+
def name
|
37
|
+
return @name if @name
|
38
|
+
buf = ''
|
39
|
+
io.ioctl(EVIOCGNAME, buf)
|
40
|
+
@name = buf.sub(/\0+$/, '')
|
72
41
|
end
|
73
42
|
|
74
43
|
def close
|
75
44
|
@io&.close
|
76
|
-
self.class.remove_device(self)
|
77
45
|
end
|
78
46
|
|
79
47
|
# @param key [Integer]
|
@@ -89,18 +57,37 @@ class Rkremap
|
|
89
57
|
|
90
58
|
# @return [Boolean]
|
91
59
|
def keyboard?
|
92
|
-
|
93
|
-
|
94
|
-
return false if
|
60
|
+
return @is_keyboard unless @is_keyboard.nil?
|
61
|
+
@is_keyboard = false
|
62
|
+
return false if name =~ /\Arkremap\0*\z/
|
63
|
+
|
64
|
+
buf = ''
|
65
|
+
io.ioctl(EVIOCGBIT_ANY, buf)
|
66
|
+
return false if buf.unpack1('C')[EV_KEY] == 0
|
67
|
+
@is_keyboard = capable?(KeyCode::KEY_0) && capable?(KeyCode::KEY_9) &&
|
68
|
+
capable?(KeyCode::KEY_A) && capable?(KeyCode::KEY_Z) && capable?(KeyCode::KEY_SPACE)
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Boolean]
|
72
|
+
def mouse?
|
73
|
+
return @is_mouse unless @is_mouse.nil?
|
74
|
+
@is_mouse = false
|
75
|
+
return false if name =~ /\Arkremap\0*\z/
|
95
76
|
|
77
|
+
buf = ''
|
96
78
|
io.ioctl(EVIOCGBIT_ANY, buf)
|
97
79
|
return false if buf.unpack1('C')[EV_KEY] == 0
|
98
|
-
|
99
|
-
capable?(KeyCode::KEY_A) && capable?(KeyCode::KEY_Z) && capable?(KeyCode::KEY_SPACE)
|
80
|
+
@is_mouse = capable?(KeyCode::BTN_MOUSE)
|
100
81
|
end
|
101
82
|
|
102
83
|
def grab
|
103
84
|
io.ioctl(EVIOCGRAB, 1)
|
85
|
+
@grab = true
|
86
|
+
end
|
87
|
+
|
88
|
+
# @return [Boolean]
|
89
|
+
def grab?
|
90
|
+
@grab
|
104
91
|
end
|
105
92
|
|
106
93
|
# struct input_event {
|
@@ -109,46 +96,21 @@ class Rkremap
|
|
109
96
|
# unsigned short code;
|
110
97
|
# unsigned int value;
|
111
98
|
# };
|
112
|
-
# @return [
|
99
|
+
# @return [Rkremap::Event]
|
113
100
|
def read_event
|
114
101
|
raw = io.sysread(24) # sizeof(struct input_event)
|
115
102
|
sec, usec, type, code, value = raw.unpack('Q!Q!SSl')
|
116
103
|
time = Time.at(sec, usec)
|
117
|
-
|
118
|
-
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)
|
104
|
+
Event.new(self, time, type, code, value)
|
145
105
|
end
|
146
106
|
|
147
|
-
# @param
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
107
|
+
# @param pattern [Symbol, Regexp]
|
108
|
+
def match?(pattern)
|
109
|
+
return true if pattern == true
|
110
|
+
return true if pattern == :keyboard && keyboard?
|
111
|
+
return true if pattern == :mouse && mouse?
|
112
|
+
return true if pattern.is_a?(Regexp) && pattern =~ name
|
113
|
+
return false
|
152
114
|
end
|
153
115
|
end
|
154
116
|
end
|
@@ -0,0 +1,90 @@
|
|
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
|
+
stat = nil
|
48
|
+
if @exclude && ev.name =~ @exclude
|
49
|
+
stat = 'excluded'
|
50
|
+
elsif ev.keyboard? || (@detect_mouse && ev.mouse?)
|
51
|
+
ev.grab if ev.match?(@grab)
|
52
|
+
@io2evdev[ev.io] = ev
|
53
|
+
@device_list[path] = ev
|
54
|
+
stat = ev.grab? ? 'grabbed' : 'not grabbed'
|
55
|
+
else
|
56
|
+
stat = 'ignored'
|
57
|
+
end
|
58
|
+
puts "detect: #{ev.name} (#{ev.path}) #{stat}" if $VERBOSE
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @param evdev [path]
|
63
|
+
def remove_device(path)
|
64
|
+
evdev = @device_list[path]
|
65
|
+
puts "disconnect: #{evdev.name} (#{evdev.path})" if $VERBOSE
|
66
|
+
@io2evdev.delete evdev.io
|
67
|
+
evdev.close
|
68
|
+
@device_list.delete path
|
69
|
+
end
|
70
|
+
|
71
|
+
# @param timeout [Numeric]
|
72
|
+
# @return [Evdev]
|
73
|
+
def select(timeout)
|
74
|
+
r, = IO.select(@device_list.values.map(&:io), nil, nil, timeout)
|
75
|
+
r && @io2evdev[r[0]]
|
76
|
+
end
|
77
|
+
|
78
|
+
def read_event
|
79
|
+
while true
|
80
|
+
evdev = select(3)
|
81
|
+
next unless evdev
|
82
|
+
begin
|
83
|
+
return evdev.read_event
|
84
|
+
rescue Errno::ENODEV
|
85
|
+
remove_device(evdev.path)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class Rkremap
|
2
|
+
class Event
|
3
|
+
attr_accessor :app
|
4
|
+
attr_reader :device
|
5
|
+
attr_reader :time
|
6
|
+
attr_reader :ev_type
|
7
|
+
attr_accessor :code
|
8
|
+
attr_accessor :value
|
9
|
+
|
10
|
+
# @param evdev [Rkremap::Evdev]
|
11
|
+
# @param time [Time]
|
12
|
+
# @param type [Integer]
|
13
|
+
# @param code [Integer]
|
14
|
+
# @param value [Integer]
|
15
|
+
def initialize(evdev, time, type, code, value)
|
16
|
+
@device = evdev
|
17
|
+
@time = time
|
18
|
+
@ev_type = type
|
19
|
+
@code = code
|
20
|
+
@value = value
|
21
|
+
@app = nil
|
22
|
+
@skipped = false
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
"#<Rkremap::Event: #{code}(#{CODE_KEY[code]}) #{value} #{device.inspect}>"
|
27
|
+
end
|
28
|
+
|
29
|
+
def type
|
30
|
+
@type ||= value == 1 ? :press : value == 0 ? :release : :repeat
|
31
|
+
end
|
32
|
+
|
33
|
+
def match?(device:, code:, type:, app:)
|
34
|
+
(device.nil? || self.device.match?(device)) &&
|
35
|
+
(code.nil? || Array(code).include?(self.code)) &&
|
36
|
+
(type.nil? || Array(type).include?(self.type)) &&
|
37
|
+
(app.nil? || @app.nil? || @app.match?(app))
|
38
|
+
end
|
39
|
+
|
40
|
+
def skip
|
41
|
+
@skipped = true
|
42
|
+
end
|
43
|
+
|
44
|
+
def skipped?
|
45
|
+
@skipped
|
46
|
+
end
|
47
|
+
end
|
48
|
+
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,105 @@
|
|
1
|
+
class Rkremap
|
2
|
+
class Remap
|
3
|
+
# @param map [Hash] from => to
|
4
|
+
# @param hold [Numeric] seconds
|
5
|
+
def initialize(rk, map:, hold:)
|
6
|
+
@rk = rk
|
7
|
+
@pressed = {}
|
8
|
+
|
9
|
+
if hold
|
10
|
+
setup_holding_event(map, hold)
|
11
|
+
else
|
12
|
+
setup_event(map)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param map [Hash] {form => to}
|
17
|
+
def setup_event(map)
|
18
|
+
keycodes = map.keys.flatten
|
19
|
+
|
20
|
+
@rk.match(code: keycodes, type: :press) do |event, current_mod|
|
21
|
+
map.each do |from, to|
|
22
|
+
if event_match?(from, event, current_mod)
|
23
|
+
make_press_event(to, event)
|
24
|
+
break
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
@rk.match(code: keycodes, type: :release) do |event|
|
30
|
+
if @pressed[event.code]
|
31
|
+
@rk.event(code: @pressed[event.code], type: :release)
|
32
|
+
@pressed[event.code] = nil
|
33
|
+
event.skip
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param map [Hash] {form => to}
|
39
|
+
# @param hold [Numeric] seconds
|
40
|
+
def setup_holding_event(map, hold)
|
41
|
+
keycodes = map.keys.flatten
|
42
|
+
timer = Tmtms::Timer.new
|
43
|
+
jobs = map.transform_values{nil}
|
44
|
+
|
45
|
+
@rk.match(code: keycodes, type: :press) do |event|
|
46
|
+
map.each do |from, to|
|
47
|
+
if from == event.code
|
48
|
+
jobs[event.code] = timer.set(hold) do
|
49
|
+
@rk.synchronize{ make_press_event(to) }
|
50
|
+
end
|
51
|
+
event.skip
|
52
|
+
break
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
@rk.match(code: keycodes, type: :repeat) do |event|
|
58
|
+
event.skip
|
59
|
+
end
|
60
|
+
|
61
|
+
@rk.match(code: keycodes, type: :release) do |event|
|
62
|
+
if jobs[event.code]&.cancel
|
63
|
+
@rk.key(event.code)
|
64
|
+
elsif map[event.code]
|
65
|
+
@rk.event(code: map[event.code], type: :release)
|
66
|
+
end
|
67
|
+
event.skip
|
68
|
+
end
|
69
|
+
|
70
|
+
@rk.match(type: :press) do
|
71
|
+
jobs.each do |code, job|
|
72
|
+
make_press_event(map[code]) if job&.cancel
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# @param keys [Integer, Array<Integer>] KEY_A, [KEY_LEFTCTRL, KEY_A], [:SHIFT, KEY_A], ...
|
78
|
+
# @param event [Rkremap::Event]
|
79
|
+
# @param mod [Hash] current modifiers status
|
80
|
+
def event_match?(keys, event, mod)
|
81
|
+
if keys == event.code
|
82
|
+
return true
|
83
|
+
end
|
84
|
+
keys = Array(keys)
|
85
|
+
return false unless keys.include? event.code
|
86
|
+
(keys - [event.code]).all?{mod[_1]}
|
87
|
+
end
|
88
|
+
|
89
|
+
# @param to [Integer, Array<Integer>]
|
90
|
+
# @param event [Rkremap::Event, nil]
|
91
|
+
def make_press_event(to, event=nil)
|
92
|
+
Array(to).each do |key|
|
93
|
+
@pressed[event.code] = key if event
|
94
|
+
if @rk.modifiers.include? key
|
95
|
+
@rk.event(code: key, type: :press)
|
96
|
+
event&.skip
|
97
|
+
elsif event
|
98
|
+
event.code = key
|
99
|
+
else
|
100
|
+
@rk.key(code: key, type: :press)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
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
|