rkremap 0.3.0.1 → 0.5.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 +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
|