diff --git a/README.md b/README.md index 3cf4e5297..c23749ae2 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,12 @@ input-remapper is now part of [Debian Unstable](https://packages.debian.org/sid/ ##### pip -Dependencies from your distros repo: `python3-evdev`, `gtksourceview4`, `python3-devel`, `python3-pydantic` +Dependencies: `python3-evdev` ≥1.3.0, `gtksourceview4`, `python3-devel`, `python3-pydantic` ```bash +sudo pip install evdev -U # If newest version not in distros repo sudo pip uninstall key-mapper -sudo pip install --no-binary :all: git+https://github.com/sezanzeb/input-remapper.git +sudo pip install --no-binary :all: git+https://github.com/sezanzeb/input-remapper.git # no --user sudo systemctl enable input-remapper sudo systemctl restart input-remapper ``` diff --git a/inputremapper/gui/user_interface.py b/inputremapper/gui/user_interface.py index 839fc1127..75e3bcc15 100644 --- a/inputremapper/gui/user_interface.py +++ b/inputremapper/gui/user_interface.py @@ -58,7 +58,7 @@ from inputremapper.event_combination import EventCombination from inputremapper.gui.reader import reader from inputremapper.gui.helper import is_helper_running -from inputremapper.injection.injector import RUNNING, FAILED, NO_GRAB +from inputremapper.injection.injector import RUNNING, FAILED, NO_GRAB, UPGRADE_EVDEV from inputremapper.daemon import Daemon from inputremapper.configs.global_config import global_config from inputremapper.injection.macros.parse import is_this_a_macro, parse @@ -629,6 +629,14 @@ def show_injection_result(self): ) return False + if state == UPGRADE_EVDEV: + self.show_status( + CTX_ERROR, + "Upgrade python-evdev", + "Your python-evdev version is too old.", + ) + return False + # keep the timeout running until a relevant state is found return True diff --git a/inputremapper/injection/injector.py b/inputremapper/injection/injector.py index 458bf2f53..1486791ec 100644 --- a/inputremapper/injection/injector.py +++ b/inputremapper/injection/injector.py @@ -22,6 +22,7 @@ """Keeps injecting keycodes in the background based on the preset.""" import os +import sys import asyncio import time import multiprocessing @@ -47,7 +48,7 @@ # messages CLOSE = 0 -OK = 1 +UPGRADE_EVDEV = 7 # states UNKNOWN = -1 @@ -80,18 +81,6 @@ def get_udev_name(name: str, suffix: str) -> str: return name -def create_uinput(*args, **kwargs): - """Safely create an UInput, compatible with various versions of evdev.""" - try: - return evdev.UInput(*args, **kwargs) - except TypeError as e: - if "input_props" in str(e): - del kwargs["input_props"] - return evdev.UInput(*args, **kwargs) - - raise e - - class Injector(multiprocessing.Process): """Initializes, starts and stops injections. @@ -139,27 +128,26 @@ def get_state(self) -> int: Can be safely called from the main process. """ - # slowly figure out what is going on + # figure out what is going on step by step alive = self.is_alive() if self._state == UNKNOWN and not alive: - # didn't start yet + # `self.start()` has not been called yet return self._state - # if it is alive, it is definitely at least starting up if self._state == UNKNOWN and alive: + # if it is alive, it is definitely at least starting up. self._state = STARTING - # if there is a message available, it might have finished starting up if self._state == STARTING and self._msg_pipe[1].poll(): + # if there is a message available, it might have finished starting up + # and the injector has the real status for us msg = self._msg_pipe[1].recv() - if msg == OK: - self._state = RUNNING - - if msg == NO_GRAB: - self._state = NO_GRAB + self._state = msg if self._state in [STARTING, RUNNING] and not alive: + # we thought it is running (maybe it was when get_state was previously), + # but the process is not alive. It probably crashed self._state = FAILED logger.error("Injector was unexpectedly found stopped") @@ -340,19 +328,29 @@ def run(self) -> None: # copy as much information as possible, because libinput uses the extra # information to enable certain features like "Disable touchpad while # typing" - forward_to = create_uinput( - name=get_udev_name(source.name, "forwarded"), - events=self._copy_capabilities(source), - # phys=source.phys, # this leads to confusion. the appearance of an uinput with this "phys" property - # causes the udev rule to autoload for the original device, overwriting our previous attempts at - # starting an injection. - vendor=source.info.vendor, - product=source.info.product, - version=source.info.version, - bustype=source.info.bustype, - # input_props has been missing in one case - input_props=getattr(source, "input_props", lambda: None)(), - ) + try: + forward_to = evdev.UInput( + name=get_udev_name(source.name, "forwarded"), + events=self._copy_capabilities(source), + # phys=source.phys, # this leads to confusion. the appearance of + # an uinput with this "phys" property causes the udev rule to + # autoload for the original device, overwriting our previous + # attempts at starting an injection. + vendor=source.info.vendor, + product=source.info.product, + version=source.info.version, + bustype=source.info.bustype, + input_props=source.input_props(), + ) + except TypeError as e: + if "input_props" in str(e): + # UInput constructor doesn't support input_props and + # source.input_props doesn't exist with old python-evdev versions. + logger.error("Please upgrade your python-evdev version. Exiting") + self._msg_pipe[0].send(UPGRADE_EVDEV) + sys.exit(12) + + raise e # actually doing things consumer_control = ConsumerControl(self.context, source, forward_to) @@ -365,7 +363,7 @@ def run(self) -> None: # grabbing devices screws this up set_numlock(numlock_state) - self._msg_pipe[0].send(OK) + self._msg_pipe[0].send(RUNNING) try: loop.run_until_complete(asyncio.gather(*coroutines)) diff --git a/tests/unit/test_injector.py b/tests/unit/test_injector.py index 93098cabd..1f960cbed 100644 --- a/tests/unit/test_injector.py +++ b/tests/unit/test_injector.py @@ -68,7 +68,6 @@ NO_GRAB, UNKNOWN, get_udev_name, - create_uinput, ) from inputremapper.injection.numlock import is_numlock_on from inputremapper.configs.system_mapping import ( @@ -129,27 +128,6 @@ def find_joystick_to_mouse(self): if isinstance(consumer, JoystickToMouse) ][0] - def test_create_uinput(self): - # can create an uinput with an input_props argument, - # which is ignored if it fails - def patch( - events=None, - name="py-evdev-uinput", - vendor=0x1, - product=0x1, - version=0x1, - bustype=0x3, - devnode="/dev/uinput", - phys="py-evdev-uinput", - ): - # act like some outdated python-evdev version or something that doesn't - # support input_props - pass - - with mock.patch.object(evdev, "UInput", patch): - # should not raise an error - create_uinput(input_props=[]) - def test_grab(self): # path is from the fixtures path = "/dev/input/event10"