9.9 KiB
Development
Contributions are very welcome, I will gladly review and discuss any merge requests. If you have questions about the code and architecture, feel free to open an issue. This file should give an overview about some internals of key-mapper.
All pull requests will at some point require unittests, the code coverage may only be improved, not decreased. It also has to be mostly compliant with pylint.
Roadmap
- show a dropdown to select valid devices
- creating presets per device
- renaming presets
- show a mapping table
- make that list extend itself automatically
- read keycodes with evdev
- inject the mapping
- keep the system defaults for unmapped buttons
- button to stop mapping and using system defaults
- highlight changes and alert before discarding unsaved changes
- automatically load presets on login for plugged in devices
- make sure it works on wayland
- support timed macros, maybe using some sort of syntax
- add to the AUR, provide .deb file
- basic support for gamepads as keyboard and mouse combi
- executing a macro forever while holding down the key using
h
- mapping D-Pad directions as buttons
- configure joystick purpose and speed via the GUI
- support for non-GUI TTY environments with a command to stop and start
- start the daemon in such a way to not require usermod
- mapping a combined button press to a key
- add "disable" as mapping option
- mapping joystick directions as buttons, making it act like a D-Pad
- mapping mouse wheel events to buttons
- automatically load presets when devices get plugged in after login (udev)
- map keys using a
modifier + modifier + ... + key
syntax - inject in an additional device instead to avoid clashing capabilities
- don't run any GUI code as root for improved wayland compatibility
- plugin support
- getting it into the official debian repo
Tests
sudo pip install coverage
pylint keymapper --extension-pkg-whitelist=evdev
sudo pkill -f key-mapper
sudo pip install . && coverage run tests/test.py
coverage combine && coverage report -m
To read events, evtest
is very helpful. Add -d
to key-mapper-gtk
to get debug output.
Single tests can be executed via
python3 tests/test.py test_paths.TestPaths.test_mkdir
Don't use your computer during integration tests to avoid interacting with the gui, which might make tests fail.
Releasing
ssh/login into a debian/ubuntu environment
./scripts/build.sh
This will generate key-mapper/deb/key-mapper-1.0.0.deb
Badges
sudo pip install git+https://github.com/jongracecox/anybadge
./scripts/badges.sh
New badges, if needed, will be created in readme/
and they
just need to be commited.
Files
gui
bin/key-mapper-gtk
the executable that starts the gui. It also sends messages to the service via dbus if certain buttons are clicked.bin/key-mapper-helper
provides information to the gui that requires root rights. Is stopped when the gui closes.data/key-mapper.policy
configures pkexec. By using auth_admin_keep 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.data/key-mapper.desktop
is the entry in the start menu
cli
bin/key-mapper-control
is an executable to send messages to the service via dbus. It can be used to start and stop injection without a GUI. 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.
service
bin/key-mapper-service
executable that starts listening for 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.data/key-mapper.service
starts key-mapper-service automatically on boot on distros using systemd.data/keymapper.Control.conf
is needed to connect to dbus services started by systemd from other applications.
autoload
data/key-mapper-autoload.desktop
executes on login and tells the systemd service to stop injecting (possibly the presets of another user) and to inject the users autoloaded presets instead (if any are configured)data/key-mapper.rules
udev rule that sends a message to the service to start injecting for new devices when they are seen for the first time.
Example system startup
- systemd loads
key-mapper.service
on boot - on login,
key-mapper-autoload.desktop
is executed, which has knowledge 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 - a bluetooth device gets connected, so udev runs
key-mapper.rules
which 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.
Communication to the service always happens via key-mapper-control
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
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 older code in 0.7.0 so that their line numbers remain valid.
- It grabs a device (e.g. /dev/input/event3), so that the key events won't reach X11/Wayland anymore source
- Reads the events from it (
evtest
can do it, you can also docat /dev/input/event3
which yields binary stuff) source - Looks up the mapping if that event maps to anything source
- Injects the output event in a new device that key-mapper created (another new path in /dev/input, device name is suffixed by "mapped") source, new device
- Forwards any events that should not be mapped to anything in another new device (device name is suffixed by "forwarded") source, new device
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
- the
a
button is pressed with your finger,a 1
arrives via evdev in key-mapper - key-mapper maps it to
x 1
and injects it b
is pressed with your finger,b 1
arrives via evdev in key-mapper- key-mapper sees a triggered combination and maps it to
y 1
and injects it b
is released,b 0
arrives at key-mapper- key-mapper remembered that it was the trigger for a combination and maps that release to
y 0
and injects it - the
a
button is released,a 0
arrives at key-mapper - key-mapper maps that release to
x 0
and injects it
Multiple sources, single UInput
https://github.com/sezanzeb/key-mapper/blob/1.0.0/keymapper/injection/injector.py
This "Injector" process is the only process that injects if key-mapper 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. Key-mapper 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
"key-mapper BrandXY Mouse mapped" -> KEY_A, KEY_B
while key-mapper reads from multiple InputDevices it injects the mapped letters into a single UInput.