Class: Fusuma::Plugin::Remap::Remapper
- Inherits:
-
Object
- Object
- Fusuma::Plugin::Remap::Remapper
- Includes:
- Revdev
- Defined in:
- lib/fusuma/plugin/remap/remapper.rb
Instance Method Summary collapse
- #create_virtual_keyboard ⇒ Object
-
#find_code_from_key(key) ⇒ Integer?
Find key code from key name (e.g. “A”, “B”, “BTN_LEFT”) If key name is not found, return nil.
-
#find_key_from_code(code) ⇒ String
Find key name from key code.
-
#find_remapped_code(mapping, code) ⇒ Integer?
Find remappable key from mapping and return remapped key code If not found, return original key code If the key is found but its value is not valid, return nil.
- #grab_keyboards ⇒ Object
-
#initialize(layer_manager:, keyboard_writer:, source_keyboards:, internal_touchpad:) ⇒ Remapper
constructor
A new instance of Remapper.
- #record_virtual_keyboard_event?(remapped_value, event_value) ⇒ void
- #released_all_keys?(device) ⇒ Boolean
- #run ⇒ Object
- #set_trap ⇒ Object
- #virtual_keyboard_all_key_released? ⇒ Boolean
- #wait_release_all_keys(device, &block) ⇒ Object
Constructor Details
#initialize(layer_manager:, keyboard_writer:, source_keyboards:, internal_touchpad:) ⇒ Remapper
Returns a new instance of Remapper.
17 18 19 20 21 22 23 24 |
# File 'lib/fusuma/plugin/remap/remapper.rb', line 17 def initialize(layer_manager:, keyboard_writer:, source_keyboards:, internal_touchpad:) @layer_manager = layer_manager # request to change layer @keyboard_writer = keyboard_writer # write event to original keyboard @source_keyboards = source_keyboards # original keyboard @internal_touchpad = internal_touchpad # internal touchpad @uinput = RuinputDevicePatched.new "/dev/uinput" @pressed_virtual_keys = Set.new end |
Instance Method Details
#create_virtual_keyboard ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/fusuma/plugin/remap/remapper.rb', line 115 def create_virtual_keyboard @uinput.create "fusuma_virtual_keyboard", Revdev::InputId.new( # recognized as an internal keyboard on libinput, # touchpad is disabled when typing # see: (https://wayland.freedesktop.org/libinput/doc/latest/palm-detection.html#disable-while-typing) { bustype: Revdev::BUS_I8042, vendor: @internal_touchpad.device_id.vendor, product: @internal_touchpad.device_id.product, version: @internal_touchpad.device_id.version } ) end |
#find_code_from_key(key) ⇒ Integer?
Find key code from key name (e.g. “A”, “B”, “BTN_LEFT”) If key name is not found, return nil
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/fusuma/plugin/remap/remapper.rb', line 196 def find_code_from_key(key) # { KEY_A => 30, KEY_B => 48, ... } @codes_per_key ||= Revdev.constants.select { |c| c.start_with?("KEY_", "BTN_") }.map { |c| [c, Revdev.const_get(c)] }.to_h case key when String if key.start_with?("BTN_") @codes_per_key[key.upcase.to_sym] else @codes_per_key["KEY_#{key}".upcase.to_sym] end when Integer @codes_per_key["KEY_#{key}".upcase.to_sym] end end |
#find_key_from_code(code) ⇒ String
Find key name from key code
180 181 182 183 184 |
# File 'lib/fusuma/plugin/remap/remapper.rb', line 180 def find_key_from_code(code) # { 30 => :A, 48 => :B, ... } @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 @keys_per_code[code] end |
#find_remapped_code(mapping, code) ⇒ Integer?
Find remappable key from mapping and return remapped key code If not found, return original key code If the key is found but its value is not valid, return nil
166 167 168 169 170 171 172 |
# File 'lib/fusuma/plugin/remap/remapper.rb', line 166 def find_remapped_code(mapping, code) key = find_key_from_code(code) # key = "A" remapped_key = mapping.fetch(key.to_sym, nil) # remapped_key = "b" return code unless remapped_key # return original code if key is not found find_code_from_key(remapped_key) # remapped_code = 48 end |
#grab_keyboards ⇒ Object
130 131 132 133 134 |
# File 'lib/fusuma/plugin/remap/remapper.rb', line 130 def grab_keyboards @source_keyboards.each do |keyboard| wait_release_all_keys(keyboard) && keyboard.grab end end |
#record_virtual_keyboard_event?(remapped_value, event_value) ⇒ void
This method returns an undefined value.
99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/fusuma/plugin/remap/remapper.rb', line 99 def record_virtual_keyboard_event?(remapped_value, event_value) case event_value when 0 @pressed_virtual_keys.delete?(remapped_value) when 1 @pressed_virtual_keys.add?(remapped_value) else # 2 is repeat true end end |
#released_all_keys?(device) ⇒ Boolean
212 213 214 215 216 |
# File 'lib/fusuma/plugin/remap/remapper.rb', line 212 def released_all_keys?(device) # key status if all bytes are 0, the key is not pressed bytes = device.read_ioctl_with(Revdev::EVIOCGKEY) bytes.unpack("C*").all? { |byte| byte == 0 } end |
#run ⇒ Object
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/fusuma/plugin/remap/remapper.rb', line 26 def run create_virtual_keyboard set_trap grab_keyboards old_ie = nil next_mapping = nil current_mapping = {} loop do ios = IO.select([*@source_keyboards.map(&:file), @layer_manager.reader]) io = ios.first.first if io == @layer_manager.reader begin @layer_manager.receive_layer rescue EOFError @destroy.call end MultiLogger.debug "Remapper#run: layer changed to #{@layer_manager.current_layer}" next_mapping = @layer_manager.find_mapping next end if next_mapping && virtual_keyboard_all_key_released? if current_mapping != next_mapping current_mapping = next_mapping end next_mapping = nil end input_event = @source_keyboards.find { |kbd| kbd.file == io }.read_input_event input_key = find_key_from_code(input_event.code) if input_event.type == EV_KEY # FIXME: exit when RIGHTCTRL-LEFTCTRL is pressed if (old_ie&.code == KEY_RIGHTCTRL && old_ie.value != 0) && (input_event.code == KEY_LEFTCTRL && input_event.value != 0) @destroy.call end old_ie = input_event if input_event.value != 2 # repeat packed = {key: input_key, status: input_event.value}.to_msgpack @keyboard_writer.puts(packed) end end remapped = current_mapping.fetch(input_key.to_sym, nil) if remapped.nil? @uinput.write_input_event(input_event) next end remapped_event = InputEvent.new(nil, input_event.type, find_code_from_key(remapped), input_event.value) # When Set.delete? fails, it means that the key was pressed before remapping started and was released. unless record_virtual_keyboard_event?(remapped, remapped_event.value) # set original key before remapping remapped_event.code = input_event.code end # remap to command will be nil # e.g) remap: { X: { command: echo 'foo' } } # this is because the command will be executed by fusuma process next if remapped_event.code.nil? @uinput.write_input_event(remapped_event) end end |
#set_trap ⇒ Object
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/fusuma/plugin/remap/remapper.rb', line 136 def set_trap @destroy = lambda do begin @source_keyboards.each { |kbd| kbd.ungrab } rescue => e puts e. end begin @uinput.destroy rescue => e puts e. end exit 0 end Signal.trap(:INT) { @destroy.call } Signal.trap(:TERM) { @destroy.call } end |
#virtual_keyboard_all_key_released? ⇒ Boolean
111 112 113 |
# File 'lib/fusuma/plugin/remap/remapper.rb', line 111 def virtual_keyboard_all_key_released? @pressed_virtual_keys.empty? end |
#wait_release_all_keys(device, &block) ⇒ Object
218 219 220 221 222 223 224 225 226 227 |
# File 'lib/fusuma/plugin/remap/remapper.rb', line 218 def wait_release_all_keys(device, &block) loop do if released_all_keys?(device) break true else # wait until all keys are released device.read_input_event end end end |