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.
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, (KEY_MAX-1)/8+1)
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
- self.class.io2evdev[@io] = self
71
- @io
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
- buf = ' '
93
- io.ioctl(EVIOCGNAME, buf)
94
- return false if buf =~ /\Arkremap\0*\z/
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
- capable?(KeyCode::KEY_0) && capable?(KeyCode::KEY_9) &&
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 [Array<Time, Integer, Integer, Integer>]
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
- return time, type, code, value
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 type [Integer] EV_KEY/ EV_SYN
148
- # @param code [Integer] キーコード / SYN_REPORT
149
- # @param value [Integer] 0:release / 1:press / 2:repeat
150
- def write_event(type, code, value)
151
- @dev.syswrite(['', type, code, value].pack('a16SSl'))
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Rkremap
4
- VERSION = "0.3.0.1"
4
+ VERSION = "0.5.0"
5
5
  end
@@ -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
- title = text_list.ptr.ptr.to_s.force_encoding('utf-8')
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