fusuma-plugin-remap 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,33 +2,31 @@ require "revdev"
2
2
  require "msgpack"
3
3
  require "set"
4
4
  require_relative "layer_manager"
5
-
6
- require_relative "ruinput_device_patched"
5
+ require_relative "uinput_keyboard"
7
6
 
8
7
  module Fusuma
9
8
  module Plugin
10
9
  module Remap
11
- class Remapper
10
+ class KeyboardRemapper
12
11
  include Revdev
12
+
13
+ VIRTUAL_KEYBOARD_NAME = "fusuma_virtual_keyboard"
14
+
13
15
  # @param layer_manager [Fusuma::Plugin::Remap::LayerManager]
14
- # @param keyboard_writer [IO]
15
- # @param source_keyboards [Array<Revdev::Device>]
16
- # @param internal_touchpad [Revdev::Device]
17
- def initialize(layer_manager:, keyboard_writer:, source_keyboards:, internal_touchpad:)
16
+ # @param fusuma_writer [IO]
17
+ # @param config [Hash]
18
+ def initialize(layer_manager:, fusuma_writer:, config: {})
18
19
  @layer_manager = layer_manager # request to change layer
19
- @keyboard_writer = keyboard_writer # write event to original keyboard
20
- @source_keyboards = source_keyboards # original keyboard
21
- @internal_touchpad = internal_touchpad # internal touchpad
22
- @uinput = RuinputDevicePatched.new "/dev/uinput"
23
- @pressed_virtual_keys = Set.new
20
+ @fusuma_writer = fusuma_writer # write event to original keyboard
21
+ @config = config
24
22
  end
25
23
 
26
24
  def run
27
25
  create_virtual_keyboard
28
- set_trap
29
- grab_keyboards
26
+ @source_keyboards = reload_keyboards
30
27
 
31
28
  old_ie = nil
29
+ layer = nil
32
30
  next_mapping = nil
33
31
  current_mapping = {}
34
32
 
@@ -37,14 +35,12 @@ module Fusuma
37
35
  io = ios.first.first
38
36
 
39
37
  if io == @layer_manager.reader
40
- begin
41
- @layer_manager.receive_layer
42
- rescue EOFError
43
- @destroy.call
38
+ layer = @layer_manager.receive_layer # update @current_layer
39
+ if layer.nil?
40
+ next
44
41
  end
45
42
 
46
- MultiLogger.debug "Remapper#run: layer changed to #{@layer_manager.current_layer}"
47
- next_mapping = @layer_manager.find_mapping
43
+ next_mapping = @layer_manager.find_mapping(layer)
48
44
  next
49
45
  end
50
46
 
@@ -59,21 +55,18 @@ module Fusuma
59
55
  input_key = find_key_from_code(input_event.code)
60
56
 
61
57
  if input_event.type == EV_KEY
62
- # FIXME: exit when RIGHTCTRL-LEFTCTRL is pressed
63
- if (old_ie&.code == KEY_RIGHTCTRL && old_ie.value != 0) && (input_event.code == KEY_LEFTCTRL && input_event.value != 0)
64
- @destroy.call
65
- end
58
+ @emergency_stop.call(old_ie, input_event)
66
59
 
67
60
  old_ie = input_event
68
61
  if input_event.value != 2 # repeat
69
- packed = {key: input_key, status: input_event.value}.to_msgpack
70
- @keyboard_writer.puts(packed)
62
+ data = {key: input_key, status: input_event.value, layer: layer}
63
+ @fusuma_writer.write(data.to_msgpack)
71
64
  end
72
65
  end
73
66
 
74
67
  remapped = current_mapping.fetch(input_key.to_sym, nil)
75
68
  if remapped.nil?
76
- @uinput.write_input_event(input_event)
69
+ uinput_keyboard.write_input_event(input_event)
77
70
  next
78
71
  end
79
72
 
@@ -91,12 +84,42 @@ module Fusuma
91
84
  # this is because the command will be executed by fusuma process
92
85
  next if remapped_event.code.nil?
93
86
 
94
- @uinput.write_input_event(remapped_event)
87
+ uinput_keyboard.write_input_event(remapped_event)
88
+ rescue Errno::ENODEV => e # device is removed
89
+ MultiLogger.error "Device is removed: #{e.message}"
90
+ @source_keyboards = reload_keyboards
95
91
  end
92
+ rescue EOFError => e # device is closed
93
+ MultiLogger.error "Device is closed: #{e.message}"
94
+ ensure
95
+ @destroy.call
96
96
  end
97
97
 
98
98
  private
99
99
 
100
+ def reload_keyboards
101
+ source_keyboards = KeyboardSelector.new(@config[:keyboard_name_patterns]).select
102
+
103
+ MultiLogger.info("Reload keyboards: #{source_keyboards.map(&:device_name)}")
104
+
105
+ set_trap(source_keyboards)
106
+ # TODO: Extract to a configuration file or make it optional
107
+ # it should stop other remappers
108
+ set_emergency_ungrab_keybinds("RIGHTCTRL", "LEFTCTRL")
109
+ grab_keyboards(source_keyboards)
110
+ rescue => e
111
+ MultiLogger.error "Failed to reload keyboards: #{e.message}"
112
+ MultiLogger.error e.backtrace.join("\n")
113
+ end
114
+
115
+ def uinput_keyboard
116
+ @uinput_keyboard ||= UinputKeyboard.new("/dev/uinput")
117
+ end
118
+
119
+ def pressed_virtual_keys
120
+ @pressed_virtual_keys ||= Set.new
121
+ end
122
+
100
123
  # record virtual keyboard event
101
124
  # @param [String] remapped_value remapped key name
102
125
  # @param [Integer] event_value event value
@@ -105,9 +128,9 @@ module Fusuma
105
128
  def record_virtual_keyboard_event?(remapped_value, event_value)
106
129
  case event_value
107
130
  when 0
108
- @pressed_virtual_keys.delete?(remapped_value)
131
+ pressed_virtual_keys.delete?(remapped_value)
109
132
  when 1
110
- @pressed_virtual_keys.add?(remapped_value)
133
+ pressed_virtual_keys.add?(remapped_value)
111
134
  true # Always return true because the remapped key may be the same
112
135
  else
113
136
  # 2 is repeat
@@ -116,26 +139,38 @@ module Fusuma
116
139
  end
117
140
 
118
141
  def virtual_keyboard_all_key_released?
119
- @pressed_virtual_keys.empty?
142
+ pressed_virtual_keys.empty?
120
143
  end
121
144
 
122
145
  def create_virtual_keyboard
123
- @uinput.create "fusuma_virtual_keyboard",
146
+ touchpad_name_patterns = @config[:touchpad_name_patterns]
147
+ internal_touchpad = TouchpadSelector.new(touchpad_name_patterns).select.first
148
+
149
+ if internal_touchpad.nil?
150
+ MultiLogger.error("No touchpad found: #{touchpad_name_patterns}")
151
+ exit
152
+ end
153
+
154
+ MultiLogger.info "Create virtual keyboard: #{VIRTUAL_KEYBOARD_NAME}"
155
+
156
+ uinput_keyboard.create VIRTUAL_KEYBOARD_NAME,
124
157
  Revdev::InputId.new(
125
- # recognized as an internal keyboard on libinput,
126
- # touchpad is disabled when typing
127
- # see: (https://wayland.freedesktop.org/libinput/doc/latest/palm-detection.html#disable-while-typing)
158
+ # disable while typing is enabled when
159
+ # - Both the keyboard and touchpad are BUS_I8042
160
+ # - The touchpad and keyboard have the same vendor/product
161
+ # ref: (https://wayland.freedesktop.org/libinput/doc/latest/palm-detection.html#disable-while-typing)
162
+ #
128
163
  {
129
164
  bustype: Revdev::BUS_I8042,
130
- vendor: @internal_touchpad.device_id.vendor,
131
- product: @internal_touchpad.device_id.product,
132
- version: @internal_touchpad.device_id.version
165
+ vendor: internal_touchpad.device_id.vendor,
166
+ product: internal_touchpad.device_id.product,
167
+ version: internal_touchpad.device_id.version
133
168
  }
134
169
  )
135
170
  end
136
171
 
137
- def grab_keyboards
138
- @source_keyboards.each do |keyboard|
172
+ def grab_keyboards(keyboards)
173
+ keyboards.each do |keyboard|
139
174
  wait_release_all_keys(keyboard)
140
175
  begin
141
176
  keyboard.grab
@@ -146,16 +181,18 @@ module Fusuma
146
181
  end
147
182
  end
148
183
 
149
- def set_trap
184
+ # @param [Array<Revdev::EventDevice>] keyboards
185
+ def set_trap(keyboards)
150
186
  @destroy = lambda do
151
- @source_keyboards.each do |kbd|
187
+ keyboards.each do |kbd|
152
188
  kbd.ungrab
153
189
  rescue Errno::EINVAL
190
+ rescue Errno::ENODEV
154
191
  # already ungrabbed
155
192
  end
156
193
 
157
194
  begin
158
- @uinput.destroy
195
+ uinput_keyboard.destroy
159
196
  rescue IOError
160
197
  # already destroyed
161
198
  end
@@ -167,6 +204,20 @@ module Fusuma
167
204
  Signal.trap(:TERM) { @destroy.call }
168
205
  end
169
206
 
207
+ # Emergency stop keybind for virtual keyboard
208
+ def set_emergency_ungrab_keybinds(first_key, second_key)
209
+ first_keycode = find_code_from_key(first_key)
210
+ second_keycode = find_code_from_key(second_key)
211
+ MultiLogger.info "Emergency ungrab keybind: #{first_key} + #{second_key}"
212
+
213
+ @emergency_stop = lambda do |prev, current|
214
+ if (prev&.code == first_keycode && prev.value != 0) && (current.code == second_keycode && current.value != 0)
215
+ MultiLogger.info "Emergency ungrab keybind: #{first_key} + #{second_key}"
216
+ @destroy.call
217
+ end
218
+ end
219
+ end
220
+
170
221
  # Find remappable key from mapping and return remapped key code
171
222
  # If not found, return original key code
172
223
  # If the key is found but its value is not valid, return nil
@@ -240,6 +291,52 @@ module Fusuma
240
291
  end
241
292
  end
242
293
  end
294
+
295
+ # Devices to detect key presses and releases
296
+ class KeyboardSelector
297
+ def initialize(names = ["keyboard", "Keyboard", "KEYBOARD"])
298
+ @names = names
299
+ end
300
+
301
+ # Select devices that match the name
302
+ # If no device is found, it will wait for 3 seconds and try again
303
+ # @return [Array<Revdev::EventDevice>]
304
+ def select
305
+ loop do
306
+ Fusuma::Device.reset # reset cache to get the latest device information
307
+ devices = Fusuma::Device.all.select { |d| Array(@names).any? { |name| d.name =~ /#{name}/ } }
308
+ if devices.empty?
309
+ wait_for_device
310
+
311
+ next
312
+ end
313
+
314
+ return devices.map { |d| Revdev::EventDevice.new("/dev/input/#{d.id}") }
315
+ end
316
+ end
317
+
318
+ def wait_for_device
319
+ sleep 3
320
+ end
321
+ end
322
+
323
+ class TouchpadSelector
324
+ def initialize(names = nil)
325
+ @names = names
326
+ end
327
+
328
+ # @return [Array<Revdev::EventDevice>]
329
+ def select
330
+ devices = if @names
331
+ Fusuma::Device.all.select { |d| Array(@names).any? { |name| d.name =~ /#{name}/ } }
332
+ else
333
+ # available returns only touchpad devices
334
+ Fusuma::Device.available
335
+ end
336
+
337
+ devices.map { |d| Revdev::EventDevice.new("/dev/input/#{d.id}") }
338
+ end
339
+ end
243
340
  end
244
341
  end
245
342
  end
@@ -8,28 +8,33 @@ module Fusuma
8
8
  class LayerManager
9
9
  require "singleton"
10
10
  include Singleton
11
- attr_reader :reader, :writer, :current_layer
11
+ attr_reader :reader, :writer, :current_layer, :layers
12
12
 
13
13
  def initialize
14
14
  @layers = {}
15
15
  @reader, @writer = IO.pipe
16
16
  @current_layer = {} # preserve order
17
+ @last_layer = nil
18
+ @last_remove = nil
17
19
  end
18
20
 
19
21
  # @param [Hash] layer
20
22
  # @param [Boolean] remove
21
23
  def send_layer(layer:, remove: false)
22
- # puts "send_layer: #{layer} remove: #{remove}"
23
- return if @last_layer == layer && @last_remove == remove
24
+ return if (@last_layer == layer) && (@last_remove == remove)
24
25
 
25
26
  @last_layer = layer
26
27
  @last_remove = remove
27
- @writer.puts({layer: layer, remove: remove}.to_msgpack)
28
+ @writer.write({layer: layer, remove: remove}.to_msgpack)
28
29
  end
29
30
 
30
- # Read layer from pipe and return remap layer
31
+ # Read layer from pipe and update @current_layer
32
+ # @return [Hash] current layer
31
33
  # @example
32
- # @return [Hash]
34
+ # receive_layer
35
+ # # => { thumbsense: true }
36
+ # receive_layer
37
+ # # => { thumbsense: true, application: "Google-chrome" }
33
38
  def receive_layer
34
39
  @layer_unpacker ||= MessagePack::Unpacker.new(@reader)
35
40
 
@@ -41,28 +46,28 @@ module Fusuma
41
46
  layer = data[:layer] # e.g { thumbsense: true }
42
47
  remove = data[:remove] # e.g true
43
48
 
49
+ # update @current_layer
44
50
  if remove
45
51
  @current_layer.delete_if { |k, _v| layer.key?(k) }
46
52
  else
47
53
  # If duplicate keys exist, order of keys is preserved
48
54
  @current_layer.merge!(layer)
49
55
  end
56
+ @current_layer
50
57
  end
51
58
 
52
59
  # Find remap layer from config
53
60
  # @param [Hash] layer
54
- # @return [Hash]
55
- def find_mapping(layer = @current_layer)
61
+ # @return [Hash] remap layer
62
+ def find_mapping(layer)
56
63
  @layers[layer] ||= begin
57
64
  result = nil
58
- _ = Fusuma::Config::Searcher.find_context(layer) {
65
+ _ = Fusuma::Config::Searcher.find_context(layer) do
59
66
  result = Fusuma::Config.search(Fusuma::Config::Index.new(:remap))
60
67
  next unless result
61
68
 
62
- result = result.deep_transform_keys do |key|
63
- key.upcase.to_sym
64
- end
65
- }
69
+ result = result.deep_transform_keys { |key| key.upcase.to_sym }
70
+ end
66
71
 
67
72
  result || {}
68
73
  end
@@ -0,0 +1,168 @@
1
+ require "revdev"
2
+ require "msgpack"
3
+ require "set"
4
+
5
+ require_relative "uinput_touchpad"
6
+
7
+ module Fusuma
8
+ module Plugin
9
+ module Remap
10
+ class TouchpadRemapper
11
+ include Revdev
12
+
13
+ VIRTUAL_TOUCHPAD_NAME = "fusuma_virtual_touchpad"
14
+
15
+ # @param fusuma_writer [IO]
16
+ # @param source_touchpad [Revdev::Device]
17
+ def initialize(fusuma_writer:, source_touchpad:)
18
+ @source_touchpad = source_touchpad # original touchpad
19
+ @fusuma_writer = fusuma_writer # write event to fusuma_input
20
+ @palm_detector ||= PalmDetection.new(source_touchpad)
21
+ end
22
+
23
+ # TODO: grab touchpad events and remap them
24
+ # send remapped events to virtual touchpad or virtual mouse
25
+ def run
26
+ create_virtual_touchpad
27
+
28
+ touch_state = {}
29
+ mt_slot = 0
30
+ finger_state = nil
31
+ loop do
32
+ IO.select([@source_touchpad.file]) # , @layer_manager.reader])
33
+
34
+ ## example of input_event
35
+ # Event: time 1698456258.380027, type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value 43679
36
+ # Event: time 1698456258.380027, type 3 (EV_ABS), code 53 (ABS_MT_POSITION_X), value 648
37
+ # Event: time 1698456258.380027, type 3 (EV_ABS), code 54 (ABS_MT_POSITION_Y), value 209
38
+ # Event: time 1698456258.380027, type 1 (EV_KEY), code 330 (BTN_TOUCH), value 1
39
+ # Event: time 1698456258.380027, type 1 (EV_KEY), code 325 (BTN_TOOL_FINGER), value 1
40
+ # Event: time 1698456258.380027, type 3 (EV_ABS), code 0 (ABS_X), value 648
41
+ # Event: time 1698456258.380027, type 3 (EV_ABS), code 1 (ABS_Y), value 209
42
+ # Event: time 1698456258.380027, type 4 (EV_MSC), code 5 (MSC_TIMESTAMP), value 0
43
+ # Event: time 1698456258.380027, -------------- SYN_REPORT ------------
44
+ # Event: time 1698456258.382693, type 3 (EV_ABS), code 47 (ABS_MT_SLOT), value 1
45
+ # Event: time 1698456258.382693, type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value 43680
46
+ # Event: time 1698456258.382693, type 3 (EV_ABS), code 53 (ABS_MT_POSITION_X), value 400
47
+ # Event: time 1698456258.382693, type 3 (EV_ABS), code 54 (ABS_MT_POSITION_Y), value 252
48
+ # Event: time 1698456258.382693, type 1 (EV_KEY), code 325 (BTN_TOOL_FINGER), value 0
49
+ # Event: time 1698456258.382693, type 1 (EV_KEY), code 333 (BTN_TOOL_DOUBLETAP), value 1
50
+ # Event: time 1698456258.382693, type 4 (EV_MSC), code 5 (MSC_TIMESTAMP), value 7100
51
+ # Event: time 1698456258.382693, -------------- SYN_REPORT ------------
52
+ input_event = @source_touchpad.read_input_event
53
+
54
+ touch_state[mt_slot] ||= {MT_TRACKING_ID: nil, X: nil, Y: nil, valid_touch_point: false}
55
+ syn_report = nil
56
+
57
+ case input_event.type
58
+ when Revdev::EV_ABS
59
+ case input_event.code
60
+ when Revdev::ABS_MT_SLOT
61
+ mt_slot = input_event.value
62
+ touch_state[mt_slot] ||= {}
63
+ when Revdev::ABS_MT_TRACKING_ID
64
+ touch_state[mt_slot][:MT_TRACKING_ID] = input_event.value
65
+ if input_event.value == -1
66
+ touch_state[mt_slot] = {}
67
+ end
68
+ when Revdev::ABS_MT_POSITION_X
69
+ touch_state[mt_slot][:X] = input_event.value
70
+ when Revdev::ABS_MT_POSITION_Y
71
+ touch_state[mt_slot][:Y] = input_event.value
72
+ when Revdev::ABS_X, Revdev::ABS_Y
73
+ # ignore
74
+ when Revdev::ABS_MT_PRESSURE
75
+ # ignore
76
+ when Revdev::ABS_MT_TOOL_TYPE
77
+ # ignore
78
+ else
79
+ raise "unhandled event"
80
+ end
81
+ when Revdev::EV_KEY
82
+ case input_event.code
83
+ when Revdev::BTN_TOUCH
84
+ # ignore
85
+ when Revdev::BTN_TOOL_FINGER
86
+ finger_state = (input_event.value == 1) ? 1 : 0
87
+ when Revdev::BTN_TOOL_DOUBLETAP
88
+ finger_state = (input_event.value == 1) ? 2 : 1
89
+ when Revdev::BTN_TOOL_TRIPLETAP
90
+ finger_state = (input_event.value == 1) ? 3 : 2
91
+ when Revdev::BTN_TOOL_QUADTAP
92
+ finger_state = (input_event.value == 1) ? 4 : 3
93
+ when 0x148 # define BTN_TOOL_QUINTTAP 0x148 /* Five fingers on trackpad */
94
+ finger_state = (input_event.value == 1) ? 5 : 4
95
+ end
96
+ when Revdev::EV_MSC
97
+ case input_event.code
98
+ when 0x05 # define MSC_TIMESTAMP 0x05
99
+ # ignore
100
+ # current_timestamp = input_event.value
101
+ end
102
+ when Revdev::EV_SYN
103
+ case input_event.code
104
+ when Revdev::SYN_REPORT
105
+ syn_report = input_event.value
106
+ when Revdev::SYN_DROPPED
107
+ MultiLogger.error "Dropped: #{input_event.value}"
108
+ else
109
+ raise "unhandled event", "#{input_event.hr_type}, #{input_event.hr_code}, #{input_event.value}"
110
+ end
111
+ else
112
+ raise "unhandled event", "#{input_event.hr_type}, #{input_event.hr_code}, #{input_event.value}"
113
+ end
114
+
115
+ # TODO:
116
+ # Remember the most recent valid touch position and exclude it if it is close to that position
117
+ # For example, when dragging, it is possible to touch around the edge of the touchpad again after reaching the edge of the touchpad, so in that case, you do not want to execute palm detection
118
+ if touch_state[mt_slot][:valid_touch_point] != true
119
+ touch_state[mt_slot][:valid_touch_point] = @palm_detector.palm?(touch_state[mt_slot])
120
+ end
121
+
122
+ if syn_report
123
+ # TODO: define format as fusuma_input
124
+ data = {finger: finger_state, touch_state: touch_state}
125
+ @fusuma_writer.write(data.to_msgpack)
126
+ end
127
+ end
128
+ end
129
+
130
+ private
131
+
132
+ def uinput
133
+ @uinput ||= UinputTouchpad.new "/dev/uinput"
134
+ end
135
+
136
+ def create_virtual_touchpad
137
+ MultiLogger.info "Create virtual keyboard: #{VIRTUAL_TOUCHPAD_NAME}"
138
+
139
+ uinput.create_from_device(name: VIRTUAL_TOUCHPAD_NAME, device: @source_touchpad)
140
+ end
141
+
142
+ # Detect palm touch
143
+ class PalmDetection
144
+ def initialize(touchpad)
145
+ @max_x = touchpad.absinfo_for_axis(Revdev::ABS_MT_POSITION_X)[:absmax]
146
+ @max_y = touchpad.absinfo_for_axis(Revdev::ABS_MT_POSITION_Y)[:absmax]
147
+ end
148
+
149
+ def palm?(touch_state)
150
+ return false unless touch_state[:X] && touch_state[:Y]
151
+
152
+ if 0.8 * @max_y < touch_state[:Y]
153
+ true
154
+ else
155
+ !(
156
+ # Disable 20% of the touch area on the left, right
157
+ (touch_state[:X] < 0.2 * @max_x || touch_state[:X] > 0.8 * @max_x) ||
158
+ # Disable 10% of the touch area on the top edge
159
+ (touch_state[:Y] < 0.1 * @max_y && (touch_state[:X] < 0.2 * @max_x || touch_state[:X] > 0.8 * @max_x)
160
+ )
161
+ )
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,95 @@
1
+ require "ruinput"
2
+
3
+ class UinputKeyboard < Ruinput::UinputDevice
4
+ include Ruinput
5
+
6
+ # create virtual event divece
7
+ # _name_ :: device name
8
+ # _id_ :: InputId ("struct input_id" on input.h)
9
+ def create name = DEFAULT_DEVICE_NAME, id = DEFAULT_INPUT_ID
10
+ if !name.is_a? String
11
+ raise ArgumentError, "1st arg expect String"
12
+ elsif !id.is_a? Revdev::InputId
13
+ raise ArgumentError, "2nd arg expect Revdev::InputId"
14
+ end
15
+
16
+ uud = Ruinput::UinputUserDev.new({
17
+ name: name,
18
+ id: id,
19
+ ff_effects_max: 0,
20
+ absmax: [],
21
+ absmin: [],
22
+ absfuzz: [],
23
+ absflat: []
24
+ })
25
+
26
+ @file.syswrite uud.to_byte_string
27
+
28
+ set_all_events
29
+
30
+ @file.ioctl UI_DEV_CREATE, nil
31
+ @is_created = true
32
+ end
33
+
34
+ def set_all_events
35
+ raise "invalid method call: this uinput is already created" if @is_created
36
+
37
+ mouse_btns = [
38
+ Revdev::BTN_0,
39
+ Revdev::BTN_MISC,
40
+ Revdev::BTN_1,
41
+ Revdev::BTN_2,
42
+ Revdev::BTN_3,
43
+ Revdev::BTN_4,
44
+ Revdev::BTN_5,
45
+ Revdev::BTN_6,
46
+ Revdev::BTN_7,
47
+ Revdev::BTN_8,
48
+ Revdev::BTN_9,
49
+ Revdev::BTN_LEFT,
50
+ Revdev::BTN_MOUSE,
51
+ Revdev::BTN_MIDDLE,
52
+ Revdev::BTN_RIGHT,
53
+ Revdev::BTN_SIDE,
54
+ Revdev::BTN_EXTRA,
55
+ Revdev::BTN_FORWARD,
56
+ Revdev::BTN_BACK,
57
+ Revdev::BTN_TASK
58
+ # Revdev::BTN_TRIGGER, # libinput recognized as joystick if set
59
+ ].freeze
60
+
61
+ keyboard_keys = Revdev.constants.select { |c| c.start_with? "KEY_" }.map { |c| Revdev.const_get(c) }.freeze
62
+
63
+ @file.ioctl UI_SET_EVBIT, Revdev::EV_KEY
64
+ @counter = 0
65
+ Revdev::KEY_CNT.times do |i|
66
+ # https://github.com/mooz/xkeysnail/pull/101/files
67
+ if keyboard_keys.include?(i) || mouse_btns.include?(i)
68
+ # puts "setting #{i} (#{Revdev::REVERSE_MAPS[:KEY][i]})"
69
+ @file.ioctl UI_SET_KEYBIT, i
70
+ end
71
+ end
72
+
73
+ # @file.ioctl UI_SET_EVBIT, Revdev::EV_MSC
74
+ # Revdev::MSC_CNT.times do |i|
75
+ # @file.ioctl UI_SET_MSCBIT, i
76
+ # end
77
+
78
+ mouse_rel = [
79
+ Revdev::REL_X,
80
+ Revdev::REL_Y,
81
+ Revdev::REL_WHEEL,
82
+ Revdev::REL_HWHEEL
83
+ ].freeze
84
+
85
+ @file.ioctl UI_SET_EVBIT, Revdev::EV_REL
86
+ Revdev::REL_CNT.times do |i|
87
+ if mouse_rel.include?(i)
88
+ # puts "setting #{i} (#{Revdev::REVERSE_MAPS[:REL][i]})"
89
+ @file.ioctl UI_SET_RELBIT, i
90
+ end
91
+ end
92
+
93
+ @file.ioctl UI_SET_EVBIT, Revdev::EV_REP
94
+ end
95
+ end