mirror of
https://github.com/sezanzeb/input-remapper
synced 2024-11-02 15:40:19 +00:00
wip
This commit is contained in:
parent
78692f40eb
commit
95b09259d3
@ -107,3 +107,5 @@ pylint keymapper --extension-pkg-whitelist=evdev
|
||||
sudo pip install . && coverage run tests/test.py
|
||||
coverage combine && coverage report -m
|
||||
```
|
||||
|
||||
To read events, `evtest` is very helpful.
|
||||
|
@ -32,8 +32,20 @@ from keymapper.logger import logger
|
||||
from keymapper.config import config
|
||||
|
||||
|
||||
# other events for ABS include buttons
|
||||
JOYSTICK = [
|
||||
evdev.ecodes.ABS_X,
|
||||
evdev.ecodes.ABS_Y,
|
||||
evdev.ecodes.ABS_Z,
|
||||
evdev.ecodes.ABS_RX,
|
||||
evdev.ecodes.ABS_RY,
|
||||
evdev.ecodes.ABS_RZ,
|
||||
]
|
||||
|
||||
|
||||
def _write(device, ev_type, keycode, value):
|
||||
"""Inject."""
|
||||
print('move mouse', ev_type, keycode, value)
|
||||
device.write(ev_type, keycode, value)
|
||||
device.syn()
|
||||
|
||||
|
@ -34,8 +34,9 @@ from evdev.ecodes import EV_KEY, EV_ABS
|
||||
from keymapper.logger import logger
|
||||
from keymapper.getdevices import get_devices
|
||||
from keymapper.state import system_mapping, KEYCODE_OFFSET
|
||||
from keymapper.dev.keycode_mapper import handle_keycode
|
||||
from keymapper.dev.ev_abs_mapper import ev_abs_mapper
|
||||
from keymapper.dev.keycode_mapper import handle_keycode, \
|
||||
should_map_event_as_btn
|
||||
from keymapper.dev.ev_abs_mapper import ev_abs_mapper, JOYSTICK
|
||||
from keymapper.dev.macros import parse
|
||||
|
||||
|
||||
@ -119,7 +120,7 @@ class KeycodeInjector:
|
||||
self._process = multiprocessing.Process(target=self._start_injecting)
|
||||
self._process.start()
|
||||
|
||||
def map_ev_to_abs(self, capabilities):
|
||||
def map_abs_to_rel(self, capabilities):
|
||||
"""Check if joystick movements can and should be mapped."""
|
||||
# mapping buttons only works without ABS events in the capabilities,
|
||||
# possibly due to some intentional constraints in the os. So always
|
||||
@ -141,16 +142,15 @@ class KeycodeInjector:
|
||||
capabilities = device.capabilities(absinfo=False)
|
||||
|
||||
needed = False
|
||||
if capabilities.get(EV_KEY) is not None:
|
||||
for (ev_type, keycode), _ in self.mapping:
|
||||
# TEST ev_type
|
||||
if keycode - KEYCODE_OFFSET in capabilities.get(ev_type, []):
|
||||
needed = True
|
||||
break
|
||||
for (ev_type, keycode), _ in self.mapping:
|
||||
# TODO test ev_type
|
||||
if keycode - KEYCODE_OFFSET in capabilities.get(ev_type, []):
|
||||
needed = True
|
||||
break
|
||||
|
||||
map_ev_abs = self.map_ev_to_abs(capabilities)
|
||||
abs_to_rel = self.map_abs_to_rel(capabilities)
|
||||
|
||||
if map_ev_abs:
|
||||
if abs_to_rel:
|
||||
needed = True
|
||||
|
||||
if not needed:
|
||||
@ -181,15 +181,15 @@ class KeycodeInjector:
|
||||
|
||||
time.sleep(0.15)
|
||||
|
||||
return device, map_ev_abs
|
||||
return device, abs_to_rel
|
||||
|
||||
def _modify_capabilities(self, input_device, map_ev_abs):
|
||||
def _modify_capabilities(self, input_device, abs_to_rel):
|
||||
"""Adds all keycode into a copy of a devices capabilities.
|
||||
|
||||
Prameters
|
||||
---------
|
||||
input_device : evdev.InputDevice
|
||||
map_ev_abs : bool
|
||||
abs_to_rel : bool
|
||||
if ABS capabilities should be removed in favor of REL
|
||||
"""
|
||||
ecodes = evdev.ecodes
|
||||
@ -207,7 +207,7 @@ class KeycodeInjector:
|
||||
if keycode is not None:
|
||||
capabilities[ev_type].append(keycode - KEYCODE_OFFSET)
|
||||
|
||||
if map_ev_abs:
|
||||
if abs_to_rel:
|
||||
del capabilities[ecodes.EV_ABS]
|
||||
capabilities[ecodes.EV_REL] = [
|
||||
evdev.ecodes.REL_X,
|
||||
@ -222,6 +222,8 @@ class KeycodeInjector:
|
||||
if ecodes.EV_FF in capabilities:
|
||||
del capabilities[ecodes.EV_FF]
|
||||
|
||||
print(capabilities)
|
||||
|
||||
return capabilities
|
||||
|
||||
async def _msg_listener(self, loop):
|
||||
@ -254,7 +256,7 @@ class KeycodeInjector:
|
||||
|
||||
# Watch over each one of the potentially multiple devices per hardware
|
||||
for path in paths:
|
||||
input_device, map_ev_abs = self._prepare_device(path)
|
||||
input_device, abs_to_rel = self._prepare_device(path)
|
||||
if input_device is None:
|
||||
continue
|
||||
|
||||
@ -264,15 +266,15 @@ class KeycodeInjector:
|
||||
uinput = evdev.UInput(
|
||||
name=f'{DEV_NAME} {self.device}',
|
||||
phys=DEV_NAME,
|
||||
events=self._modify_capabilities(input_device, map_ev_abs)
|
||||
events=self._modify_capabilities(input_device, abs_to_rel)
|
||||
)
|
||||
|
||||
# keycode injection
|
||||
coroutine = self._keycode_loop(input_device, uinput, map_ev_abs)
|
||||
coroutine = self._keycode_loop(input_device, uinput, abs_to_rel)
|
||||
coroutines.append(coroutine)
|
||||
|
||||
# mouse movement injection
|
||||
if map_ev_abs:
|
||||
if abs_to_rel:
|
||||
self.abs_state[0] = 0
|
||||
self.abs_state[1] = 0
|
||||
coroutine = ev_abs_mapper(
|
||||
@ -307,7 +309,7 @@ class KeycodeInjector:
|
||||
uinput.write(EV_KEY, keycode - KEYCODE_OFFSET, value)
|
||||
uinput.syn()
|
||||
|
||||
async def _keycode_loop(self, device, uinput, map_ev_abs):
|
||||
async def _keycode_loop(self, device, uinput, abs_to_rel):
|
||||
"""Inject keycodes for one of the virtual devices.
|
||||
|
||||
Can be stopped by stopping the asyncio loop.
|
||||
@ -318,7 +320,7 @@ class KeycodeInjector:
|
||||
where to read keycodes from
|
||||
uinput : evdev.UInput
|
||||
where to write keycodes to
|
||||
map_ev_abs : bool
|
||||
abs_to_rel : bool
|
||||
if joystick events should be mapped to mouse movements
|
||||
"""
|
||||
# efficiently figure out the target keycode without taking
|
||||
@ -352,7 +354,7 @@ class KeycodeInjector:
|
||||
)
|
||||
|
||||
async for event in device.async_read_loop():
|
||||
if map_ev_abs and event.type == EV_ABS:
|
||||
if abs_to_rel and event.type == EV_ABS and event.code in JOYSTICK:
|
||||
if event.code not in [evdev.ecodes.ABS_X, evdev.ecodes.ABS_Y]:
|
||||
continue
|
||||
if event.code == evdev.ecodes.ABS_X:
|
||||
@ -361,16 +363,14 @@ class KeycodeInjector:
|
||||
self.abs_state[1] = event.value
|
||||
continue
|
||||
|
||||
if event.type != EV_KEY:
|
||||
uinput.write(event.type, event.code, event.value)
|
||||
# this already includes SYN events, so need to syn here again
|
||||
if should_map_event_as_btn(event):
|
||||
handle_keycode(code_code_mapping, macros, event, uinput)
|
||||
continue
|
||||
|
||||
if event.value == 2:
|
||||
# linux does them itself, no need to trigger them
|
||||
continue
|
||||
|
||||
handle_keycode(code_code_mapping, macros, event, uinput)
|
||||
# forward the rest
|
||||
uinput.write(event.type, event.code, event.value)
|
||||
# this already includes SYN events, so need to syn here again
|
||||
continue
|
||||
|
||||
# this should only ever happen in tests to avoid blocking them
|
||||
# forever, as soon as all events are consumed. In normal operation
|
||||
|
@ -24,12 +24,35 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
import evdev
|
||||
|
||||
from keymapper.logger import logger
|
||||
from keymapper.state import KEYCODE_OFFSET
|
||||
|
||||
|
||||
def should_map_event_as_btn(event):
|
||||
"""Does this event describe a button.
|
||||
|
||||
Especially important for gamepad events, some of the buttons
|
||||
require special rules.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
event : evdev.events.InputEvent
|
||||
"""
|
||||
# TODO test
|
||||
if event.type == evdev.events.EV_KEY:
|
||||
return True
|
||||
|
||||
if event.type == evdev.events.EV_ABS and event.code > 5:
|
||||
# 1 - 5 seem to be joystick events
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def handle_keycode(code_code_mapping, macros, event, uinput):
|
||||
"""Write the mapped keycode.
|
||||
"""Write the mapped keycode or forward unmapped ones.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@ -39,6 +62,10 @@ def handle_keycode(code_code_mapping, macros, event, uinput):
|
||||
macros : dict
|
||||
mapping of linux-keycode to _Macro objects
|
||||
"""
|
||||
if event.value == 2:
|
||||
# button-hold event
|
||||
return
|
||||
|
||||
input_keycode = event.code
|
||||
|
||||
# for logging purposes. It should log the same keycode as xev and gtk,
|
||||
@ -63,9 +90,10 @@ def handle_keycode(code_code_mapping, macros, event, uinput):
|
||||
if input_keycode in code_code_mapping:
|
||||
target_keycode = code_code_mapping[input_keycode]
|
||||
logger.spam(
|
||||
'got code:%s value:%s, maps to code:%s',
|
||||
'got code:%s value:%s event:%s, maps to code:%s',
|
||||
xkb_keycode,
|
||||
event.value,
|
||||
evdev.ecodes.EV[event.type],
|
||||
target_keycode + KEYCODE_OFFSET
|
||||
)
|
||||
else:
|
||||
@ -76,5 +104,6 @@ def handle_keycode(code_code_mapping, macros, event, uinput):
|
||||
)
|
||||
target_keycode = input_keycode
|
||||
|
||||
print('write', event.type, target_keycode, event.value)
|
||||
uinput.write(event.type, target_keycode, event.value)
|
||||
uinput.syn()
|
||||
|
@ -31,6 +31,7 @@ import evdev
|
||||
from keymapper.logger import logger
|
||||
from keymapper.getdevices import get_devices, refresh_devices
|
||||
from keymapper.state import KEYCODE_OFFSET
|
||||
from keymapper.dev.keycode_mapper import should_map_event_as_btn
|
||||
|
||||
|
||||
CLOSE = 1
|
||||
@ -108,10 +109,14 @@ class _KeycodeReader:
|
||||
logger.debug('Pipe closed, reader stops.')
|
||||
sys.exit(0)
|
||||
|
||||
if event.type == evdev.ecodes.EV_KEY and event.value == 1:
|
||||
# TODO write a test to map event `type 3 (EV_ABS), code 16
|
||||
# (ABS_HAT0X), value 0` to a button
|
||||
if should_map_event_as_btn(event):
|
||||
logger.spam(
|
||||
'got code:%s value:%s',
|
||||
event.code + KEYCODE_OFFSET, event.value
|
||||
'got code:%s value:%s type:%s',
|
||||
event.code + KEYCODE_OFFSET,
|
||||
event.value,
|
||||
evdev.ecodes.EV[event.type]
|
||||
)
|
||||
self._pipe[1].send((event.type, event.code + KEYCODE_OFFSET))
|
||||
|
||||
@ -148,7 +153,7 @@ class _KeycodeReader:
|
||||
"""Get the newest tuple of event type, keycode or None."""
|
||||
if self._pipe is None:
|
||||
logger.debug('No pipe available to read from')
|
||||
return None
|
||||
return (None, None)
|
||||
|
||||
newest_event = (None, None)
|
||||
while self._pipe[0].poll():
|
||||
|
@ -76,7 +76,9 @@ class Row(Gtk.ListBoxRow):
|
||||
"""Check if a keycode has been pressed and if so, display it."""
|
||||
# the newest_keycode is populated since the ui regularly polls it
|
||||
# in order to display it in the status bar.
|
||||
previous_keycode = self.get_keycode()
|
||||
key = self.get_keycode()
|
||||
previous_keycode = key[1] if key else None
|
||||
|
||||
character = self.get_character()
|
||||
|
||||
# no input
|
||||
@ -158,7 +160,7 @@ class Row(Gtk.ListBoxRow):
|
||||
keycode_input.set_size_request(50, -1)
|
||||
|
||||
if keycode is not None:
|
||||
keycode_input.set_label(f'{ev_type},{keycode})')
|
||||
keycode_input.set_label(f'{ev_type},{keycode}')
|
||||
|
||||
# make the togglebutton go back to its normal state when doing
|
||||
# something else in the UI
|
||||
|
@ -87,7 +87,7 @@ class TestInjector(unittest.TestCase):
|
||||
fake_device = FakeDevice()
|
||||
capabilities = self.injector._modify_capabilities(
|
||||
fake_device,
|
||||
map_ev_abs=False
|
||||
abs_to_rel=False
|
||||
)
|
||||
|
||||
self.assertIn(EV_KEY, capabilities)
|
||||
@ -105,8 +105,8 @@ class TestInjector(unittest.TestCase):
|
||||
path = '/dev/input/event10'
|
||||
# this test needs to pass around all other constraints of
|
||||
# _prepare_device
|
||||
device, map_ev_abs = self.injector._prepare_device(path)
|
||||
self.assertFalse(map_ev_abs)
|
||||
device, abs_to_rel = self.injector._prepare_device(path)
|
||||
self.assertFalse(abs_to_rel)
|
||||
self.assertEqual(self.failed, 2)
|
||||
# success on the third try
|
||||
device.name = fixtures[path]['name']
|
||||
@ -115,10 +115,10 @@ class TestInjector(unittest.TestCase):
|
||||
self.injector = KeycodeInjector('gamepad', custom_mapping)
|
||||
|
||||
path = '/dev/input/event30'
|
||||
device, map_ev_abs = self.injector._prepare_device(path)
|
||||
self.assertTrue(map_ev_abs)
|
||||
device, abs_to_rel = self.injector._prepare_device(path)
|
||||
self.assertTrue(abs_to_rel)
|
||||
|
||||
capabilities = self.injector._modify_capabilities(device, map_ev_abs)
|
||||
capabilities = self.injector._modify_capabilities(device, abs_to_rel)
|
||||
self.assertNotIn(evdev.ecodes.EV_ABS, capabilities)
|
||||
self.assertIn(evdev.ecodes.EV_REL, capabilities)
|
||||
|
||||
@ -127,8 +127,8 @@ class TestInjector(unittest.TestCase):
|
||||
custom_mapping.change(EV_KEY, 10, 'a')
|
||||
self.injector = KeycodeInjector('device 1', custom_mapping)
|
||||
path = '/dev/input/event11'
|
||||
device, map_ev_abs = self.injector._prepare_device(path)
|
||||
self.assertFalse(map_ev_abs)
|
||||
device, abs_to_rel = self.injector._prepare_device(path)
|
||||
self.assertFalse(abs_to_rel)
|
||||
self.assertEqual(self.failed, 0)
|
||||
self.assertIsNone(device)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user