fusuma-plugin-remap 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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