Merge main into beta (#421)

pull/422/head
jonasBoss 2 years ago committed by GitHub
parent f955290b04
commit 88e4f0e5ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -9,16 +9,18 @@ assignees: ''
Please install the newest version from source to see if the problem has already been solved.
Share some logs please:
**System Information and logs**
1. `input-remapper-control --version`
2. which linux distro (ubuntu 20.04, manjaro, etc.)
3. `echo $XDG_SESSION_TYPE`
4. which desktop environment (gnome, plasma, xfce4, etc.)
5. `sudo ls -l /proc/1/exe`
3. which desktop environment (gnome, plasma, xfce4, etc.)
4. `sudo ls -l /proc/1/exe` to check if you are using systemd
5. `cat ~/.config/input-remapper/config.json` to see if the "autoload" config is written correctly
6. `systemctl status input-remapper -n 50` the service has to be running
6. `cat ~/.config/input-remapper/config.json`
7. `input-remapper-control --command hello`
8. `systemctl status input-remapper -n 50`
9. `sudo pkill -f input-remapper-service && sudo input-remapper-service -d & sleep 2 && input-remapper-control --command autoload`, are your keys mapped now?
10. (while the previous command is still running) `sudo evtest` and search for a device suffixed by "mapped". Select it, does it report any events? Share the output.
**Testing the setup**
1. `input-remapper-control --command hello`
2. `sudo pkill -f input-remapper-service && sudo input-remapper-service -d & sleep 2 && input-remapper-control --command autoload`, are your keys mapped now?
3. (while the previous command is still running) `sudo evtest` and search for a device suffixed by "mapped". Select it, does it report any events? Share the output.
4. `sudo udevadm control --log-priority=debug && sudo udevadm control --reload-rules && journalctl -f | grep input-remapper`, now plug in the device that should autoload

@ -1,5 +1,5 @@
Package: input-remapper
Version: 1.5.0-beta
Version: 1.6.0-beta
Architecture: all
Maintainer: Sezanzeb <proxima@sezanzeb.de>
Depends: build-essential, libpython3-dev, libdbus-1-dev, python3, python3-setuptools, python3-evdev, python3-pydbus, python3-gi, gettext, python3-cairo, libgtk-3-0, libgtksourceview-4-dev, python3-pydantic

@ -38,7 +38,7 @@ or install the latest changes via:
sudo apt install git python3-setuptools gettext
git clone https://github.com/sezanzeb/input-remapper.git
cd input-remapper && ./scripts/build.sh
sudo apt install ./dist/input-remapper-1.5.0-beta.deb
sudo apt install ./dist/input-remapper-1.6.0-beta.deb
```
input-remapper is now part of [Debian Unstable](https://packages.debian.org/sid/input-remapper)

@ -27,7 +27,7 @@ import argparse
import logging
import subprocess
from inputremapper.logger import logger, update_verbosity, log_info, add_filehandler
from inputremapper.logger import logger, update_verbosity, log_info
from inputremapper.configs.migrations import migrate
from inputremapper.configs.global_config import global_config
@ -223,8 +223,6 @@ def main(options):
if options.debug:
update_verbosity(True)
add_filehandler('/var/log/input-remapper-control')
if options.version:
log_info()
return

@ -25,7 +25,7 @@
import sys
from argparse import ArgumentParser
from inputremapper.logger import update_verbosity, log_info, add_filehandler
from inputremapper.logger import update_verbosity, log_info
if __name__ == '__main__':
@ -46,7 +46,6 @@ if __name__ == '__main__':
# import input-remapper stuff after setting the log verbosity
from inputremapper.daemon import Daemon
add_filehandler()
if not options.hide_info:
log_info('input-remapper-service')

@ -7,4 +7,4 @@
# journalctl -f
# to get available variables:
# udevadm monitor --environment --udev --subsystem input
ACTION=="add", SUBSYSTEM=="input", RUN+="/bin/input-remapper-control --command autoload --device $env{DEVNAME}"
ACTION=="add", SUBSYSTEM=="input", ENV{ID_PATH}!="platform-sound", RUN+="/bin/input-remapper-control --command autoload --device $env{DEVNAME}"

@ -3,7 +3,6 @@ Description=Service to inject keycodes without the GUI application
# dbus is required for ipc between gui and input-remapper-control
Requires=dbus.service
After=dbus.service
RequiresMountsFor=/var/log
[Service]
Type=dbus

@ -49,7 +49,7 @@ from inputremapper.configs.mapping import Mapping, UIMapping
from inputremapper.event_combination import EventCombination
from inputremapper.logger import logger, VERSION, IS_BETA
from inputremapper.user import HOME
from inputremapper.configs.paths import get_preset_path, mkdir, CONFIG_PATH
from inputremapper.configs.paths import get_preset_path, mkdir, CONFIG_PATH, remove
from inputremapper.configs.system_mapping import system_mapping
from inputremapper.injection.global_uinputs import global_uinputs
from inputremapper.injection.macros.parse import is_this_a_macro
@ -169,7 +169,7 @@ def _rename_config(new_path=CONFIG_PATH):
def _find_target(symbol):
"""Try to find a uinput with the required capabilities for the symbol."""
"""try to find a uinput with the required capabilities for the symbol."""
capabilities = {EV_KEY: set(), EV_REL: set()}
if is_this_a_macro(symbol):
@ -191,7 +191,7 @@ def _find_target(symbol):
def _add_target():
"""Add the target field to each preset mapping."""
"""add the target field to each preset mapping"""
for preset, preset_dict in all_presets():
if "mapping" not in preset_dict.keys():
continue
@ -397,6 +397,18 @@ def _copy_to_beta():
shutil.copytree(regular_path, CONFIG_PATH)
def _remove_logs():
"""We will try to rely on journalctl for this in the future."""
try:
remove(f"{HOME}/.log/input-remapper")
remove("/var/log/input-remapper")
remove("/var/log/input-remapper-control")
except Exception as error:
logger.debug("Failed to remove deprecated logfiles: %s", str(error))
# this migration is not important. Continue
pass
def migrate():
"""Migrate config files to the current release."""
@ -419,7 +431,10 @@ def migrate():
if v < pkg_resources.parse_version("1.4.1"):
_otherwise_to_else()
if v < pkg_resources.parse_version("1.5.0-beta"):
if v < pkg_resources.parse_version("1.5.0"):
_remove_logs()
if v < pkg_resources.parse_version("1.6.0-beta"):
_convert_to_individual_mappings()
# add new migrations here

@ -44,6 +44,15 @@ DEFAULT_UINPUTS = {
evdev.ecodes.EV_REL: [*range(0x00, 0x0D)], # all REL axis
},
}
DEFAULT_UINPUTS["keyboard + mouse"] = {
evdev.ecodes.EV_KEY: [
*DEFAULT_UINPUTS["keyboard"][evdev.ecodes.EV_KEY],
*DEFAULT_UINPUTS["mouse"][evdev.ecodes.EV_KEY],
],
evdev.ecodes.EV_REL: [
*DEFAULT_UINPUTS["mouse"][evdev.ecodes.EV_REL],
],
}
class UInput(evdev.UInput):

@ -116,12 +116,6 @@ class Logger(logging.Logger):
self._log(logging.DEBUG, msg, args=None)
LOG_PATH = (
"/var/log/input-remapper"
if os.access("/var/log", os.W_OK)
else f"{HOME}/.log/input-remapper"
)
# https://github.com/python/typeshed/issues/1801
logging.setLoggerClass(Logger)
logger = cast(Logger, logging.getLogger("input-remapper"))
@ -254,7 +248,7 @@ logger.setLevel(logging.INFO)
logging.getLogger("asyncio").setLevel(logging.WARNING)
VERSION = "1.5.0-beta"
VERSION = "1.6.0-beta"
EVDEV_VERSION = None
try:
EVDEV_VERSION = pkg_resources.require("evdev")[0].version
@ -340,24 +334,3 @@ def trim_logfile(log_path):
raise
except Exception as e:
logger.error('Failed to trim logfile: "%s"', str(e))
def add_filehandler(log_path=LOG_PATH):
"""Clear the existing logfile and start logging to it."""
try:
log_path = os.path.expanduser(log_path)
os.makedirs(os.path.dirname(log_path), exist_ok=True)
if os.path.isdir(log_path):
# used to be a folder < 0.8.0
shutil.rmtree(log_path)
trim_logfile(log_path)
file_handler = logging.FileHandler(log_path)
file_handler.setFormatter(ColorfulFormatter())
logger.addHandler(file_handler)
logger.info('Starting logging to "%s"', log_path)
except PermissionError:
logger.debug('No permission to log to "%s"', log_path)

@ -9,44 +9,10 @@ All pull requests will at some point require unittests (see below for more
info), the code coverage may only be improved, not decreased. It also has to
be mostly compliant with pylint.
## Roadmap
- [x] show a dropdown to select valid devices
- [x] creating presets per device
- [x] renaming presets
- [x] show a mapping table
- [x] make that list extend itself automatically
- [x] read keycodes with evdev
- [x] inject the mapping
- [x] keep the system defaults for unmapped buttons
- [x] button to stop mapping and using system defaults
- [x] highlight changes and alert before discarding unsaved changes
- [x] automatically load presets on login for plugged in devices
- [x] make sure it works on wayland
- [x] support timed macros, maybe using some sort of syntax
- [x] add to the AUR, provide .deb file
- [x] basic support for gamepads as keyboard and mouse combi
- [x] executing a macro forever while holding down the key using `h`
- [x] mapping D-Pad directions as buttons
- [x] configure joystick purpose and speed via the GUI
- [x] support for non-GUI TTY environments with a command to stop and start
- [x] start the daemon in such a way to not require usermod
- [x] mapping a combined button press to a key
- [x] add "disable" as mapping option
- [x] mapping joystick directions as buttons, making it act like a D-Pad
- [x] mapping mouse wheel events to buttons
- [x] automatically load presets when devices get plugged in after login (udev)
- [x] map keys using a `modifier + modifier + ... + key` syntax
- [x] inject in an additional device instead to avoid clashing capabilities
- [x] don't run any GUI code as root for improved wayland compatibility
- [x] advanced multiline editor
- [ ] plugin support
- [x] getting it into the official debian repo
## Tests
```bash
sudo pip install coverage
pip install coverage --user
pylint inputremapper --extension-pkg-whitelist=evdev
sudo pkill -f input-remapper
sudo pip install . && coverage run tests/test.py
@ -88,12 +54,15 @@ ssh/login into a debian/ubuntu environment
./scripts/build.sh
```
This will generate `input-remapper/deb/input-remapper-1.5.0-beta.deb`
This will generate `input-remapper/deb/input-remapper-1.6.0-beta.deb`
## Badges
```bash
sudo pip install git+https://github.com/jongracecox/anybadge
sudo pip install anybadge pylint
sudo pkill -f input-remapper
sudo pip install .
# the source path in .coveragerc might be incorrect for your system
./scripts/badges.sh
```
@ -176,99 +145,6 @@ devices. Communicates via pipes. It should not exceed the lifetime of
the user interface because it exposes all the input events. Starts via
pkexec.
## Unsupported Devices
Either open up an issue or debug it yourself and make a pull request.
You will need to work with the devices capabilities. You can get those using
```
sudo evtest
```
**It tries or doesn't try to map ABS_X/ABS_Y**
Is the device a gamepad? Does the GUI show joystick configurations?
- if yes, no: adjust `is_gamepad` to loosen up the constraints
- if no, yes: adjust `is_gamepad` to tighten up the constraints
Try to do it in such a way that other devices won't break. Also see
readme/capabilities.md
**It won't offer mapping a button**
If `sudo evtest` shows an event for the button, try to
modify `should_map_as_btn`. If not, the button cannot be mapped.
## How it works
It uses evdev. The links below point to the 1.0.0 release, line numbers might have changed in the current main.
1. It grabs a device (e.g. /dev/input/event3), so that the key events won't
reach X11/Wayland anymore
[source](https://github.com/sezanzeb/input-remapper/blob/1.0.0/inputremapper/injection/injector.py#L197)
2. Reads the events from it (`evtest` can do it, you can also do
`cat /dev/input/event3` which yields binary stuff)
[source](https://github.com/sezanzeb/input-remapper/blob/1.0.0/inputremapper/injection/injector.py#L443)
3. Looks up the mapping if that event maps to anything
[source](https://github.com/sezanzeb/input-remapper/blob/1.0.0/inputremapper/injection/keycode_mapper.py#L434)
4. Injects the output event in a new device that input-remapper created (another
new path in /dev/input, device name is suffixed by "mapped")
[source](https://github.com/sezanzeb/input-remapper/blob/1.0.0/inputremapper/injection/keycode_mapper.py#L242),
[new device](https://github.com/sezanzeb/input-remapper/blob/1.0.0/inputremapper/injection/injector.py#L356)
5. Forwards any events that should not be mapped to anything in another new
device (device name is suffixed by "forwarded")
[source](https://github.com/sezanzeb/input-remapper/blob/1.0.0/inputremapper/injection/keycode_mapper.py#L247),
[new device](https://github.com/sezanzeb/input-remapper/blob/1.0.0/inputremapper/injection/injector.py#L367)
This stuff is going on as a daemon in the background
## How combinations are injected
Here is an example how combinations are injected:
```
a -> x
a + b -> y
```
1. the `a` button is pressed with your finger, `a 1` arrives via evdev in input-remapper
2. input-remapper maps it to `x 1` and injects it
3. `b` is pressed with your finger, `b 1` arrives via evdev in input-remapper
4. input-remapper sees a triggered combination and maps it to `y 1` and injects it
5. `b` is released, `b 0` arrives at input-remapper
6. input-remapper remembered that it was the trigger for a combination and maps that release to `y 0` and injects it
7. the `a` button is released, `a 0` arrives at input-remapper
8. input-remapper maps that release to `x 0` and injects it
## Multiple sources, single UInput
https://github.com/sezanzeb/input-remapper/blob/1.0.0/inputremapper/injection/injector.py
This "Injector" process is the only process that injects if input-remapper is used for a single device.
Inside `run` of that process there is an iteration of `for source in sources:`,
which runs an event loop for each possible source for events.
Each event loop has convenient access to the "context" to read some globals.
Consider this typical example of device capabilities:
- "BrandXY Mouse" -> EV_REL, BTN_LEFT, ...
- "BrandXY Mouse" -> KEY_1, KEY_2
There are two devices called "BrandXY Mouse", and they report different events.
Input-remapper creates a single uinput to inject all mapped events to. For example
- BTN_LEFT -> a
- KEY_2 -> b
so you end up with a new device with the following capabilities
"input-remapper BrandXY Mouse mapped" -> KEY_A, KEY_B
while input-remapper reads from multiple InputDevices it injects the mapped letters into a single UInput.
## Resources
- [Guidelines for device capabilities](https://www.kernel.org/doc/Documentation/input/event-codes.txt)

@ -55,9 +55,9 @@ if_tap(
For regular combinations on only single devices it is not required to
configure macros. See [readme/usage.md](usage.md#combinations).
**Keyboard** `space` `set(foo, 1).hold(space).set(foo, 0)`
**Keyboard** `space` `set(foo, 1).hold_keys(space).set(foo, 0)`
**Mouse** `middle` `if_eq($foo, 1, hold(a), hold(BTN_MIDDLE))`
**Mouse** `middle` `if_eq($foo, 1, hold_keys(a), hold_keys(BTN_MIDDLE))`
Apply both presets. If you press space on your keyboard, it will write a
space exactly like it used to. If you hold down space and press the middle
@ -66,7 +66,7 @@ middle button of your mouse it behaves like a regular middle mouse button.
**Explanation**
`hold(space)` makes your key work exactly like if it was mapped to "space".
`hold_keys(space)` makes your key work exactly like if it was mapped to "space".
It will inject a key-down event if you press it, does nothing as long you
hold your key down, and injects a key-up event after releasing.
`set(foo, 1).set(foo, 0)` sets "foo" to 1 and then sets "foo" to 0.

@ -1,23 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="73" height="20">
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<mask id="anybadge_1">
<rect width="73" height="20" rx="3" fill="#fff"/>
<rect width="80" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#anybadge_1)">
<path fill="#555" d="M0 0h44v20H0z"/>
<path fill="#4c1" d="M44 0h29v20H44z"/>
<path fill="url(#b)" d="M0 0h73v20H0z"/>
<path fill="#4c1" d="M44 0h36v20H44z"/>
<path fill="url(#b)" d="M0 0h80v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="23.0" y="15" fill="#010101" fill-opacity=".3">pylint</text>
<text x="22.0" y="14">pylint</text>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="59.5" y="15" fill="#010101" fill-opacity=".3">9.3</text>
<text x="58.5" y="14">9.3</text>
<text x="63.0" y="15" fill="#010101" fill-opacity=".3">9.18</text>
<text x="62.0" y="14">9.18</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -6,6 +6,7 @@ coverage_badge() {
coverage run tests/test.py
coverage combine
rating=$(coverage report | tail -n 1 | ack "\d+%" -o | ack "\d+" -o)
echo "coverage rating: $rating"
rm readme/coverage.svg
anybadge -l coverage -v $rating -f readme/coverage.svg coverage
@ -19,7 +20,7 @@ pylint_badge() {
rm readme/pylint.svg
anybadge -l pylint -v $rating -f readme/pylint.svg pylint
echo $rating
echo "pylint rating: $rating"
echo "pylint badge created"
}

@ -9,8 +9,8 @@ build_deb() {
mv build/deb/usr/local/lib/python3.*/ build/deb/usr/lib/python3/
cp ./DEBIAN build/deb/ -r
mkdir dist -p
rm dist/input-remapper-1.5.0-beta.deb || true
dpkg -b build/deb dist/input-remapper-1.5.0-beta.deb
rm dist/input-remapper-1.6.0-beta.deb || true
dpkg -b build/deb dist/input-remapper-1.6.0-beta.deb
}
build_deb &

@ -102,7 +102,7 @@ for po_file in glob.glob(PO_FILES):
setup(
name="input-remapper",
version="1.5.0-beta",
version="1.6.0-beta",
description="A tool to change the mapping of your input device buttons",
author="Sezanzeb",
author_email="proxima@sezanzeb.de",

@ -26,10 +26,20 @@ import shutil
import unittest
import logging
from inputremapper.logger import logger, add_filehandler, update_verbosity, log_info
from inputremapper.logger import logger, update_verbosity, log_info, ColorfulFormatter
from inputremapper.configs.paths import remove
def add_filehandler(log_path):
"""Start logging to a file."""
log_path = os.path.expanduser(log_path)
os.makedirs(os.path.dirname(log_path), exist_ok=True)
file_handler = logging.FileHandler(log_path)
file_handler.setFormatter(ColorfulFormatter())
logger.addHandler(file_handler)
logger.info('Starting logging to "%s"', log_path)
class TestLogger(unittest.TestCase):
def tearDown(self):
update_verbosity(debug=True)
@ -77,22 +87,6 @@ class TestLogger(unittest.TestCase):
add_filehandler(new_path)
self.assertTrue(os.path.exists(new_path))
def test_clears_log(self):
path = os.path.join(tmp, "logger-test")
os.makedirs(os.path.dirname(path), exist_ok=True)
os.mknod(path)
with open(path, "w") as f:
f.write("aaaa\n" * 2000 + "end")
add_filehandler(os.path.join(tmp, "logger-test"))
with open(path, "r") as f:
# it only keeps the newest information
content = f.readlines()
self.assertLess(abs(len(content) - 1000), 10)
# whatever the logging module decides to log into that file
self.assertNotIn("aaaa", content[-1])
def test_debug(self):
path = os.path.join(tmp, "logger-test")
add_filehandler(path)

Loading…
Cancel
Save