input-remapper/readme/development.md

277 lines
11 KiB
Markdown
Raw Normal View History

2020-12-19 15:09:24 +00:00
# Development
2020-12-19 15:12:55 +00:00
Contributions are very welcome, I will gladly review and discuss any merge
2021-03-21 18:15:20 +00:00
requests. If you have questions about the code and architecture, feel free
2022-01-01 12:00:49 +00:00
to [open an issue](https://github.com/sezanzeb/input-remapper/issues). This
file should give an overview about some internals of input-remapper.
2020-12-19 15:12:55 +00:00
2021-08-22 12:00:08 +00:00
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.
2021-08-04 08:44:48 +00:00
2020-12-19 15:09:24 +00:00
## 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
2020-12-19 23:41:28 +00:00
- [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
2020-12-31 20:46:57 +00:00
- [x] mapping a combined button press to a key
2021-01-01 13:17:41 +00:00
- [x] add "disable" as mapping option
2021-01-01 21:20:33 +00:00
- [x] mapping joystick directions as buttons, making it act like a D-Pad
2021-01-05 18:33:47 +00:00
- [x] mapping mouse wheel events to buttons
2021-02-07 14:00:36 +00:00
- [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
2021-03-21 18:15:20 +00:00
- [x] don't run any GUI code as root for improved wayland compatibility
2021-08-22 12:00:08 +00:00
- [ ] macro editor with easier to read function names
2021-04-08 16:19:55 +00:00
- [ ] plugin support
2021-08-18 13:01:40 +00:00
- [x] getting it into the official debian repo
2020-12-19 15:09:24 +00:00
## Tests
```bash
sudo pip install coverage
2022-01-01 12:00:49 +00:00
pylint inputremapper --extension-pkg-whitelist=evdev
sudo pkill -f input-remapper
2020-12-19 15:09:24 +00:00
sudo pip install . && coverage run tests/test.py
coverage combine && coverage report -m
```
2022-01-01 12:00:49 +00:00
To read events, `evtest` is very helpful. Add `-d` to `input-remapper-gtk`
2020-12-19 15:12:55 +00:00
to get debug output.
2021-02-13 13:11:49 +00:00
Single tests can be executed via
```bash
python3 tests/test.py test_paths.TestPaths.test_mkdir
```
2021-04-02 10:16:34 +00:00
Don't use your computer during integration tests to avoid interacting
with the gui, which might make tests fail.
2021-08-22 12:00:08 +00:00
## Writing Tests
2022-01-01 12:00:49 +00:00
Tests are in https://github.com/sezanzeb/input-remapper/tree/main/tests
2021-08-22 12:00:08 +00:00
2022-01-01 12:00:49 +00:00
https://github.com/sezanzeb/input-remapper/blob/main/tests/test.py patches some modules and runs tests. The tests need
2021-08-22 12:00:08 +00:00
patches because every environment that runs them will be different. By using patches they all look the same to the
individual tests. Some patches also allow to make some handy assertions, like the `write_history` of `UInput`.
Test files are usually named after the module they are in.
In the tearDown functions, usually one of `quick_cleanup` or `cleanup` should be called. This avoids making a test
fail that comes after your new test, because some state variables might still be modified by yours.
2020-12-19 15:12:55 +00:00
## Releasing
2021-01-05 18:33:47 +00:00
ssh/login into a debian/ubuntu environment
2020-12-19 15:12:55 +00:00
```bash
./scripts/build.sh
```
2022-01-10 19:20:16 +00:00
This will generate `input-remapper/deb/input-remapper-1.3.0.deb`
2020-12-19 15:12:55 +00:00
## Badges
```bash
sudo pip install git+https://github.com/jongracecox/anybadge
./scripts/badges.sh
```
New badges, if needed, will be created in `readme/` and they
2020-12-19 15:12:55 +00:00
just need to be commited.
## Files
**gui**
2022-01-01 12:00:49 +00:00
- `bin/input-remapper-gtk` the executable that starts the gui. It also sends
2021-01-23 16:12:20 +00:00
messages to the service via dbus if certain buttons are clicked.
2022-01-01 12:00:49 +00:00
- `bin/input-remapper-helper` provides information to the gui that requires
2021-03-21 18:15:20 +00:00
root rights. Is stopped when the gui closes.
2022-01-01 12:00:49 +00:00
- `data/input-remapper.policy` configures pkexec. By using auth_admin_keep
2021-03-21 18:15:20 +00:00
the user is not asked multiple times for each task that needs elevated
rights. This is done instead of granting the whole application root rights
because it is [considered problematic](https://wiki.archlinux.org/index.php/Running_GUI_applications_as_root).
2022-01-01 12:00:49 +00:00
- `data/input-remapper.desktop` is the entry in the start menu
**cli**
2022-01-01 12:00:49 +00:00
- `bin/input-remapper-control` is an executable to send messages to the service
2021-01-23 16:12:20 +00:00
via dbus. It can be used to start and stop injection without a GUI.
2021-03-21 18:15:20 +00:00
The gui also uses it to run the service (if not already running) and
helper, because by using one single command for both the polkit rules file
remembers not to ask for a password again.
2021-02-07 14:00:36 +00:00
**service**
2022-01-01 12:00:49 +00:00
- `bin/input-remapper-service` executable that starts listening for
2021-02-07 14:00:36 +00:00
commands via dbus and runs the injector when needed. It shouldn't matter how
it is started as long as it manages to start without throwing errors. It
usually needs root rights.
2022-01-01 12:00:49 +00:00
- `data/input-remapper.service` starts input-remapper-service automatically on boot
2021-01-23 16:12:20 +00:00
on distros using systemd.
2022-01-01 12:00:49 +00:00
- `data/inputremapper.Control.conf` is needed to connect to dbus services started
2021-01-23 16:12:20 +00:00
by systemd from other applications.
2021-02-07 14:00:36 +00:00
**autoload**
2022-01-01 12:00:49 +00:00
- `data/input-remapper-autoload.desktop` executes on login and tells the systemd
2021-01-23 16:12:20 +00:00
service to stop injecting (possibly the presets of another user) and to
inject the users autoloaded presets instead (if any are configured)
2022-01-01 12:00:49 +00:00
- `data/input-remapper.rules` udev rule that sends a message to the service to
2021-02-07 14:00:36 +00:00
start injecting for new devices when they are seen for the first time.
**Example system startup**
2022-01-01 12:00:49 +00:00
1. systemd loads `input-remapper.service` on boot
2. on login, `input-remapper-autoload.desktop` is executed, which has knowledge
2021-02-07 14:00:36 +00:00
of the current user und doesn't run as root
2.1 it sends the users config directory to the service
2.2 it makes the service stop all ongoing injectings
2.3 it tells the service to start loading all of the configured presets
2022-01-01 12:00:49 +00:00
3. a bluetooth device gets connected, so udev runs `input-remapper.rules` which
2021-02-07 14:00:36 +00:00
tells the service to start injecting for that device if it has a preset
assigned. Works because step 2 told the service about the current users
config.
2022-01-01 12:00:49 +00:00
Communication to the service always happens via `input-remapper-control`
2020-12-26 15:45:29 +00:00
2021-03-21 18:15:20 +00:00
## Permissions
**gui**
The gui process starts without root rights. It makes sure the daemon and
helper are running via pkexec.
**daemon**
The daemon exists to keep injections alive beyond the lifetime of the
user interface. Runs via root. Communicates via dbus. Either started
via systemd or pkexec.
**helper**
The helper provides information to the user interface like events and
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**
2021-03-21 18:15:20 +00:00
If `sudo evtest` shows an event for the button, try to
modify `should_map_as_btn`. If not, the button cannot be mapped.
2021-03-05 16:20:53 +00:00
## How it works
2021-08-22 12:00:08 +00:00
It uses evdev. The links below point to the 1.0.0 release, line numbers might have changed in the current main.
2021-03-05 16:20:53 +00:00
2021-03-21 18:15:20 +00:00
1. It grabs a device (e.g. /dev/input/event3), so that the key events won't
reach X11/Wayland anymore
2022-01-01 12:00:49 +00:00
[source](https://github.com/sezanzeb/input-remapper/blob/1.0.0/inputremapper/injection/injector.py#L197)
2021-03-21 18:15:20 +00:00
2. Reads the events from it (`evtest` can do it, you can also do
`cat /dev/input/event3` which yields binary stuff)
2022-01-01 12:00:49 +00:00
[source](https://github.com/sezanzeb/input-remapper/blob/1.0.0/inputremapper/injection/injector.py#L443)
2021-03-21 18:15:20 +00:00
3. Looks up the mapping if that event maps to anything
2022-01-01 12:00:49 +00:00
[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
2021-03-21 18:15:20 +00:00
new path in /dev/input, device name is suffixed by "mapped")
2022-01-01 12:00:49 +00:00
[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)
2021-03-21 18:15:20 +00:00
5. Forwards any events that should not be mapped to anything in another new
device (device name is suffixed by "forwarded")
2022-01-01 12:00:49 +00:00
[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)
2021-03-05 16:20:53 +00:00
This stuff is going on as a daemon in the background
2021-08-18 13:01:40 +00:00
## How combinations are injected
2021-08-04 08:44:48 +00:00
Here is an example how combinations are injected:
```
a -> x
a + b -> y
```
2022-01-01 12:00:49 +00:00
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
2021-08-04 08:44:48 +00:00
2021-08-18 13:01:40 +00:00
## Multiple sources, single UInput
2022-01-01 12:00:49 +00:00
https://github.com/sezanzeb/input-remapper/blob/1.0.0/inputremapper/injection/injector.py
2021-08-18 13:01:40 +00:00
2022-01-01 12:00:49 +00:00
This "Injector" process is the only process that injects if input-remapper is used for a single device.
2021-08-18 13:01:40 +00:00
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.
2022-01-01 12:00:49 +00:00
Input-remapper creates a single uinput to inject all mapped events to. For example
2021-08-18 13:01:40 +00:00
- BTN_LEFT -> a
- KEY_2 -> b
so you end up with a new device with the following capabilities
2022-01-01 12:00:49 +00:00
"input-remapper BrandXY Mouse mapped" -> KEY_A, KEY_B
2021-08-18 13:01:40 +00:00
2022-01-01 12:00:49 +00:00
while input-remapper reads from multiple InputDevices it injects the mapped letters into a single UInput.
2021-08-18 13:01:40 +00:00
2020-12-26 15:45:29 +00:00
## Resources
- [Guidelines for device capabilities](https://www.kernel.org/doc/Documentation/input/event-codes.txt)
- [PyGObject API Reference](https://lazka.github.io/pgi-docs/)
- [python-evdev](https://python-evdev.readthedocs.io/en/stable/)
2021-03-21 18:15:20 +00:00
- [Python Unix Domain Sockets](https://pymotw.com/2/socket/uds.html)
2021-03-21 19:47:36 +00:00
- [GNOME HIG](https://developer.gnome.org/hig/stable/)