rkremap 0.3.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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,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"
4
+ VERSION = "0.4.1"
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