From 3bce3f50f671b0a6838f72d791723c958838e902 Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Sun, 22 Nov 2020 17:30:06 +0100 Subject: [PATCH] properly reads keycode device for devices that are being injected into --- keymapper/config.py | 22 +--------------------- keymapper/getdevices.py | 19 +++++-------------- keymapper/gtk/window.py | 10 +++++++--- keymapper/injector.py | 5 ----- keymapper/mapping.py | 2 -- keymapper/reader.py | 24 +++++++++++------------- tests/test.py | 11 ++++++----- tests/testcases/config.py | 3 --- tests/testcases/getdevices.py | 21 +++++++++++---------- tests/testcases/reader.py | 12 ++++++++++++ 10 files changed, 53 insertions(+), 76 deletions(-) diff --git a/keymapper/config.py b/keymapper/config.py index f98d8205..9c20d92e 100644 --- a/keymapper/config.py +++ b/keymapper/config.py @@ -35,8 +35,7 @@ CONFIG_PATH = os.path.join(CONFIG, 'config') # an empty config with basic expected substructures INITIAL_CONFIG = { - 'autoload': {}, - 'map_EV_REL_devices': True + 'autoload': {} } @@ -53,25 +52,6 @@ class _Config: """get tuples of (device, preset).""" return self._config['autoload'].items() - def set_modify_movement_devices(self, active): - """Set if devices that control movements should also be mapped. - - This causes many movements event to be passed through python code, - and if this ever seems to affect the responsiveness of mouse movements, - it can be disabled. This is just an optional precaution. Disabling this - may make mapping some keys of the device impossible. - """ - self._config['map_EV_REL_devices'] = active - - def may_modify_movement_devices(self): - """Get if devices that control movements may be modified as well. - - Since movement events happen quite often and fast, I'd like to - add the option to disabling mapping those if it affects their - performance. TODO figure out which devices to inject to instead? - """ - return self._config['map_EV_REL_devices'] - def load_config(self): """Load the config from the file system.""" if not os.path.exists(CONFIG_PATH): diff --git a/keymapper/getdevices.py b/keymapper/getdevices.py index f1629188..c0518b77 100644 --- a/keymapper/getdevices.py +++ b/keymapper/getdevices.py @@ -70,18 +70,6 @@ class _GetDevicesProcess(multiprocessing.Process): if evdev.ecodes.EV_KEY not in capabilities: continue - if ( - not config.may_modify_movement_devices() - and evdev.ecodes.EV_REL in capabilities - ): - # TODO add checkbox to automatically load - # a preset on login - logger.debug( - 'Skipping %s to avoid impairing mouse movement', - device.path - ) - continue - usb = device.phys.split('/')[0] if grouped.get(usb) is None: grouped[usb] = [] @@ -128,6 +116,10 @@ def get_devices(include_keymapper=False): paths is a list of files in /dev/input that belong to the devices. They are grouped by usb port. + + Since this needs to do some stuff with /dev and spawn processes the + result is cached. Use refresh_devices if you need up to date + devices. """ global _devices if _devices is None: @@ -144,8 +136,7 @@ def get_devices(include_keymapper=False): # filter the result result = {} for device in _devices.keys(): - if include_keymapper and device.startswith('key-mapper'): - result[device] = _devices[device] + if not include_keymapper and device.startswith('key-mapper'): continue result[device] = _devices[device] diff --git a/keymapper/gtk/window.py b/keymapper/gtk/window.py index f7f81ef0..a200a734 100755 --- a/keymapper/gtk/window.py +++ b/keymapper/gtk/window.py @@ -50,9 +50,6 @@ CTX_APPLY = 1 CTX_ERROR = 3 -# TODO test on wayland - - def get_selected_row_bg(): """Get the background color that a row is going to have when selected.""" # ListBoxRows can be selected, but either they are always selectable @@ -226,6 +223,9 @@ class Window: CTX_APPLY, f'Applied the system default' ) + # restart reading because after injecting the device landscape + # changes a bit + keycode_reader.start_reading(self.selected_device) def on_save_preset_clicked(self, button): """Save changes to a preset to the file system.""" @@ -281,6 +281,10 @@ class Window: f'Could not grab device "{self.selected_device}"' ) + # restart reading because after injecting the device landscape + # changes a bit + keycode_reader.start_reading(self.selected_device) + def on_select_device(self, dropdown): """List all presets, create one if none exist yet.""" if dropdown.get_active_id() == self.selected_device: diff --git a/keymapper/injector.py b/keymapper/injector.py index f20e424d..0da7bfe7 100644 --- a/keymapper/injector.py +++ b/keymapper/injector.py @@ -264,9 +264,6 @@ class KeycodeInjector: if len(self.processes) == 0: raise OSError('Could not grab any device') - # key-mapper devices were added, freshly scan /dev/input - refresh_devices() - @ensure_numlock def stop_injecting(self): """Stop injecting keycodes.""" @@ -278,5 +275,3 @@ class KeycodeInjector: if process.is_alive(): process.terminate() self.processes[i] = None - - refresh_devices() diff --git a/keymapper/mapping.py b/keymapper/mapping.py index faa6c3e1..9f497e28 100644 --- a/keymapper/mapping.py +++ b/keymapper/mapping.py @@ -103,7 +103,6 @@ class Mapping: def load(self, device, preset): """Load a dumped JSON from home to overwrite the mappings.""" - # TODO test path = get_config_path(device, preset) logger.info('Loading preset from %s', path) @@ -125,7 +124,6 @@ class Mapping: def save(self, device, preset): """Dump as JSON into home.""" - # TODO test path = get_config_path(device, preset) logger.info('Saving preset to %s', path) diff --git a/keymapper/reader.py b/keymapper/reader.py index a915c4ff..06b5fc98 100644 --- a/keymapper/reader.py +++ b/keymapper/reader.py @@ -25,7 +25,7 @@ import evdev from keymapper.logger import logger -from keymapper.getdevices import get_devices +from keymapper.getdevices import get_devices, refresh_devices class _KeycodeReader: @@ -50,25 +50,23 @@ class _KeycodeReader: If read is called without prior start_reading, no keycodes will be available. - - Parameters - ---------- - device : string - The name of the device. """ - groups = get_devices(include_keymapper=True) - for name, group in groups.items(): + # make sure this sees up to date devices, including those created + # by key-mapper + refresh_devices() + + self.virtual_devices = [] + + for name, group in get_devices(include_keymapper=True).items(): # also find stuff like "key-mapper {device}" if device not in name: - return - - paths = group['paths'] + continue # Watch over each one of the potentially multiple devices per # hardware - self.virtual_devices = [ + self.virtual_devices += [ evdev.InputDevice(path) - for path in paths + for path in group['paths'] ] logger.debug( diff --git a/tests/test.py b/tests/test.py index 1ea3c5e1..d4b93598 100644 --- a/tests/test.py +++ b/tests/test.py @@ -48,7 +48,7 @@ fixtures = { 'name': 'device 1 foo' }, '/dev/input/event10': { - 'capabilities': {evdev.ecodes.EV_KEY: []}, + 'capabilities': {evdev.ecodes.EV_KEY: list(evdev.ecodes.keys.keys())}, 'phys': 'usb-0000:03:00.0-1/input3', 'name': 'device 1' }, @@ -65,7 +65,7 @@ fixtures = { # device 2 '/dev/input/event20': { - 'capabilities': {evdev.ecodes.EV_KEY: []}, + 'capabilities': {evdev.ecodes.EV_KEY: list(evdev.ecodes.keys.keys())}, 'phys': 'usb-0000:03:00.0-2/input1', 'name': 'device 2' }, @@ -77,10 +77,10 @@ fixtures = { 'name': 'device 3' }, - # key-mapper devices are also ignored, another instance of key-mapper - # started injecting apparently. + # key-mapper devices are not displayed in the ui, some instance + # of key-mapper started injecting apparently. '/dev/input/event40': { - 'capabilities': {evdev.ecodes.EV_KEY: []}, + 'capabilities': {evdev.ecodes.EV_KEY: list(evdev.ecodes.keys.keys())}, 'phys': 'key-mapper/input1', 'name': 'key-mapper device 2' }, @@ -170,6 +170,7 @@ def patch_evdev(): class UInput: def __init__(self, *args, **kwargs): self.fd = 0 + self.device = InputDevice('/dev/input/event40') pass def write(self, type, code, value): diff --git a/tests/testcases/config.py b/tests/testcases/config.py index ac1ab5e8..7f3cf4e5 100644 --- a/tests/testcases/config.py +++ b/tests/testcases/config.py @@ -48,11 +48,9 @@ class TestConfig(unittest.TestCase): def test_save_load(self): config.set_autoload_preset('d1', 'a') config.set_autoload_preset('d2', 'b') - config.set_modify_movement_devices(True) config.save_config() # ignored after load - config.set_modify_movement_devices(False) config.set_autoload_preset('d3', 'c') config.load_config() @@ -60,7 +58,6 @@ class TestConfig(unittest.TestCase): list(config.iterate_autoload_presets()), [('d1', 'a'), ('d2', 'b')] ) - self.assertTrue(config.may_modify_movement_devices()) if __name__ == "__main__": diff --git a/tests/testcases/getdevices.py b/tests/testcases/getdevices.py index 1c36c789..cd5774ef 100644 --- a/tests/testcases/getdevices.py +++ b/tests/testcases/getdevices.py @@ -21,8 +21,7 @@ import unittest -from keymapper.getdevices import _GetDevicesProcess -from keymapper.config import config +from keymapper.getdevices import _GetDevicesProcess, get_devices class FakePipe: @@ -54,14 +53,16 @@ class TestGetDevices(unittest.TestCase): 'device 2': { 'paths': ['/dev/input/event20'], 'devices': ['device 2'] - } + }, + 'key-mapper device 2': { + 'paths': ['/dev/input/event40'], + 'devices': ['key-mapper device 2'] + }, }) + self.assertDictEqual(pipe.devices, get_devices(include_keymapper=True)) - def test_map_movement_devices(self): - pipe = FakePipe() - config.set_modify_movement_devices(False) - _GetDevicesProcess(pipe).run() - self.assertDictEqual(pipe.devices, { + def test_get_devices_2(self): + self.assertDictEqual(get_devices(), { 'device 1': { 'paths': [ '/dev/input/event11', @@ -75,8 +76,8 @@ class TestGetDevices(unittest.TestCase): ] }, 'device 2': { - 'paths': ['/dev/input/event20'], - 'devices': ['device 2'] + 'paths': ['/dev/input/event20'], + 'devices': ['device 2'] } }) diff --git a/tests/testcases/reader.py b/tests/testcases/reader.py index 1b2fdc4f..06b00354 100644 --- a/tests/testcases/reader.py +++ b/tests/testcases/reader.py @@ -60,6 +60,18 @@ class TestReader(unittest.TestCase): ] self.assertIsNone(keycode_reader.read()) + def test_keymapper_devices(self): + # key-mapper creates devices in /dev, which are also used for + # reading since the original device is in grab-mode + keycode_reader.start_reading('device 2') + pending_events['key-mapper device 2'] = [ + Event(evdev.events.EV_KEY, CODE_1, 1), + Event(evdev.events.EV_KEY, CODE_2, 1), + Event(evdev.events.EV_KEY, CODE_3, 1) + ] + self.assertEqual(keycode_reader.read(), CODE_3 + 8) + self.assertIsNone(keycode_reader.read()) + def test_clear(self): keycode_reader.start_reading('device 1') pending_events['device 1'] = [