fusuma-plugin-remap 0.3.0 → 0.4.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.
@@ -0,0 +1,172 @@
1
+ require "ruinput"
2
+
3
+ class UinputTouchpad < Ruinput::UinputDevice
4
+ include Ruinput
5
+
6
+ # create from original event device
7
+ # copy absinfo using eviocgabs
8
+ def create_from_device(name:, device:)
9
+ id = Revdev::InputId.new(
10
+ {
11
+ bustype: Revdev::BUS_I8042,
12
+ vendor: device.device_id.vendor,
13
+ product: device.device_id.product,
14
+ version: device.device_id.version
15
+ }
16
+ )
17
+
18
+ absinfo = {
19
+ Revdev::ABS_X => device.absinfo_for_axis(Revdev::ABS_X),
20
+ Revdev::ABS_Y => device.absinfo_for_axis(Revdev::ABS_Y),
21
+ Revdev::ABS_MT_POSITION_X => device.absinfo_for_axis(Revdev::ABS_MT_POSITION_X),
22
+ Revdev::ABS_MT_POSITION_Y => device.absinfo_for_axis(Revdev::ABS_MT_POSITION_Y),
23
+ Revdev::ABS_MT_SLOT => device.absinfo_for_axis(Revdev::ABS_MT_SLOT),
24
+ Revdev::ABS_MT_TOOL_TYPE => device.absinfo_for_axis(Revdev::ABS_MT_TOOL_TYPE),
25
+ Revdev::ABS_MT_TRACKING_ID => device.absinfo_for_axis(Revdev::ABS_MT_TRACKING_ID)
26
+ }
27
+
28
+ uud = Ruinput::UinputUserDev.new({
29
+ name: name,
30
+ id: id,
31
+ ff_effects_max: 0,
32
+ absmax: Array.new(Revdev::ABS_CNT, 0).tap { |a| absinfo.each { |k, v| a[k] = v[:absmax] } },
33
+ absmin: Array.new(Revdev::ABS_CNT, 0).tap { |a| absinfo.each { |k, v| a[k] = v[:absmin] } },
34
+ absfuzz: Array.new(Revdev::ABS_CNT, 0).tap { |a| absinfo.each { |k, v| a[k] = v[:absfuzz] } },
35
+ absflat: Array.new(Revdev::ABS_CNT, 0).tap { |a| absinfo.each { |k, v| a[k] = v[:absflat] } },
36
+ resolution: Array.new(Revdev::ABS_CNT, 0).tap { |a| absinfo.each { |k, v| a[k] = v[:resolution] } }
37
+ })
38
+
39
+ @file.syswrite uud.to_byte_string
40
+
41
+ set_all_events
42
+
43
+ @file.ioctl UI_DEV_CREATE, nil
44
+ @is_created = true
45
+ end
46
+
47
+ def set_all_events
48
+ raise "invalid method call: this uinput is already created" if @is_created
49
+
50
+ mouse_btns = [
51
+ Revdev::BTN_0,
52
+ Revdev::BTN_MISC,
53
+ Revdev::BTN_1,
54
+ Revdev::BTN_2,
55
+ Revdev::BTN_3,
56
+ Revdev::BTN_4,
57
+ Revdev::BTN_5,
58
+ Revdev::BTN_6,
59
+ Revdev::BTN_7,
60
+ Revdev::BTN_8,
61
+ Revdev::BTN_9,
62
+ Revdev::BTN_LEFT,
63
+ Revdev::BTN_MOUSE,
64
+ Revdev::BTN_MIDDLE,
65
+ Revdev::BTN_RIGHT,
66
+ Revdev::BTN_SIDE,
67
+ Revdev::BTN_EXTRA,
68
+ Revdev::BTN_FORWARD,
69
+ Revdev::BTN_BACK,
70
+ Revdev::BTN_TASK
71
+ # Revdev::BTN_TRIGGER, # disable because libinput recognize this device as a joystick
72
+ ].freeze
73
+
74
+ touchpad_btns = [
75
+ Revdev::BTN_TOUCH,
76
+ Revdev::BTN_TOOL_FINGER,
77
+ Revdev::BTN_TOOL_DOUBLETAP,
78
+ Revdev::BTN_TOOL_TRIPLETAP,
79
+ Revdev::BTN_TOOL_QUADTAP,
80
+ 0x148 # define BTN_TOOL_QUINTTAP 0x148 /* Five fingers on trackpad */
81
+ ].freeze
82
+
83
+ @file.ioctl UI_SET_EVBIT, Revdev::EV_KEY
84
+ @counter = 0
85
+ Revdev::KEY_CNT.times do |i|
86
+ # https://github.com/mooz/xkeysnail/pull/101/files
87
+ if mouse_btns.include?(i) || touchpad_btns.include?(i)
88
+ # puts "setting #{i} (#{Revdev::REVERSE_MAPS[:KEY][i]})"
89
+ @file.ioctl UI_SET_KEYBIT, i
90
+ else
91
+ # puts "skipping #{i} (#{Revdev::REVERSE_MAPS[:KEY][i]})"
92
+ end
93
+ end
94
+
95
+ touchpad_abs = [
96
+ Revdev::ABS_X,
97
+ Revdev::ABS_Y,
98
+ # Revdev::ABS_PRESSURE,
99
+ Revdev::ABS_MT_SLOT,
100
+ # Revdev::ABS_MT_TOUCH_MAJOR,
101
+ # Revdev::ABS_MT_TOUCH_MINOR,
102
+ Revdev::ABS_MT_POSITION_X,
103
+ Revdev::ABS_MT_POSITION_Y,
104
+ Revdev::ABS_MT_TRACKING_ID,
105
+ Revdev::ABS_MT_TOOL_TYPE
106
+ ].freeze
107
+
108
+ # kernel bug: device has min == max on ABS_Y
109
+ @file.ioctl UI_SET_EVBIT, Revdev::EV_ABS
110
+ Revdev::ABS_CNT.times do |i|
111
+ if touchpad_abs.include?(i)
112
+ puts "setting #{i} (#{Revdev::REVERSE_MAPS[:ABS][i]})"
113
+ @file.ioctl UI_SET_ABSBIT, i
114
+ else
115
+ puts "skipping #{i} (#{Revdev::REVERSE_MAPS[:ABS][i]})"
116
+ end
117
+ end
118
+
119
+ touchpad_rels = [
120
+ Revdev::REL_X,
121
+ Revdev::REL_Y,
122
+ Revdev::REL_WHEEL,
123
+ Revdev::REL_HWHEEL
124
+ ].freeze
125
+
126
+ @file.ioctl UI_SET_EVBIT, Revdev::EV_REL
127
+ Revdev::REL_CNT.times do |i|
128
+ if touchpad_rels.include?(i)
129
+ puts "setting #{i} (#{Revdev::REVERSE_MAPS[:REL][i]})"
130
+ @file.ioctl UI_SET_RELBIT, i
131
+ else
132
+ puts "skipping #{i} (#{Revdev::REVERSE_MAPS[:REL][i]})"
133
+ end
134
+ end
135
+
136
+ @file.ioctl UI_SET_EVBIT, Revdev::EV_REP
137
+
138
+ touchpad_mscs = [
139
+ 0x05 # define MSC_TIMESTAMP 0x05
140
+ ]
141
+ @file.ioctl UI_SET_EVBIT, Revdev::EV_MSC
142
+ Revdev::MSC_CNT.times do |i|
143
+ if touchpad_mscs.include?(i)
144
+ # puts "setting #{i} (#{Revdev::REVERSE_MAPS[:MSC][i]})"
145
+ @file.ioctl UI_SET_MSCBIT, i
146
+ else
147
+ # puts "skipping #{i} (#{Revdev::REVERSE_MAPS[:MSC][i]})"
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ class Revdev::EventDevice
154
+ def absinfo_for_axis(abs)
155
+ data = read_ioctl_with(eviocgabs(abs))
156
+
157
+ {
158
+ value: data[0, 4].unpack1("l<"),
159
+ absmin: data[4, 4].unpack1("l<"),
160
+ absmax: data[8, 4].unpack1("l<"),
161
+ absfuzz: data[12, 4].unpack1("l<"),
162
+ absflat: data[16, 4].unpack1("l<"),
163
+ resolution: data[20, 4].unpack1("l<")
164
+ }
165
+ end
166
+
167
+ # FIXME: undefined constants in revdev
168
+ def eviocgabs(abs)
169
+ # #define EVIOCGABS(abs) _IOR('E', 0x40 + abs, struct input_absinfo)
170
+ 0x80404540 + abs # EVIOCGABS(abs)
171
+ end
172
+ end
@@ -3,7 +3,7 @@
3
3
  module Fusuma
4
4
  module Plugin
5
5
  module Remap
6
- VERSION = "0.3.0"
6
+ VERSION = "0.4.0"
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fusuma-plugin-remap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - iberianpig
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-09-03 00:00:00.000000000 Z
11
+ date: 2024-04-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fusuma
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '3.1'
19
+ version: '3.4'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '3.1'
26
+ version: '3.4'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: fusuma-plugin-keypress
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0.5'
33
+ version: 0.11.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0.5'
40
+ version: 0.11.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: msgpack
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -90,6 +90,7 @@ email:
90
90
  - yhkyky@gmail.com
91
91
  executables:
92
92
  - fusuma-remap
93
+ - fusuma-touchpad-remap
93
94
  extensions: []
94
95
  extra_rdoc_files: []
95
96
  files:
@@ -98,13 +99,18 @@ files:
98
99
  - bin/console
99
100
  - bin/setup
100
101
  - exe/fusuma-remap
102
+ - exe/fusuma-touchpad-remap
101
103
  - fusuma-plugin-remap.gemspec
102
104
  - lib/fusuma/plugin/inputs/remap_keyboard_input.rb
103
105
  - lib/fusuma/plugin/inputs/remap_keyboard_input.yml
106
+ - lib/fusuma/plugin/inputs/remap_touchpad_input.rb
107
+ - lib/fusuma/plugin/inputs/remap_touchpad_input.yml
104
108
  - lib/fusuma/plugin/remap.rb
109
+ - lib/fusuma/plugin/remap/keyboard_remapper.rb
105
110
  - lib/fusuma/plugin/remap/layer_manager.rb
106
- - lib/fusuma/plugin/remap/remapper.rb
107
- - lib/fusuma/plugin/remap/ruinput_device_patched.rb
111
+ - lib/fusuma/plugin/remap/touchpad_remapper.rb
112
+ - lib/fusuma/plugin/remap/uinput_keyboard.rb
113
+ - lib/fusuma/plugin/remap/uinput_touchpad.rb
108
114
  - lib/fusuma/plugin/remap/version.rb
109
115
  homepage: https://github.com/iberianpig/fusuma-plugin-remap
110
116
  licenses:
@@ -126,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
132
  - !ruby/object:Gem::Version
127
133
  version: '0'
128
134
  requirements: []
129
- rubygems_version: 3.4.10
135
+ rubygems_version: 3.4.19
130
136
  signing_key:
131
137
  specification_version: 4
132
138
  summary: A Fusuma plugin for remapping keyboard events into virtual input devices.
@@ -1,233 +0,0 @@
1
- require "revdev"
2
- require "msgpack"
3
- require "set"
4
- require_relative "./layer_manager"
5
-
6
- require_relative "./ruinput_device_patched"
7
-
8
- module Fusuma
9
- module Plugin
10
- module Remap
11
- class Remapper
12
- include Revdev
13
- # @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:)
18
- @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
24
- end
25
-
26
- def run
27
- create_virtual_keyboard
28
- set_trap
29
- grab_keyboards
30
-
31
- old_ie = nil
32
- next_mapping = nil
33
- current_mapping = {}
34
-
35
- loop do
36
- ios = IO.select([*@source_keyboards.map(&:file), @layer_manager.reader])
37
- io = ios.first.first
38
-
39
- if io == @layer_manager.reader
40
- begin
41
- @layer_manager.receive_layer
42
- rescue EOFError
43
- @destroy.call
44
- end
45
-
46
- MultiLogger.debug "Remapper#run: layer changed to #{@layer_manager.current_layer}"
47
- next_mapping = @layer_manager.find_mapping
48
- next
49
- end
50
-
51
- if next_mapping && virtual_keyboard_all_key_released?
52
- if current_mapping != next_mapping
53
- current_mapping = next_mapping
54
- end
55
- next_mapping = nil
56
- end
57
-
58
- input_event = @source_keyboards.find { |kbd| kbd.file == io }.read_input_event
59
- input_key = find_key_from_code(input_event.code)
60
-
61
- 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
66
-
67
- old_ie = input_event
68
- if input_event.value != 2 # repeat
69
- packed = {key: input_key, status: input_event.value}.to_msgpack
70
- @keyboard_writer.puts(packed)
71
- end
72
- end
73
-
74
- remapped = current_mapping.fetch(input_key.to_sym, nil)
75
- if remapped.nil?
76
- @uinput.write_input_event(input_event)
77
- next
78
- end
79
-
80
- remapped_event = InputEvent.new(nil, input_event.type, find_code_from_key(remapped), input_event.value)
81
-
82
- # When Set.delete? fails, it means that the key was pressed before remapping started and was released.
83
- unless record_virtual_keyboard_event?(remapped, remapped_event.value)
84
- # set original key before remapping
85
- remapped_event.code = input_event.code
86
- end
87
-
88
- # remap to command will be nil
89
- # e.g) remap: { X: { command: echo 'foo' } }
90
- # this is because the command will be executed by fusuma process
91
- next if remapped_event.code.nil?
92
-
93
- @uinput.write_input_event(remapped_event)
94
- end
95
- end
96
-
97
- # @param [Revdev::InputEvent] event
98
- # @return [void]
99
- def record_virtual_keyboard_event?(remapped_value, event_value)
100
- case event_value
101
- when 0
102
- @pressed_virtual_keys.delete?(remapped_value)
103
- when 1
104
- @pressed_virtual_keys.add?(remapped_value)
105
- else
106
- # 2 is repeat
107
- true
108
- end
109
- end
110
-
111
- def virtual_keyboard_all_key_released?
112
- @pressed_virtual_keys.empty?
113
- end
114
-
115
- def create_virtual_keyboard
116
- @uinput.create "fusuma_virtual_keyboard",
117
- Revdev::InputId.new(
118
- # recognized as an internal keyboard on libinput,
119
- # touchpad is disabled when typing
120
- # see: (https://wayland.freedesktop.org/libinput/doc/latest/palm-detection.html#disable-while-typing)
121
- {
122
- bustype: Revdev::BUS_I8042,
123
- vendor: @internal_touchpad.device_id.vendor,
124
- product: @internal_touchpad.device_id.product,
125
- version: @internal_touchpad.device_id.version
126
- }
127
- )
128
- end
129
-
130
- def grab_keyboards
131
- @source_keyboards.each do |keyboard|
132
- wait_release_all_keys(keyboard) && keyboard.grab
133
- end
134
- end
135
-
136
- def set_trap
137
- @destroy = lambda do
138
- @source_keyboards.each do |kbd|
139
- kbd.ungrab
140
- rescue Errno::EINVAL
141
- # already ungrabbed
142
- end
143
-
144
- begin
145
- @uinput.destroy
146
- rescue IOError
147
- # already destroyed
148
- end
149
-
150
- exit 0
151
- end
152
-
153
- Signal.trap(:INT) { @destroy.call }
154
- Signal.trap(:TERM) { @destroy.call }
155
- end
156
-
157
- # Find remappable key from mapping and return remapped key code
158
- # If not found, return original key code
159
- # If the key is found but its value is not valid, return nil
160
- # @example
161
- # find_remapped_code({ "A" => "b" }, 30) # => 48
162
- # find_remapped_code({ "A" => "b" }, 100) # => 100
163
- # find_remapped_code({ "A" => {command: 'echo foobar'} }, 30) # => nil
164
- #
165
- # @param [Hash] mapping
166
- # @param [Integer] code
167
- # @return [Integer, nil]
168
- def find_remapped_code(mapping, code)
169
- key = find_key_from_code(code) # key = "A"
170
- remapped_key = mapping.fetch(key.to_sym, nil) # remapped_key = "b"
171
- return code unless remapped_key # return original code if key is not found
172
-
173
- find_code_from_key(remapped_key) # remapped_code = 48
174
- end
175
-
176
- # Find key name from key code
177
- # @example
178
- # find_key_from_code(30) # => "A"
179
- # find_key_from_code(48) # => "B"
180
- # @param [Integer] code
181
- # @return [String]
182
- def find_key_from_code(code)
183
- # { 30 => :A, 48 => :B, ... }
184
- @keys_per_code ||= Revdev.constants.select { |c| c.start_with? "KEY_" }.map { |c| [Revdev.const_get(c), c.to_s.delete_prefix("KEY_")] }.to_h
185
- @keys_per_code[code]
186
- end
187
-
188
- # Find key code from key name (e.g. "A", "B", "BTN_LEFT")
189
- # If key name is not found, return nil
190
- # @example
191
- # find_code_from_key("A") # => 30
192
- # find_code_from_key("B") # => 48
193
- # find_code_from_key("BTN_LEFT") # => 272
194
- # find_code_from_key("NOT_FOUND") # => nil
195
- # @param [String] key
196
- # @return [Integer] when key is available
197
- # @return [nil] when key is not available
198
- def find_code_from_key(key)
199
- # { KEY_A => 30, KEY_B => 48, ... }
200
- @codes_per_key ||= Revdev.constants.select { |c| c.start_with?("KEY_", "BTN_") }.map { |c| [c, Revdev.const_get(c)] }.to_h
201
-
202
- case key
203
- when String
204
- if key.start_with?("BTN_")
205
- @codes_per_key[key.upcase.to_sym]
206
- else
207
- @codes_per_key["KEY_#{key}".upcase.to_sym]
208
- end
209
- when Integer
210
- @codes_per_key["KEY_#{key}".upcase.to_sym]
211
- end
212
- end
213
-
214
- def released_all_keys?(device)
215
- # key status if all bytes are 0, the key is not pressed
216
- bytes = device.read_ioctl_with(Revdev::EVIOCGKEY)
217
- bytes.unpack("C*").all? { |byte| byte == 0 }
218
- end
219
-
220
- def wait_release_all_keys(device, &block)
221
- loop do
222
- if released_all_keys?(device)
223
- break true
224
- else
225
- # wait until all keys are released
226
- device.read_input_event
227
- end
228
- end
229
- end
230
- end
231
- end
232
- end
233
- end
@@ -1,49 +0,0 @@
1
- require "ruinput"
2
-
3
- class RuinputDevicePatched < Ruinput::UinputDevice
4
- include Ruinput
5
- def set_all_events
6
- raise "invalid method call: this uinput is already created" if @is_created
7
-
8
- mouse_btns = [
9
- Revdev::BTN_0,
10
- Revdev::BTN_MISC,
11
- Revdev::BTN_1,
12
- Revdev::BTN_2,
13
- Revdev::BTN_3,
14
- Revdev::BTN_4,
15
- Revdev::BTN_5,
16
- Revdev::BTN_6,
17
- Revdev::BTN_7,
18
- Revdev::BTN_8,
19
- Revdev::BTN_9,
20
- Revdev::BTN_LEFT,
21
- Revdev::BTN_MOUSE,
22
- Revdev::BTN_MIDDLE,
23
- Revdev::BTN_RIGHT
24
- ].freeze
25
-
26
- keyboard_keys = Revdev.constants.select { |c| c.start_with? "KEY_" }.map { |c| Revdev.const_get(c) }.freeze
27
-
28
- @file.ioctl UI_SET_EVBIT, Revdev::EV_KEY
29
- (Revdev::KEY_RESERVED...Revdev::KEY_CNT).each do |n|
30
- # https://github.com/mooz/xkeysnail/pull/101/files
31
- next unless keyboard_keys.include?(n) || mouse_btns.include?(n)
32
-
33
- @file.ioctl UI_SET_KEYBIT, n
34
- end
35
-
36
- # @file.ioctl UI_SET_EVBIT, Revdev::EV_MSC
37
- # Revdev::MSC_CNT.times do |i|
38
- # @file.ioctl UI_SET_MSCBIT, i
39
- # end
40
-
41
- # kernel bug: device has min == max on ABS_Y
42
- # @file.ioctl UI_SET_EVBIT, Revdev::EV_ABS
43
- # Revdev::ABS_CNT.times do |i|
44
- # @file.ioctl UI_SET_ABSBIT, i
45
- # end
46
-
47
- @file.ioctl UI_SET_EVBIT, Revdev::EV_REP
48
- end
49
- end