properly reads keycode device for devices that are being injected into

xkb
sezanzeb 4 years ago committed by sezanzeb
parent 6f0e8d97fb
commit 3bce3f50f6

@ -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):

@ -70,18 +70,6 @@ class _GetDevicesProcess(multiprocessing.Process):
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] = []
@ -128,6 +116,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:
@ -144,8 +136,7 @@ def get_devices(include_keymapper=False):
# filter the result # filter the result
result = {} result = {}
for device in _devices.keys(): for device in _devices.keys():
if include_keymapper and device.startswith('key-mapper'): if not include_keymapper and device.startswith('key-mapper'):
result[device] = _devices[device]
continue continue
result[device] = _devices[device] result[device] = _devices[device]

@ -50,9 +50,6 @@ CTX_APPLY = 1
CTX_ERROR = 3 CTX_ERROR = 3
# TODO test on wayland
def get_selected_row_bg(): def get_selected_row_bg():
"""Get the background color that a row is going to have when selected.""" """Get the background color that a row is going to have when selected."""
# ListBoxRows can be selected, but either they are always selectable # ListBoxRows can be selected, but either they are always selectable
@ -226,6 +223,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 +281,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:

@ -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()

@ -103,7 +103,6 @@ class Mapping:
def load(self, device, preset): def load(self, device, preset):
"""Load a dumped JSON from home to overwrite the mappings.""" """Load a dumped JSON from home to overwrite the mappings."""
# TODO test
path = get_config_path(device, preset) path = get_config_path(device, preset)
logger.info('Loading preset from %s', path) logger.info('Loading preset from %s', path)
@ -125,7 +124,6 @@ class Mapping:
def save(self, device, preset): def save(self, device, preset):
"""Dump as JSON into home.""" """Dump as JSON into home."""
# TODO test
path = get_config_path(device, preset) path = get_config_path(device, preset)
logger.info('Saving preset to %s', path) logger.info('Saving preset to %s', path)

@ -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,23 @@ 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, including those created
for name, group in groups.items(): # 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}" # 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(

@ -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 not displayed in the ui, some instance
# started injecting apparently. # of key-mapper 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):

@ -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__":

@ -21,8 +21,7 @@
import unittest import unittest
from keymapper.getdevices import _GetDevicesProcess from keymapper.getdevices import _GetDevicesProcess, get_devices
from keymapper.config import config
class FakePipe: class FakePipe:
@ -54,14 +53,16 @@ class TestGetDevices(unittest.TestCase):
'device 2': { 'device 2': {
'paths': ['/dev/input/event20'], 'paths': ['/dev/input/event20'],
'devices': ['device 2'] '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): def test_get_devices_2(self):
pipe = FakePipe() self.assertDictEqual(get_devices(), {
config.set_modify_movement_devices(False)
_GetDevicesProcess(pipe).run()
self.assertDictEqual(pipe.devices, {
'device 1': { 'device 1': {
'paths': [ 'paths': [
'/dev/input/event11', '/dev/input/event11',
@ -75,8 +76,8 @@ class TestGetDevices(unittest.TestCase):
] ]
}, },
'device 2': { 'device 2': {
'paths': ['/dev/input/event20'], 'paths': ['/dev/input/event20'],
'devices': ['device 2'] 'devices': ['device 2']
} }
}) })

@ -60,6 +60,18 @@ class TestReader(unittest.TestCase):
] ]
self.assertIsNone(keycode_reader.read()) 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): def test_clear(self):
keycode_reader.start_reading('device 1') keycode_reader.start_reading('device 1')
pending_events['device 1'] = [ pending_events['device 1'] = [

Loading…
Cancel
Save