chore(repo): baseline du fork input-remapper (upstream v2.2.1)

Le dépôt ne contenait que README.md ; ajout de l'intégralité du code
fonctionnel du fork comme socle stable de la branche de release.
L'état runtime d'orchestration (.ideai/) est exclu du suivi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 00:47:12 +02:00
parent 1d790a78fb
commit 0ab77d2a64
257 changed files with 51859 additions and 0 deletions

BIN
readme/architecture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

176
readme/capabilities.md Normal file
View File

@ -0,0 +1,176 @@
# Capabilities
A list of example capabilities for reference.
- [Gamepads](#Gamepads)
- [Graphics Tablets](#Graphics-tablets)
- [Touchpads](#Touchpads)
Feel free to extend this list with more devices that are not keyboards
and not mice.
```bash
sudo python3
```
```py
import evdev
evdev.InputDevice('/dev/input/event12').capabilities(verbose=True)
```
## Gamepads
#### Microsoft X-Box 360 pad
```py
{
('EV_SYN', 0): [('SYN_REPORT', 0), ('SYN_CONFIG', 1), ('SYN_DROPPED', 3), ('?', 21)],
('EV_KEY', 1): [
(['BTN_A', 'BTN_GAMEPAD', 'BTN_SOUTH'], 304),
(['BTN_B', 'BTN_EAST'], 305), (['BTN_NORTH', 'BTN_X'], 307),
(['BTN_WEST', 'BTN_Y'], 308), ('BTN_TL', 310), ('BTN_TR', 311),
('BTN_SELECT', 314), ('BTN_START', 315), ('BTN_MODE', 316),
('BTN_THUMBL', 317), ('BTN_THUMBR', 318)
],
('EV_ABS', 3): [
(('ABS_X', 0), AbsInfo(value=1476, min=-32768, max=32767, fuzz=16, flat=128, resolution=0)),
(('ABS_Y', 1), AbsInfo(value=366, min=-32768, max=32767, fuzz=16, flat=128, resolution=0)),
(('ABS_Z', 2), AbsInfo(value=0, min=0, max=255, fuzz=0, flat=0, resolution=0)),
(('ABS_RX', 3), AbsInfo(value=-2950, min=-32768, max=32767, fuzz=16, flat=128, resolution=0)),
(('ABS_RY', 4), AbsInfo(value=1973, min=-32768, max=32767, fuzz=16, flat=128, resolution=0)),
(('ABS_RZ', 5), AbsInfo(value=0, min=0, max=255, fuzz=0, flat=0, resolution=0)),
(('ABS_HAT0X', 16), AbsInfo(value=0, min=-1, max=1, fuzz=0, flat=0, resolution=0)),
(('ABS_HAT0Y', 17), AbsInfo(value=0, min=-1, max=1, fuzz=0, flat=0, resolution=0))
],
('EV_FF', 21): [
(['FF_EFFECT_MIN', 'FF_RUMBLE'], 80), ('FF_PERIODIC', 81),
(['FF_SQUARE', 'FF_WAVEFORM_MIN'], 88), ('FF_TRIANGLE', 89),
('FF_SINE', 90), (['FF_GAIN', 'FF_MAX_EFFECTS'], 96)
]
}
```
## Graphics tablets
#### Wacom Intuos 5 M
Pen
```py
{
('EV_SYN', 0): [
('SYN_REPORT', 0), ('SYN_CONFIG', 1),
('SYN_MT_REPORT', 2), ('SYN_DROPPED', 3), ('?', 4)
],
('EV_KEY', 1): [
(['BTN_LEFT', 'BTN_MOUSE'], 272), ('BTN_RIGHT', 273), ('BTN_MIDDLE', 274),
('BTN_SIDE', 275), ('BTN_EXTRA', 276), (['BTN_DIGI', 'BTN_TOOL_PEN'], 320),
('BTN_TOOL_RUBBER', 321), ('BTN_TOOL_BRUSH', 322), ('BTN_TOOL_PENCIL', 323),
('BTN_TOOL_AIRBRUSH', 324), ('BTN_TOOL_MOUSE', 326), ('BTN_TOOL_LENS', 327),
('BTN_TOUCH', 330), ('BTN_STYLUS', 331), ('BTN_STYLUS2', 332)
],
('EV_REL', 2): [('REL_WHEEL', 8)],
('EV_ABS', 3): [
(('ABS_X', 0), AbsInfo(value=0, min=0, max=44704, fuzz=4, flat=0, resolution=200)),
(('ABS_Y', 1), AbsInfo(value=0, min=0, max=27940, fuzz=4, flat=0, resolution=200)),
(('ABS_Z', 2), AbsInfo(value=0, min=-900, max=899, fuzz=0, flat=0, resolution=287)),
(('ABS_RZ', 5), AbsInfo(value=0, min=-900, max=899, fuzz=0, flat=0, resolution=287)),
(('ABS_THROTTLE', 6), AbsInfo(value=0, min=-1023, max=1023, fuzz=0, flat=0, resolution=0)),
(('ABS_WHEEL', 8), AbsInfo(value=0, min=0, max=1023, fuzz=0, flat=0, resolution=0)),
(('ABS_PRESSURE', 24), AbsInfo(value=0, min=0, max=2047, fuzz=0, flat=0, resolution=0)),
(('ABS_DISTANCE', 25), AbsInfo(value=0, min=0, max=63, fuzz=1, flat=0, resolution=0)),
(('ABS_TILT_X', 26), AbsInfo(value=0, min=-64, max=63, fuzz=1, flat=0, resolution=57)),
(('ABS_TILT_Y', 27), AbsInfo(value=0, min=-64, max=63, fuzz=1, flat=0, resolution=57)),
(('ABS_MISC', 40), AbsInfo(value=0, min=0, max=0, fuzz=0, flat=0, resolution=0))
],
('EV_MSC', 4): [('MSC_SERIAL', 0)]
}
```
Pad
```py
{
('EV_SYN', 0): [('SYN_REPORT', 0), ('SYN_CONFIG', 1), ('SYN_DROPPED', 3)],
('EV_KEY', 1): [
(['BTN_0', 'BTN_MISC'], 256), ('BTN_1', 257), ('BTN_2', 258),
('BTN_3', 259), ('BTN_4', 260), ('BTN_5', 261), ('BTN_6', 262),
('BTN_7', 263), ('BTN_8', 264), ('BTN_STYLUS', 331)],
('EV_ABS', 3): [
(('ABS_X', 0), AbsInfo(value=0, min=0, max=1, fuzz=0, flat=0, resolution=0)),
(('ABS_Y', 1), AbsInfo(value=0, min=0, max=1, fuzz=0, flat=0, resolution=0)),
(('ABS_WHEEL', 8), AbsInfo(value=0, min=0, max=71, fuzz=0, flat=0, resolution=0)),
(('ABS_MISC', 40), AbsInfo(value=0, min=0, max=0, fuzz=0, flat=0, resolution=0))
]
}
```
#### 10 inch PenTablet
```py
{
('EV_SYN', 0): [('SYN_REPORT', 0), ('SYN_CONFIG', 1), ('SYN_DROPPED', 3), ('?', 4)],
('EV_KEY', 1): [(['BTN_DIGI', 'BTN_TOOL_PEN'], 320), ('BTN_TOUCH', 330), ('BTN_STYLUS', 331)],
('EV_ABS', 3): [
(('ABS_X', 0), AbsInfo(value=41927, min=0, max=50794, fuzz=0, flat=0, resolution=200)),
(('ABS_Y', 1), AbsInfo(value=11518, min=0, max=30474, fuzz=0, flat=0, resolution=200)),
(('ABS_PRESSURE', 24), AbsInfo(value=0, min=0, max=8191, fuzz=0, flat=0, resolution=0)),
(('ABS_TILT_X', 26), AbsInfo(value=0, min=-127, max=127, fuzz=0, flat=0, resolution=0)),
(('ABS_TILT_Y', 27), AbsInfo(value=0, min=-127, max=127, fuzz=0, flat=0, resolution=0))
],
('EV_MSC', 4): [('MSC_SCAN', 4)]
}
```
10 inch PenTablet Mouse
```py
{
('EV_SYN', 0): [
('SYN_REPORT', 0), ('SYN_CONFIG', 1), ('SYN_MT_REPORT', 2),
('SYN_DROPPED', 3), ('?', 4)
],
('EV_KEY', 1): [
(['BTN_LEFT', 'BTN_MOUSE'], 272), ('BTN_RIGHT', 273),
('BTN_MIDDLE', 274), ('BTN_SIDE', 275), ('BTN_EXTRA', 276),
('BTN_TOUCH', 330)
],
('EV_REL', 2): [
('REL_X', 0), ('REL_Y', 1), ('REL_HWHEEL', 6), ('REL_WHEEL', 8),
('REL_WHEEL_HI_RES', 11), ('REL_HWHEEL_HI_RES', 12)
],
('EV_ABS', 3): [
(('ABS_X', 0), AbsInfo(value=0, min=0, max=32767, fuzz=0, flat=0, resolution=0)),
(('ABS_Y', 1), AbsInfo(value=0, min=0, max=32767, fuzz=0, flat=0, resolution=0)),
(('ABS_PRESSURE', 24), AbsInfo(value=0, min=0, max=2047, fuzz=0, flat=0, resolution=0))
],
('EV_MSC', 4): [('MSC_SCAN', 4)]
}
```
## Touchpads
#### ThinkPad E590 SynPS/2 Synaptics TouchPad
```py
{
('EV_SYN', 0): [('SYN_REPORT', 0), ('SYN_CONFIG', 1), ('SYN_DROPPED', 3)],
('EV_KEY', 1): [
(['BTN_LEFT', 'BTN_MOUSE'], 272), ('BTN_TOOL_FINGER', 325),
('BTN_TOOL_QUINTTAP', 328), ('BTN_TOUCH', 330),
('BTN_TOOL_DOUBLETAP', 333), ('BTN_TOOL_TRIPLETAP', 334),
('BTN_TOOL_QUADTAP', 335)
],
('EV_ABS', 3): [
(('ABS_X', 0), AbsInfo(value=3111, min=1266, max=5678, fuzz=0, flat=0, resolution=0)),
(('ABS_Y', 1), AbsInfo(value=2120, min=1162, max=4694, fuzz=0, flat=0, resolution=0)),
(('ABS_PRESSURE', 24), AbsInfo(value=0, min=0, max=255, fuzz=0, flat=0, resolution=0)),
(('ABS_TOOL_WIDTH', 28), AbsInfo(value=0, min=0, max=15, fuzz=0, flat=0, resolution=0)),
(('ABS_MT_SLOT', 47), AbsInfo(value=0, min=0, max=1, fuzz=0, flat=0, resolution=0)),
(('ABS_MT_POSITION_X', 53), AbsInfo(value=0, min=1266, max=5678, fuzz=0, flat=0, resolution=0)),
(('ABS_MT_POSITION_Y', 54), AbsInfo(value=0, min=1162, max=4694, fuzz=0, flat=0, resolution=0)),
(('ABS_MT_TRACKING_ID', 57), AbsInfo(value=0, min=0, max=65535, fuzz=0, flat=0, resolution=0)),
(('ABS_MT_PRESSURE', 58), AbsInfo(value=0, min=0, max=255, fuzz=0, flat=0, resolution=0))
]
}
```

23
readme/coverage.svg Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="99" 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="99" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#anybadge_1)">
<path fill="#555" d="M0 0h65v20H0z"/>
<path fill="#4C1" d="M65 0h34v20H65z"/>
<path fill="url(#b)" d="M0 0h99v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="32.5" y="14">coverage</text>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="83.0" y="15" fill="#010101" fill-opacity=".3">88%</text>
<text x="82.0" y="14">88%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

156
readme/development.md Normal file
View File

@ -0,0 +1,156 @@
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](https://github.com/sezanzeb/input-remapper/issues).
This file should give an overview about some internals of input-remapper.
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.
Running
-------
To quickly restart input-remapper without pkexec prompts, you can use
```bash
sudo pkill -f input-remapper && sudo input-remapper-reader-service -d & sudo input-remapper-service -d & input-remapper-gtk -d
```
Linting
-------
```bash
mypy inputremapper # find typing issues
black . # auto-format all code in-place
pip install pylint-pydantic --user # https://github.com/fcfangcc/pylint-pydantic
pylint inputremapper # get a code quality rating from pylint
```
Pylint gives lots of great advice on how to write better python code and even detects
errors. Mypy checks for typing errors. Use black to format it.
Automated tests
---------------
You should be able to use your IDEs built in python unittest features to run tests.
But you can also run them from your console:
```bash
pip install psutil # https://github.com/giampaolo/psutil
sudo pkill -f input-remapper
python3 -m unittest discover -s ./tests/
python3 -m unittest tests/unit/test_daemon.py
python3 -m unittest tests.unit.test_ipc.TestPipe -k "test_pipe" -f
# See `python -m unittest -h` for more.
```
Don't use your computer during integration tests to avoid interacting with the gui,
which might make tests fail.
To read events for manual testing, `evtest` is very helpful.
Add `-d` to `input-remapper-gtk` to get debug output.
Writing Tests
-------------
Tests are in https://github.com/sezanzeb/input-remapper/tree/main/tests
Make sure to use the `@test_setup` decorator. Look for other tests that did something
vaguely similar for inspiration. For example, copy one of the macro test files
and modify it if you write a new macro.
If you have difficulty running your tests locally, github will run them for you when
you create a new pull request.
Scripts
-------
To automate some of the development tasks, you can use the
[setup.sh](/scripts/setup.sh) script. The script avoids using `pip` for installation.
Instead, it uses either your local `python3` in your virtual env, or using
`/usr/bin/python3` explicitly. For more information run
```
scripts/setup.sh help
```
Advice
------
Do not use GTKs `foreach` methods, because when the function fails it just freezes up
completely. Use `get_children()` and iterate over it with regular python `for` loops.
Use `gtk_iteration()` in tests when interacting with GTK methods to trigger events to
be emitted.
Do not do `from evdev import list_devices; list_devices()`, and instead do
`import evdev; evdev.list_devices()`. The first variant cannot be easily patched in
tests (there are ways, but as far as I can tell it has to be configured individually
for each source-file/module). The second option allows for patches to be defiend in
one central places. Importing `KEY_*`, `BTN_*`, etc. constants via `from evdev` is
fine.
Releasing
---------
ssh/login into a debian/ubuntu environment
```bash
scripts/build-deb.sh
```
This will generate `dist/input-remapper-2.2.1.deb`
Badges
------
```bash
# https://github.com/nedbat/coveragepy https://github.com/giampaolo/psutil
pip install coverage anybadge pylint psutil
sudo pkill -f input-remapper
# Make sure input-remapper is uninstalled, then install it editable (without sudo
# should be fine), so that the path for the coverage collection is correct.
# Use `find /usr/ -iname "*inputremapper*"` to check if it is uninstalled.
pip install -e .
./scripts/badges.sh
```
New badges, if needed, will be created in `readme/` and they just need to be commited.
Translations
------------
To regenerate the `po/input-remapper.pot` file, run
```bash
xgettext -k --keyword=translatable --sort-output -o po/input-remapper.pot data/input-remapper.glade
xgettext --keyword=_ -L Python --sort-output -jo po/input-remapper.pot inputremapper/configs/mapping.py inputremapper/gui/*.py inputremapper/gui/components/*.py
```
This is the template file that you can copy to fill in the translations. Also create a
corresponding symlink, like `ln -s it_IT.po it.po`, because some environments expect
different names, apparently. See https://github.com/sezanzeb/input-remapper/tree/main/po
for examples.
Architecture
------------
There is a miro board describing input-remappers architecture:
https://miro.com/app/board/uXjVPLa8ilM=/?share_link_id=272180986764
![architecture.png](./architecture.png)
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/)
- [Python Unix Domain Sockets](https://pymotw.com/2/socket/uds.html)
- [GNOME HIG](https://developer.gnome.org/hig/stable/)
- [GtkSource Example](https://github.com/wolfthefallen/py-GtkSourceCompletion-example)
- [linux/input-event-codes.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h)
- [Screenshot Guidelines](https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html)

190
readme/examples.md Normal file
View File

@ -0,0 +1,190 @@
# Examples
Examples for particular devices and/or use cases:
## Event Names
- Alphanumeric `a` to `z` and `0` to `9`
- Modifiers `Alt_L` `Control_L` `Control_R` `Shift_L` `Shift_R`
- Mouse buttons `BTN_LEFT` `BTN_RIGHT` `BTN_MIDDLE` `BTN_SIDE` ...
- Multimedia keys `KEY_NEXTSONG` `KEY_PLAYPAUSE` `XF86AudioMicMute` ...
Mouse movements have to be performed by macros. See below.
## Short Macro Examples
- `key(BTN_LEFT)` a single mouse-click
- `key(1).key(2)` 1, 2
- `wheel(down, 10)` `wheel(up, 10)` Scroll while the input is pressed.
- `mouse(left, 5)` `mouse(right, 2)` `mouse(up, 1)` `mouse(down, 3)` Move the cursor while the input is pressed.
- `repeat(3, key(a).w(500))` a, a, a with 500ms pause
- `modify(Control_L, key(a).key(x))` CTRL + a, CTRL + x
- `key(1).hold(key(2)).key(3)` writes 1 2 2 ... 2 2 3 while the key is pressed
- `event(EV_REL, REL_X, 10)` moves the mouse cursor 10px to the right
- `mouse(right, 4)` which keeps moving the mouse while pressed
- `wheel(down, 1)` keeps scrolling down while held
- `set(foo, 1)` set ["foo"](https://en.wikipedia.org/wiki/Metasyntactic_variable) to 1
- `if_eq($foo, 1, key(x), key(y))` if "foo" is 1, write x, otherwise y
- `hold()` does nothing as long as your key is held down
- `hold_keys(a)` holds down "a" as long as the key is pressed, just like a regular non-macro mapping
- `if_tap(key(a), key(b))` writes a if the key is tapped, otherwise b
- `if_tap(key(a), key(b), 1000)` writes a if the key is released within a second, otherwise b
- `if_single(key(a), key(b))` writes b if another key is pressed, or a if the key is released
and no other key was pressed in the meantime.
- `if_tap(if_tap(key(a), key(b)), key(c))` "a" if tapped twice, "b" if tapped once and "c" if
held down long enough
- `key_up(a).wait(1000).key_down(a)` keeps a pressed for one second
- `hold_keys(Control_L, a)` holds down those two keys
- `key(BTN_LEFT).wait(100).key(BTN_LEFT)` a double-click
## Double Tap
```
if_tap(
if_tap(
key(a),
key(c)
),
key(b)
)
```
- press twice: a
- press and hold: b
- press and release: c
## Combinations Spanning Multiple Devices
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_keys(space).set(foo, 0)`
**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
button of your mouse, it will write "a" instead. If you just press the
middle button of your mouse it behaves like a regular middle mouse button.
**Explanation**
`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.
`set` and `if_eq` work on shared memory, so all injections will see your
variables. Combine both to get a key that works like a normal key, but that also
works as a modifier for other keys of other devices. `if_eq($foo, 1, ..., ...)`
runs the first param if foo is 1, or the second one if foo is not 1.
## Scroll and Click on a Keyboard
Seldom used PrintScreen, ScrollLock and Pause keys on keyboards with TKL (ten key
less) layout are easily accessible by the right hand thanks to the missing
numeric block, so they can be mapped to mouse scroll and click events:
- Print: `wheel(up, 1)`
- Pause: `wheel(down, 1)`
- Scroll Lock: `BTN_LEFT`
- Menu: `BTN_RIGHT`
- F12: `KEY_LEFTCTRL + w`
In contrast to libinput's `ScrollMethod` `button` which requires the scroll
button to belong to the same (mouse) device, clicking and scrolling events mapped
to a keyboard key can fully cooperate with events from a real mouse, e.g.
drag'n'drop by holding a (mapped) keyboard key and moving the cursor by mouse.
Mapping the scrolling to a keyboard key is also useful for trackballs without
a scroll ring.
In contrast to a real scroll wheel, holding a key which has mouse wheel event
mapped produces linear auto-repeat, without any acceleration. Using a PageDown
key for fast scrolling requires only a small adjustment of the right hand
position.
## Scroll on a 3-Button Mouse
Cheap 3-button mouse without a scroll wheel can scroll using the middle button:
- Button MIDDLE: `wheel(down, 1)`
## Click on Lower Buttons of Trackball
Trackball with 4 buttons (e.g. Kensington Wireless Expert Mouse) with lower 2
buttons by default assigned to middle and side button can be remapped to provide
left and right click on both the upper and lower pairs of buttons to avoid
readjusting a hand after moving the cursor down:
- Button MIDDLE: BTN_LEFT
- Button SIDE: BTN_RIGHT
## Scroll on Foot Pedals
While Kinesis Savant Elite 2 foot pedals can be programmed to emit key press or
mouse click events, they cannot emit scroll events themselves. Using the pedals
for scrolling while standing at a standing desk is possible thanks to remapping:
- Button LEFT: `wheel(up, 1)`
- Button RIGHT: `wheel(down, 1)`
## Gamepads
Joystick movements will be translated to mouse movements, while the second
joystick acts as a mouse wheel. You can swap this in the user interface.
All buttons, triggers and D-Pads can be mapped to keycodes and macros.
The D-Pad can be mapped to W, A, S, D for example, to run around in games,
while the joystick turns the view (depending on the game).
Tested with the XBOX 360 Gamepad. On Ubuntu, gamepads worked better in
Wayland than with X11.
## Sequence of Keys with Modifiers
Alt+TAB, Enter, Alt+TAB:
```
modify(Alt_L, key(tab)).wait(250).
key(KP_Enter).key(key_UP).wait(150).
modify(Alt_L, key(tab))
```
## Home Row Mods
See https://precondition.github.io/home-row-mods#home-row-mods-order
- a: `mod_tap(a, Super_L)`
- s: `mod_tap(s, Alt_L)`
- d: `mod_tap(d, Shift_L)`
- f: `mod_tap(f, Control_L)`
## Emitting Unavailable Symbols
For example Japanese letters without overwriting any existing key
of your system-layout. Only works in X11.
```
xmodmap -pke > keyboard_layout
mousepad keyboard_layout &
```
Find a code that is not mapped to anything, for example `keycode 93 = `,
and map it like `keycode 93 = kana_YA`. See [this gist](https://gist.github.com/sezanzeb/e29bae637b8a799ccf2490b8537487df)
for available symbols.
```
xmodmap keyboard_layout
input-remapper-gtk
```
"kana_YA" should be in the dropdown of available symbols now. Map it
to a key and press apply. Now run
```
xmodmap keyboard_layout
```
again for the injection to use that xmodmap as well. It should be possible
to write "ヤ" now when pressing the key.

431
readme/macros.md Normal file
View File

@ -0,0 +1,431 @@
# Macros
input-remapper comes with an optional custom macro language with support for cross-device
variables, conditions and named parameters.
Syntax errors are shown in the UI on save. Each `key` function adds a short delay of 10ms
between key-down, key-up and at the end. See [usage.md](usage.md#configuration-files)
for more info.
Macros are written into the same text field, that would usually contain the output symbol.
Bear in mind that anti-cheat software might detect macros in games.
### key
> Acts like a pressed key. All names that are available in regular mappings can be used
> here.
>
> ```ts
> key(symbol: str)
> ```
>
> Examples:
>
> ```ts
> key(symbol=KEY_A)
> key(b).key(space)
> ```
### key_down and key_up
> Inject the press/down/1 and release/up/0 events individually with those macros.
>
> ```ts
> key_down(symbol: str)
> key_up(symbol: str)
> ```
>
> Examples:
>
> ```ts
> key_down(KEY_A)
> key_up(KEY_B)
> ```
### wait
> Waits in milliseconds before continuing the macro. If the max_time argument is
> provided, it will randomize the time between time and max_time.
>
> ```ts
> wait(time: int, max_time: int | None)
> ```
>
> Examples:
>
> ```ts
> wait(time=100)
> wait(500)
> wait(10, 1000)
> ```
### repeat
> Repeats the execution of the second parameter a few times
>
> ```ts
> repeat(repeats: int, macro: Macro)
> ```
>
> Examples:
>
> ```ts
> repeat(1, key(KEY_A))
> repeat(repeats=2, key(space))
> ```
### toggle
> Repeats the execution of the parameter until activated again
>
> ```ts
> toggle(macro: Macro)
> ```
>
> Examples:
>
> ```ts
> toggle(key(KEY_A))
> ```
### modify
> Holds a modifier while executing the second parameter
>
> ```ts
> modify(modifier: str, macro: Macro)
> ```
>
> Examples:
>
> ```ts
> modify(Control_L, key(a).key(x))
> ```
### mod_tap
> If an input is held down long enough, then it turns into a modifier for all keys
> that came and come afterwards.
>
> You can use this to create home row mods for example.
>
> Behaves similar to the Mod-Tap feature of QMK.
>
> ```ts
> mod_tap(
> default: str,
> modifier: str,
> tapping_term: int
> )
> ```
>
> Examples:
>
> ```ts
> mod_tap(a, Shift_L)
> mod_tap(s, Control_L, 300)
> ```
### hold_keys
> Holds down all the provided symbols like a combination, and releases them when the
> actual key on your keyboard is released.
>
> An arbitrary number of symbols can be provided.
>
> When provided with a single key, it will behave just like a regular keyboard key.
>
> ```ts
> hold_keys(*symbols: str)
> ```
>
> Examples:
>
> ```ts
> hold_keys(KEY_B)
> hold_keys(KEY_LEFTCTRL, KEY_A)
> hold_keys(Control_L, Alt_L, Delete)
> set(foo, KEY_A).hold_keys($foo)
> ```
### hold
> Runs the child macro repeatedly as long as the input is pressed.
>
> ```ts
> hold(macro: Macro)
> ```
>
> Examples:
>
> ```ts
> hold(key(space))
> ```
### mouse
> Moves the mouse cursor
>
> If the fractional `acceleration` value is provided then the cursor will accelerate
> from zero to a maximum speed of `speed`.
>
> ```ts
> mouse(
> direction: up | down | left | right,
> speed: int,
> acceleration: int | float
> )
> ```
>
> Examples:
>
> ```ts
> mouse(up, 1)
> mouse(left, 2)
> mouse(down, 10, 0.05)
> ```
### mouse_xy
> Moves the mouse cursor in both x and y direction.
>
> If the fractional `acceleration` value is provided then the cursor will accelerate
> from zero to the maximum specified x and y speeds.
>
> ```ts
> mouse(
> x: int | float,
> y: int | float,
> acceleration: int | float
> )
> ```
>
> Examples:
>
> ```ts
> mouse_xy(x=10, y=20)
> mouse_xy(-5, -1, 0.01)
> mouse_xy(x=10, acceleration=0.05)
> ```
### wheel
> Injects scroll wheel events
>
> ```ts
> wheel(
> direction: up | down | left | right,
> speed: int
> )
> ```
>
> Examples:
>
> ```ts
> mouse(up, 10)
> mouse(left, 20)
> ```
### event
> Writes an event. Examples for `type`, `code` and `value` can be found via the
> `sudo evtest` command. Also check out [input-event-codes.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h).
> `EV_KEY` for keys, `EV_REL` for mouse movements, `EV_ABS` for gamepad events among
> others.
>
> ```ts
> event(
> type: str | int,
> code: str | int,
> value: int
> )
> ```
>
> Examples:
>
> ```ts
> event(EV_KEY, KEY_A, 1)
> event(EV_REL, REL_X, -10)
> event(2, 8, 1)
> ```
### set
> Set a variable to a value. This variable and its value is available in all injection
> processes.
>
> Variables can be used in function arguments by adding a `$` in front of their name:
> `repeat($foo, key(KEY_A))`
>
> Their values are available for other injections/devices as well, so you can make them
> interact with each other. In other words, using `set` on a keyboard and `if_eq` with
> the previously used variable name on a mouse will work.
>
> ```ts
> set(variable: str, value: str | int)
> ```
>
> Examples:
>
> ```ts
> set(foo, 1)
> set(foo, "qux")
> ```
### add
> Adds a number fo a variable.
>
> ```ts
> add(variable: str, value: int)
> ```
>
> Examples:
>
> ```ts
> set(a, 1).add(a, 2).if_eq($a, 3, key(x), key(y))
> ```
### if_eq
> Compare two values and run different macros depending on the outcome.
>
> ```ts
> if_eq(
> value_1: str | int,
> value_2: str | int,
> then: Macro | None,
> else: Macro | None
> )
> ```
>
> Examples:
>
> ```ts
> set(a, 1).if_eq($a, 1, key(KEY_A), key(KEY_B))
> set(a, 1).set(b, 1).if_eq($a, $b, else=key(KEY_B).key(KEY_C))
> set(a, "foo").if_eq("foo", $a, key(KEY_A))
> set(a, 1).if_eq($a, 1, None, key(KEY_B))
> ```
### if_capslock
> Run the first macro if your capslock is on, otherwise the second.
>
> ```ts
> if_capslock(
> then: Macro | None,
> else: Macro | None
> )
> ```
>
> Examples:
>
> ```ts
> if_capslock(
> then=hold_keys(KEY_3),
> else=hold_keys(KEY_4)
> )
> ```
### if_numlock
> Run the first macro if your numlock is on, otherwise the second.
>
> ```ts
> if_numlock(
> then: Macro | None,
> else: Macro | None
> )
> ```
>
> Examples:
>
> ```ts
> if_numlock(hold_keys(KEY_3), hold_keys(KEY_4))
> ```
### if_tap
> If the key is tapped quickly, run the `then` macro, otherwise the
> second. The third param is the optional time in milliseconds and defaults to
> 300ms
>
> ```ts
> if_tap(
> then: Macro | None,
> else: Macro | None,
> timeout: int
> )
> ```
>
> Examples:
>
> ```ts
> if_tap(key(KEY_A), key(KEY_B), timeout=500)
> if_tap(then=key(KEY_A), else=key(KEY_B))
> ```
### if_single
> If the key that is mapped to the macro is pressed and released, run the `then` macro.
>
> If another key is pressed while the triggering key is held down, run the `else` macro.
>
> If a timeout number is provided, the macro will run `else` if no event arrives for
> more than the configured number in milliseconds.
>
> ```ts
> if_single(
> then: Macro | None,
> else: Macro | None,
> timeout: int | None
> )
> ```
>
> Examples:
>
> ```ts
> if_single(key(KEY_A), key(KEY_B))
> if_single(None, key(KEY_B))
> if_single(then=key(KEY_A), else=key(KEY_B))
> if_single(key(KEY_A), key(KEY_B), timeout=1000)
> ```
### parallel
> Run all provided macros in parallel.
>
> ```ts
> parallel(*macros: Macro)
> ```
>
> Examples:
>
> ```ts
> parallel(
> mouse(up, 10),
> hold_keys(a),
> wheel(down, 10)
> )
> ```
## Syntax
Multiple functions are chained using `.`.
Unlike other programming languages, `qux(bar())` would not run `bar` and then
`qux`. Instead, `cux` can decide to run `bar` during runtime depending on various
other factors. Like `repeat` is running its parameter multiple times.
Whitespaces, newlines and tabs don't have any meaning and are removed when the macro
gets compiled, unless you wrap your strings in "quotes".
Similar to python, arguments can be either positional or keyword arguments.
`key(symbol=KEY_A)` is the same as `key(KEY_A)`.
Using `$` resolves a variable during runtime. For example `set(a, $1)` and
`if_eq($a, 1, key(KEY_A), key(KEY_B))`.
Comments can be written with '#', like `key(KEY_A) # write an "a"`

BIN
readme/plus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

23
readme/pylint.svg Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<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="80" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#anybadge_1)">
<path fill="#555" d="M0 0h44v20H0z"/>
<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="63.0" y="15" fill="#010101" fill-opacity=".3">9.21</text>
<text x="62.0" y="14">9.21</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
readme/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
readme/screenshot_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

355
readme/usage.md Normal file
View File

@ -0,0 +1,355 @@
# Usage
Look into your applications menu and search for **Input Remapper** to open the UI.
You should be prompted for your sudo password as special permissions are needed to read
events from `/dev/input/` files. You can also start it via `input-remapper-gtk`.
First, select your device (like your keyboard) on the first page, then create a new
preset on the second page, and add a mapping. Then you can already edit your inputs,
as shown in the screenshots below.
<p align="center">
<img src="usage_1.png"/>
<img src="usage_2.png"/>
</p>
In the "Output" textbox on the right, type the key to which you would like to map this input.
More information about the possible mappings can be found in
[examples.md](./examples.md) and [below](#key-names). You can also write your macro
into the "Output" textbox. If you hit enter, it will switch to a multiline-editor with
line-numbers.
Changes are saved automatically. Press the "Apply" button to activate (inject) the
mapping you created.
If you later want to modify the Input of your mapping you need to use the
"Stop" button, so that the application can read your original input.
It would otherwise be invisible since the daemon maps it independently of the GUI.
## Troubleshooting
If your key is hanging due to a macro, unplug your device, and then plug it back in.
This should reset the key.
If stuff doesn't work, check the output of `input-remapper-gtk -d` and feel free
to [open up an issue here](https://github.com/sezanzeb/input-remapper/issues/new).
Make sure to not post any debug logs that were generated while you entered
private information with your device. Debug logs are quite verbose.
If input-remapper or your presets prevents your input device from working
at all due to autoload, please try to unplug and plug it in twice.
No injection should be running anymore.
## Combinations
You can use combinations of different inputs to trigger a mapping: While you record
the input (`Record` - Button) press multiple keys and/or move axis at once.
The mapping will be triggered as soon as all the recorded inputs are pressed.
If you use an axis an input you can modify the threshold at which the mapping is
activated in the advanced input configuration, which can be opened by clicking
on the `Advanced` button.
A mapping with an input combination is only injected once all combination keys
are pressed. This means all the input keys you press before the combination is complete
will be injected unmodified. In some cases this can be desirable, in others not.
*Option 1*: In the advanced input configuration there is the `Release Input` toggle.
This will release all inputs which are part of the combination before the mapping is
injected. Consider a mapping `Shift+1 -> a` this will inject a lowercase `a` if the
toggle is on and an uppercase `A` if it is off. The exact behaviour if the toggle is off
is dependent on keys (are modifiers involved?), the order in which they are pressed and
on your environment (X11/Wayland). By default the toggle is on.
*Option 2*: Disable the keys that are part of the combination individually. So with
a mapping of `Super+1 -> a`, you could additionally map `Super` to `disable`. Now
`Super` won't do anything anymore, and therefore pressing the combination won't have
any side effects anymore.
## Writing Combinations
You can write `Control_L + a` as mapping, which will inject those two
keycodes into your system on a single key press. An arbitrary number of
names can be chained using ` + `.
<p align="center">
<img src="plus.png"/>
</p>
## UI Shortcuts
- `ctrl` + `del` stops the injection (only works while the gui is in focus)
- `ctrl` + `q` closes the application
- `ctrl` + `r` refreshes the device list
## Key Names
Check the autocompletion of the GUI for possible values. You can also
obtain a complete list of possiblities using `input-remapper-control --symbol-names`.
Input-remapper only recognizes symbol names, but not the symbols themselves. So for
example, input-remapper might (depending on the system layout) know what a `minus` is, but
it doesn't know `-`.
Key names that start with `KEY_` are keyboard layout independent constants that might
not result in the expected output. For example using `KEY_Y` would result in "z"
if the layout of the environment is set to german. Using `y` on the other hand would
correctly result in "y" to be written.
It is also possible to map a key to `disable` to stop it from doing anything.
## Limitations
**If your fingers can't type it on your keyboard, input-remapper can't inject it.**
The available symbols depend on the environments keyboard layout, and only those that
don't require a combination to be pressed can be used without workarounds (so most
special characters need some extra steps to use them). Furthermore, if your configured
keyboard layout doesn't support the special character at all (not even via a
combination), then it also won't be possible for input-remapper to map that character at
all.
For example, mapping a key to an exclamation mark is not possible if the keyboard
layout is set to german. However, it is possible to mimic the combination that would
be required to write it, by writing `Shift_L + 1` into the mapping.
This is because input-remapper creates a new virtual keyboard and injects numeric keycodes,
and it won't be able to inject anything a usb keyboard wouldn't been able to. This has
the benefit of being compatible to all display servers, but means the environment will
ultimately decide which character to write.
## Analog Axis
It is possible to map analog inputs to analog outputs. E.g. use a gamepad as a mouse.
For this you need to create a mapping and record the input axis. Then click on
`Advanced` and select `Use as Analog`. Make sure to select a target
which supports analog axis and switch to the `Analog Axis` tab.
There you can select an output axis and use the different sliders to configure the
sensitivity, non-linearity and other parameters as you like.
It is also possible to use an analog output with an input combination.
This will result in the analog axis to be only injected if the combination is pressed
## Wheels
When mapping wheels, you need to be aware that there are both `WHEEL` and `WHEEL_HI_RES`
events. This can cause your wheel to scroll, despite being mapped to something.
By fiddling around with the advanced settings when editing one of your inputs, you can
map the "Hi Res" inputs to `disable`.
# External tools
Repositories listed here are made by input-remappers users. Feel free to extend. Beware,
that I can't review their code, so use them at your own risk (just like everything).
- input-remapper-xautopresets: https://github.com/DreadPirateLynx/input-remapper-xautopresets
# Advanced
## Configuration Files
If you don't have a graphical user interface, you'll need to edit the
configuration files. All configuration files need to be valid json files, otherwise the
parser refuses to work.
The default configuration is stored at `~/.config/input-remapper-2/config.json`,
which doesn't include any mappings, but rather other parameters that
are interesting for injections. The config might look something like this:
```json
{
"version": "2.2.1",
"autoload": {
"USB Optical Mouse": "preset name"
}
}
```
`preset name` refers to `~/.config/input-remapper/presets/device name/preset name.json`.
The device name can be found with `sudo input-remapper-control --list-devices`.
### Preset
The preset files are a collection of mappings.
Here is an example configuration for preset "a" for the "gamepad" device:
`~/.config/input-remapper-2/presets/gamepad/a.json`
```json
[
{
"input_combination": [
{"type": 1, "code": 307}
],
"target_uinput": "keyboard",
"output_symbol": "key(2).key(3)",
"macro_key_sleep_ms": 100
},
{
"input_combination": [
{"type": 1, "code": 315, "origin_hash": "07f543a6d19f00769e7300c2b1033b7a"},
{"type": 3, "code": 1, "analog_threshold": 10}
],
"target_uinput": "keyboard",
"output_symbol": "1"
},
{
"input_combination": [
{"type": 3, "code": 1}
],
"target_uinput": "mouse",
"output_type": 2,
"output_code": 1,
"gain": 0.5
}
]
```
This preset consists of three mappings.
* The first maps the key event with code 307 to a macro and sets the time between
injected events of macros to 100 ms. The macro injects its events to the virtual keyboard.
* The second mapping is a combination of a key event with the code 315 and a
analog input of the axis 1 (y-Axis).
* The third maps the y-Axis of a joystick to the y-Axis on the virtual mouse.
### Mapping
As shown above, the mapping is part of the preset. It consists of the input-combination,
which is a list of input-configurations and the mapping parameters.
```
{
"input_combination": [
<InputConfig 1>,
<InputConfig 2>
]
<parameter 1>: <value1>,
<parameter 2>: <value2>
}
```
#### Input Combination and Configuration
The input-combination is a list of one or more input configurations. To trigger a
mapping, all input configurations must trigger.
A input configuration is a dictionary with some or all of the following parameters:
| Parameter | Default | Type | Description |
|------------------|---------|------------------------|---------------------------------------------------------------------|
| type | - | int | Input Event Type |
| code | - | int | Input Evnet Code |
| origin_hash | None | hex (string formatted) | A unique identifier for the device which emits the described event. |
| analog_threshold | None | int | The threshold above which a input axis triggers the mapping. |
##### type, code
The `type` and `code` parameters are always needed. Use the program `evtest` to find
Available types and codes. See also the [evdev documentation](https://www.kernel.org/doc/html/latest/input/event-codes.html#input-event-codes)
##### origin_hash
The origin_hash is an internally computed hash. It is used associate the input with a
specific `/dev/input/eventXX` device. This is useful when a single pyhsical device
creates multiple `/dev/input/eventXX` devices wihth similar capabilities.
See also: [Issue#435](https://github.com/sezanzeb/input-remapper/issues/435)
##### analog_threshold
Setting the `analog_threshold` to zero or omitting it means that the input will be
mapped to an axis. There can only be one axis input with a threshold of 0 in a mapping.
If the `type` is 1 (EV_KEY) the `analog_threshold` has no effect.
The `analog_threshold` is needend when the input is a analog axis which should be
treated as a key input. If the event type is `3 (EV_ABS)` (as in: map a joystick axis to
a key or macro) the threshold can be between `-100 [%]` and `100 [%]`. The mapping will
be triggered once the joystick reaches the position described by the value.
If the event type is `2 (EV_REL)` (as in: map a relative axis (e.g. mouse wheel) to a
key or macro) the threshold can be anything. The mapping will be triggered once the
speed and direction of the axis is higher than described by the threshold.
#### Mapping Parameters
The following table contains all possible parameters and their default values:
| Parameter | Default | Type | Description |
|--------------------------|---------|-----------------|-------------------------------------------------------------------------------------------------------------------------|
| input_combination | | list | see [above](#input-combination-and-configuration) |
| target_uinput | | string | The UInput to which the mapped event will be sent |
| output_symbol | | string | The symbol or macro string if applicable |
| output_type | | int | The event type of the mapped event |
| output_code | | int | The event code of the mapped event |
| release_combination_keys | true | bool | If release events will be sent to the forwarded device as soon as a combination triggers see also #229 |
| **Macro settings** | | | |
| macro_key_sleep_ms | 0 | positive int | |
| **Axis settings** | | | |
| deadzone | 0.1 | float ∈ (0, 1) | The deadzone of the input axis |
| gain | 1.0 | float | Scale factor when mapping an axis to an axis |
| expo | 0 | float ∈ (-1, 1) | Non liniarity factor see also [GeoGebra](https://www.geogebra.org/calculator/mkdqueky) |
| **EV_REL output** | | | |
| rel_rate | 60 | positive int | The frequency `[Hz]` at which `EV_REL` events get generated (also effects mouse macro) |
| **EV_REL as input** | | | |
| rel_to_abs_input_cutoff | 2 | positive float | The value relative to a predefined base-speed, at which `EV_REL` input (cursor and wheel) is considered at its maximum. |
| release_timeout | 0.05 | positive float | The time `[s]` until a relative axis is considered stationary if no new events arrive |
## CLI
**input-remapper-control**
`--command` requires the service to be running. You can start it via
`systemctl start input-remapper` or `sudo input-remapper-service` if it isn't already
running (or without sudo if your user has the appropriate permissions).
Examples:
| Description | Command |
|---------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------|
| Load all configured presets for all devices | `input-remapper-control --command autoload` |
| If you are running as root user, provide information about the whereabouts of the input-remapper config | `input-remapper-control --command autoload --config-dir "~/.config/input-remapper/"` |
| List available device names for the `--device` parameter | `sudo input-remapper-control --list-devices` |
| Stop injecting | `input-remapper-control --command stop --device "Razer Razer Naga Trinity"` |
| Load `~/.config/input-remapper/presets/Razer Razer Naga Trinity/a.json` | `input-remapper-control --command start --device "Razer Razer Naga Trinity" --preset "a"` |
| Loads the configured preset for whatever device is using this /dev path | `/bin/input-remapper-control --command autoload --device /dev/input/event5` |
| Make the input-remapper-service process exit | `/bin/input-remapper-control --command quit` |
**systemctl**
Stopping the service will stop all ongoing injections
```bash
sudo systemctl stop input-remapper
sudo systemctl start input-remapper
systemctl status input-remapper
```
## Testing your Installation
The following commands can be used to make sure it works:
```bash
sudo input-remapper-service &
input-remapper-control --command hello
```
should print `Daemon answered with "hello"`. And
```bash
sudo input-remapper-control --list-devices
```
should print `Found "...", ...`. If anything looks wrong, feel free to [create
an issue](https://github.com/sezanzeb/input-remapper/issues/new).
## Migrating beta configs to version 2
By default, Input Remapper will not migrate configurations from the beta.
If you want to use those you will need to copy them manually.
```bash
rm ~/.config/input-remapper-2 -r
cp ~/.config/input-remapper/beta_1.6.0-beta ~/.config/input-remapper-2 -r
```
Then start input-remapper

BIN
readme/usage_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
readme/usage_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB