Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Instructing python-evdev upgrade #346

Merged
merged 11 commits into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down
10 changes: 9 additions & 1 deletion inputremapper/gui/user_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
70 changes: 34 additions & 36 deletions inputremapper/injection/injector.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"""Keeps injecting keycodes in the background based on the preset."""

import os
import sys
import asyncio
import time
import multiprocessing
Expand All @@ -47,7 +48,7 @@

# messages
CLOSE = 0
OK = 1
UPGRADE_EVDEV = 7

# states
UNKNOWN = -1
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -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)
sezanzeb marked this conversation as resolved.
Show resolved Hide resolved
sys.exit(12)

raise e

# actually doing things
consumer_control = ConsumerControl(self.context, source, forward_to)
Expand All @@ -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)
sezanzeb marked this conversation as resolved.
Show resolved Hide resolved

try:
loop.run_until_complete(asyncio.gather(*coroutines))
Expand Down
22 changes: 0 additions & 22 deletions tests/unit/test_injector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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"
Expand Down