mirror of
https://github.com/sezanzeb/input-remapper
synced 2024-11-04 12:00:16 +00:00
wip reading keycodes even though the device is grabbed
This commit is contained in:
parent
c7736b15ed
commit
2afde0039f
@ -64,7 +64,12 @@ class _Config:
|
|||||||
self._config['map_EV_REL_devices'] = active
|
self._config['map_EV_REL_devices'] = active
|
||||||
|
|
||||||
def may_modify_movement_devices(self):
|
def may_modify_movement_devices(self):
|
||||||
"""Get if devices that control movements may be modified as well."""
|
"""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']
|
return self._config['map_EV_REL_devices']
|
||||||
|
|
||||||
def load_config(self):
|
def load_config(self):
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
|
|
||||||
|
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
import time
|
||||||
|
import copy
|
||||||
|
|
||||||
import evdev
|
import evdev
|
||||||
|
|
||||||
@ -62,10 +64,6 @@ 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()
|
||||||
@ -76,8 +74,6 @@ class _GetDevicesProcess(multiprocessing.Process):
|
|||||||
not config.may_modify_movement_devices()
|
not config.may_modify_movement_devices()
|
||||||
and evdev.ecodes.EV_REL in capabilities
|
and evdev.ecodes.EV_REL in capabilities
|
||||||
):
|
):
|
||||||
# skip devices that control movement to avoid affecting
|
|
||||||
# their performance due to the amount of their events.
|
|
||||||
# TODO add checkbox to automatically load
|
# TODO add checkbox to automatically load
|
||||||
# a preset on login
|
# a preset on login
|
||||||
logger.debug(
|
logger.debug(
|
||||||
@ -90,7 +86,7 @@ class _GetDevicesProcess(multiprocessing.Process):
|
|||||||
if grouped.get(usb) is None:
|
if grouped.get(usb) is None:
|
||||||
grouped[usb] = []
|
grouped[usb] = []
|
||||||
|
|
||||||
logger.debug('Found "%s", %s, %s', device.name, device.path, usb)
|
logger.spam('Found "%s", %s, %s', device.name, device.path, usb)
|
||||||
|
|
||||||
grouped[usb].append((device.name, device.path))
|
grouped[usb].append((device.name, device.path))
|
||||||
|
|
||||||
@ -109,13 +105,19 @@ class _GetDevicesProcess(multiprocessing.Process):
|
|||||||
|
|
||||||
|
|
||||||
def refresh_devices():
|
def refresh_devices():
|
||||||
"""Get new devices, e.g. new ones created by key-mapper."""
|
"""Get new devices, e.g. new ones created by key-mapper.
|
||||||
|
|
||||||
|
This should be called whenever devices in /dev are added or removed.
|
||||||
|
"""
|
||||||
|
# it may take a little bit of time until devices are visible after
|
||||||
|
# changes
|
||||||
|
time.sleep(0.1)
|
||||||
global _devices
|
global _devices
|
||||||
_devices = None
|
_devices = None
|
||||||
return get_devices()
|
return get_devices()
|
||||||
|
|
||||||
|
|
||||||
def get_devices():
|
def get_devices(include_keymapper=False):
|
||||||
"""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
|
||||||
@ -138,4 +140,14 @@ def get_devices():
|
|||||||
else:
|
else:
|
||||||
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))
|
||||||
return _devices
|
|
||||||
|
# filter the result
|
||||||
|
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
|
||||||
|
@ -86,8 +86,10 @@ class Row(Gtk.ListBoxRow):
|
|||||||
self.keycode.set_label(str(new_keycode))
|
self.keycode.set_label(str(new_keycode))
|
||||||
# switch to the character, don't require mouse input because
|
# switch to the character, don't require mouse input because
|
||||||
# that would overwrite the key with the mouse-button key if
|
# that would overwrite the key with the mouse-button key if
|
||||||
# the current device is a mouse
|
# the current device is a mouse. idle_add this so that the
|
||||||
self.window.window.set_focus(self.character_input)
|
# keycode event won't write into the character input as well.
|
||||||
|
window = self.window.window
|
||||||
|
GLib.idle_add(lambda: window.set_focus(self.character_input))
|
||||||
self.highlight()
|
self.highlight()
|
||||||
|
|
||||||
# the character is empty and therefore the mapping is not complete
|
# the character is empty and therefore the mapping is not complete
|
||||||
|
@ -34,13 +34,14 @@ import multiprocessing
|
|||||||
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
|
||||||
from keymapper.state import custom_mapping, system_mapping
|
from keymapper.state import custom_mapping, system_mapping
|
||||||
|
|
||||||
|
|
||||||
DEV_NAME = 'key-mapper'
|
DEV_NAME = 'key-mapper'
|
||||||
DEVICE_CREATED = 1
|
DEVICE_CREATED = 1
|
||||||
FAILED = 2
|
FAILED = 2
|
||||||
|
DEVICE_SKIPPED = 3
|
||||||
|
|
||||||
|
|
||||||
def _grab(path):
|
def _grab(path):
|
||||||
@ -88,6 +89,24 @@ def _modify_capabilities(device):
|
|||||||
return capabilities
|
return capabilities
|
||||||
|
|
||||||
|
|
||||||
|
def _is_device_mapped(device, mapping):
|
||||||
|
"""Check if this device has capabilities that are being mapped.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
device : evdev.InputDevice
|
||||||
|
mapping : Mapping
|
||||||
|
"""
|
||||||
|
capabilities = device.capabilities(absinfo=False)[evdev.ecodes.EV_KEY]
|
||||||
|
needed = False
|
||||||
|
for keycode, _ in custom_mapping:
|
||||||
|
if keycode in capabilities:
|
||||||
|
needed = True
|
||||||
|
if not needed:
|
||||||
|
logger.debug('No need to grab %s', device.path)
|
||||||
|
return needed
|
||||||
|
|
||||||
|
|
||||||
def _start_injecting_worker(path, pipe, mapping):
|
def _start_injecting_worker(path, pipe, mapping):
|
||||||
"""Inject keycodes for one of the virtual devices.
|
"""Inject keycodes for one of the virtual devices.
|
||||||
|
|
||||||
@ -108,19 +127,21 @@ def _start_injecting_worker(path, pipe, mapping):
|
|||||||
pipe.send(FAILED)
|
pipe.send(FAILED)
|
||||||
return
|
return
|
||||||
|
|
||||||
capabilities = _modify_capabilities(device)
|
if not _is_device_mapped(device, mapping):
|
||||||
|
pipe.send(DEVICE_SKIPPED)
|
||||||
|
return
|
||||||
|
|
||||||
keymapper_device = evdev.UInput(
|
keymapper_device = evdev.UInput(
|
||||||
name=f'key-mapper {device.name}',
|
name=f'key-mapper {device.name}',
|
||||||
phys='key-mapper',
|
phys='key-mapper',
|
||||||
events=capabilities
|
events=_modify_capabilities(device)
|
||||||
)
|
)
|
||||||
|
|
||||||
pipe.send(DEVICE_CREATED)
|
pipe.send(DEVICE_CREATED)
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'Started injecting into %s, fd %s',
|
'Started injecting into %s, fd %s',
|
||||||
device.path, keymapper_device.fd
|
keymapper_device.device.path, keymapper_device.fd
|
||||||
)
|
)
|
||||||
|
|
||||||
for event in device.read_loop():
|
for event in device.read_loop():
|
||||||
@ -222,11 +243,7 @@ class KeycodeInjector:
|
|||||||
|
|
||||||
paths = get_devices()[self.device]['paths']
|
paths = get_devices()[self.device]['paths']
|
||||||
|
|
||||||
logger.info(
|
logger.info('Starting injecting the mapping for %s', self.device)
|
||||||
'Starting injecting the mapping for %s on %s',
|
|
||||||
self.device,
|
|
||||||
', '.join(paths)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Watch over each one of the potentially multiple devices per hardware
|
# Watch over each one of the potentially multiple devices per hardware
|
||||||
for path in paths:
|
for path in paths:
|
||||||
@ -247,6 +264,9 @@ 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."""
|
||||||
@ -258,3 +278,5 @@ class KeycodeInjector:
|
|||||||
if process.is_alive():
|
if process.is_alive():
|
||||||
process.terminate()
|
process.terminate()
|
||||||
self.processes[i] = None
|
self.processes[i] = None
|
||||||
|
|
||||||
|
refresh_devices()
|
||||||
|
@ -50,21 +50,32 @@ 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.
|
||||||
"""
|
"""
|
||||||
paths = get_devices()[device]['paths']
|
groups = get_devices(include_keymapper=True)
|
||||||
|
for name, group in groups.items():
|
||||||
|
# also find stuff like "key-mapper {device}"
|
||||||
|
if device not in name:
|
||||||
|
return
|
||||||
|
|
||||||
logger.debug(
|
paths = group['paths']
|
||||||
'Starting reading keycodes for %s on %s',
|
|
||||||
device,
|
|
||||||
', '.join(paths)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Watch over each one of the potentially multiple devices per hardware
|
# Watch over each one of the potentially multiple devices per
|
||||||
|
# hardware
|
||||||
self.virtual_devices = [
|
self.virtual_devices = [
|
||||||
evdev.InputDevice(path)
|
evdev.InputDevice(path)
|
||||||
for path in paths
|
for path in paths
|
||||||
]
|
]
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
'Starting reading keycodes from "%s"',
|
||||||
|
'", "'.join([device.name for device in self.virtual_devices])
|
||||||
|
)
|
||||||
|
|
||||||
def read(self):
|
def read(self):
|
||||||
"""Get the newest keycode or None if none was pressed."""
|
"""Get the newest keycode or None if none was pressed."""
|
||||||
newest_keycode = None
|
newest_keycode = None
|
||||||
@ -74,12 +85,11 @@ class _KeycodeReader:
|
|||||||
if event is None:
|
if event is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if event.type == evdev.ecodes.EV_KEY and event.value == 1:
|
||||||
logger.spam(
|
logger.spam(
|
||||||
'got code:%s value:%s',
|
'got code:%s value:%s',
|
||||||
event.code + 8, event.value
|
event.code + 8, event.value
|
||||||
)
|
)
|
||||||
|
|
||||||
if event.type == evdev.ecodes.EV_KEY and event.value == 1:
|
|
||||||
# value: 1 for down, 0 for up, 2 for hold.
|
# value: 1 for down, 0 for up, 2 for hold.
|
||||||
# this happens to report key codes that are 8 lower
|
# this happens to report key codes that are 8 lower
|
||||||
# than the ones reported by evtest and used in xkb files
|
# than the ones reported by evtest and used in xkb files
|
||||||
|
@ -22,16 +22,18 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from keymapper.getdevices import _GetDevicesProcess
|
from keymapper.getdevices import _GetDevicesProcess
|
||||||
|
from keymapper.config import config
|
||||||
|
|
||||||
|
|
||||||
class TestGetDevices(unittest.TestCase):
|
|
||||||
def test_get_devices(self):
|
|
||||||
class FakePipe:
|
class FakePipe:
|
||||||
devices = None
|
devices = None
|
||||||
|
|
||||||
def send(self, devices):
|
def send(self, devices):
|
||||||
self.devices = devices
|
self.devices = devices
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetDevices(unittest.TestCase):
|
||||||
|
def test_get_devices(self):
|
||||||
# don't actually start the process, just use the `run` function.
|
# don't actually start the process, just use the `run` function.
|
||||||
# otherwise the coverage tool can't keep track.
|
# otherwise the coverage tool can't keep track.
|
||||||
pipe = FakePipe()
|
pipe = FakePipe()
|
||||||
@ -41,7 +43,31 @@ class TestGetDevices(unittest.TestCase):
|
|||||||
'paths': [
|
'paths': [
|
||||||
'/dev/input/event11',
|
'/dev/input/event11',
|
||||||
'/dev/input/event10',
|
'/dev/input/event10',
|
||||||
'/dev/input/event13'],
|
'/dev/input/event13'
|
||||||
|
],
|
||||||
|
'devices': [
|
||||||
|
'device 1 foo',
|
||||||
|
'device 1',
|
||||||
|
'device 1'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'device 2': {
|
||||||
|
'paths': ['/dev/input/event20'],
|
||||||
|
'devices': ['device 2']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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': [
|
'devices': [
|
||||||
'device 1 foo',
|
'device 1 foo',
|
||||||
'device 1',
|
'device 1',
|
||||||
|
Loading…
Reference in New Issue
Block a user