properly reads keycode device for devices that are being injected into

This commit is contained in:
sezanzeb 2020-11-22 17:30:06 +01:00
parent 2afde0039f
commit 06a10c64b4
8 changed files with 33 additions and 93 deletions

View File

@ -35,8 +35,7 @@ CONFIG_PATH = os.path.join(CONFIG, 'config')
# an empty config with basic expected substructures # an empty config with basic expected substructures
INITIAL_CONFIG = { INITIAL_CONFIG = {
'autoload': {}, 'autoload': {}
'map_EV_REL_devices': True
} }
@ -53,25 +52,6 @@ class _Config:
"""get tuples of (device, preset).""" """get tuples of (device, preset)."""
return self._config['autoload'].items() 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): def load_config(self):
"""Load the config from the file system.""" """Load the config from the file system."""
if not os.path.exists(CONFIG_PATH): if not os.path.exists(CONFIG_PATH):

View File

@ -64,24 +64,16 @@ class _GetDevicesProcess(multiprocessing.Process):
# "Logitech USB Keyboard" and "Logitech USB Keyboard Consumer Control" # "Logitech USB Keyboard" and "Logitech USB Keyboard Consumer Control"
grouped = {} grouped = {}
for device in devices: for device in devices:
if device.phys.startswith('key-mapper'):
# injector device, not really periphery
continue
# only keyboard devices # only keyboard devices
# https://www.kernel.org/doc/html/latest/input/event-codes.html # https://www.kernel.org/doc/html/latest/input/event-codes.html
capabilities = device.capabilities().keys() capabilities = device.capabilities().keys()
if evdev.ecodes.EV_KEY not in capabilities: if evdev.ecodes.EV_KEY not in capabilities:
continue 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] usb = device.phys.split('/')[0]
if grouped.get(usb) is None: if grouped.get(usb) is None:
grouped[usb] = [] grouped[usb] = []
@ -117,7 +109,7 @@ def refresh_devices():
return get_devices() return get_devices()
def get_devices(include_keymapper=False): def get_devices():
"""Group devices and get relevant infos per group. """Group devices and get relevant infos per group.
Returns a list containing mappings of Returns a list containing mappings of
@ -128,6 +120,10 @@ def get_devices(include_keymapper=False):
paths is a list of files in /dev/input that belong to the devices. paths is a list of files in /dev/input that belong to the devices.
They are grouped by usb port. 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 global _devices
if _devices is None: if _devices is None:
@ -141,13 +137,4 @@ def get_devices(include_keymapper=False):
names = [f'"{name}"' for name in _devices] names = [f'"{name}"' for name in _devices]
logger.info('Found %s', ', '.join(names)) logger.info('Found %s', ', '.join(names))
# filter the result return _devices
result = {}
for device in _devices.keys():
if include_keymapper and device.startswith('key-mapper'):
result[device] = _devices[device]
continue
result[device] = _devices[device]
return result

View File

@ -226,6 +226,9 @@ class Window:
CTX_APPLY, CTX_APPLY,
f'Applied the system default' 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): def on_save_preset_clicked(self, button):
"""Save changes to a preset to the file system.""" """Save changes to a preset to the file system."""
@ -281,6 +284,10 @@ class Window:
f'Could not grab device "{self.selected_device}"' 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): def on_select_device(self, dropdown):
"""List all presets, create one if none exist yet.""" """List all presets, create one if none exist yet."""
if dropdown.get_active_id() == self.selected_device: if dropdown.get_active_id() == self.selected_device:

View File

@ -264,9 +264,6 @@ class KeycodeInjector:
if len(self.processes) == 0: if len(self.processes) == 0:
raise OSError('Could not grab any device') raise OSError('Could not grab any device')
# key-mapper devices were added, freshly scan /dev/input
refresh_devices()
@ensure_numlock @ensure_numlock
def stop_injecting(self): def stop_injecting(self):
"""Stop injecting keycodes.""" """Stop injecting keycodes."""
@ -278,5 +275,3 @@ class KeycodeInjector:
if process.is_alive(): if process.is_alive():
process.terminate() process.terminate()
self.processes[i] = None self.processes[i] = None
refresh_devices()

View File

@ -25,7 +25,7 @@
import evdev import evdev
from keymapper.logger import logger from keymapper.logger import logger
from keymapper.getdevices import get_devices from keymapper.getdevices import get_devices, refresh_devices
class _KeycodeReader: class _KeycodeReader:
@ -50,25 +50,22 @@ class _KeycodeReader:
If read is called without prior start_reading, no keycodes If read is called without prior start_reading, no keycodes
will be available. will be available.
Parameters
----------
device : string
The name of the device.
""" """
groups = get_devices(include_keymapper=True) # make sure this sees up to date devices
for name, group in groups.items(): refresh_devices()
self.virtual_devices = []
for name, group in get_devices().items():
# also find stuff like "key-mapper {device}" # also find stuff like "key-mapper {device}"
if device not in name: if device not in name:
return continue
paths = group['paths']
# Watch over each one of the potentially multiple devices per # Watch over each one of the potentially multiple devices per
# hardware # hardware
self.virtual_devices = [ self.virtual_devices += [
evdev.InputDevice(path) evdev.InputDevice(path)
for path in paths for path in group['paths']
] ]
logger.debug( logger.debug(

View File

@ -48,7 +48,7 @@ fixtures = {
'name': 'device 1 foo' 'name': 'device 1 foo'
}, },
'/dev/input/event10': { '/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', 'phys': 'usb-0000:03:00.0-1/input3',
'name': 'device 1' 'name': 'device 1'
}, },
@ -65,7 +65,7 @@ fixtures = {
# device 2 # device 2
'/dev/input/event20': { '/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', 'phys': 'usb-0000:03:00.0-2/input1',
'name': 'device 2' 'name': 'device 2'
}, },
@ -77,10 +77,10 @@ fixtures = {
'name': 'device 3' 'name': 'device 3'
}, },
# key-mapper devices are also ignored, another instance of key-mapper # key-mapper devices are also ignored, some instance of key-mapper
# started injecting apparently. # started injecting apparently.
'/dev/input/event40': { '/dev/input/event40': {
'capabilities': {evdev.ecodes.EV_KEY: []}, 'capabilities': {evdev.ecodes.EV_KEY: list(evdev.ecodes.keys.keys())},
'phys': 'key-mapper/input1', 'phys': 'key-mapper/input1',
'name': 'key-mapper device 2' 'name': 'key-mapper device 2'
}, },
@ -170,6 +170,7 @@ def patch_evdev():
class UInput: class UInput:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.fd = 0 self.fd = 0
self.device = InputDevice('/dev/input/event40')
pass pass
def write(self, type, code, value): def write(self, type, code, value):

View File

@ -48,11 +48,9 @@ class TestConfig(unittest.TestCase):
def test_save_load(self): def test_save_load(self):
config.set_autoload_preset('d1', 'a') config.set_autoload_preset('d1', 'a')
config.set_autoload_preset('d2', 'b') config.set_autoload_preset('d2', 'b')
config.set_modify_movement_devices(True)
config.save_config() config.save_config()
# ignored after load # ignored after load
config.set_modify_movement_devices(False)
config.set_autoload_preset('d3', 'c') config.set_autoload_preset('d3', 'c')
config.load_config() config.load_config()
@ -60,7 +58,6 @@ class TestConfig(unittest.TestCase):
list(config.iterate_autoload_presets()), list(config.iterate_autoload_presets()),
[('d1', 'a'), ('d2', 'b')] [('d1', 'a'), ('d2', 'b')]
) )
self.assertTrue(config.may_modify_movement_devices())
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -22,7 +22,6 @@
import unittest import unittest
from keymapper.getdevices import _GetDevicesProcess from keymapper.getdevices import _GetDevicesProcess
from keymapper.config import config
class FakePipe: class FakePipe:
@ -57,29 +56,6 @@ class TestGetDevices(unittest.TestCase):
} }
}) })
def test_map_movement_devices(self):
pipe = FakePipe()
config.set_modify_movement_devices(False)
_GetDevicesProcess(pipe).run()
self.assertDictEqual(pipe.devices, {
'device 1': {
'paths': [
'/dev/input/event11',
'/dev/input/event10',
'/dev/input/event13'
],
'devices': [
'device 1 foo',
'device 1',
'device 1'
]
},
'device 2': {
'paths': ['/dev/input/event20'],
'devices': ['device 2']
}
})
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()