# Technical Documentation: sezanzeb/input-remapper

> ℹ️ **Provenance:** In-Tree Documentation (16 Chapters) · [CodeWiki Reference](https://codewiki.google/github.com/sezanzeb/input-remapper) · Recency: Active (< 180 days)

## File: README.md

<h1 align="center">Input Remapper</h1>












## Installation

### Ubuntu/Debian

Either download an installable .deb file from the [latest release](https://github.com/sezanzeb/input-remapper/releases):

```sh
wget https://github.com/sezanzeb/input-remapper/releases/download/2.2.1/input-remapper-2.2.1.deb
sudo apt install -f ./input-remapper-2.2.1.deb
```

Or install the very latest changes via:

```sh
sudo apt install git gettext
git clone https://github.com/sezanzeb/input-remapper.git
cd input-remapper
./scripts/build-deb.sh
sudo apt purge input-remapper input-remapper-daemon input-remapper-gtk python3-inputremapper
sudo apt install -f ./dist/input-remapper-2.2.1.deb
```

Input Remapper is also available in the repositories of [Debian](https://tracker.debian.org/pkg/input-remapper)
and [Ubuntu](https://packages.ubuntu.com/oracular/input-remapper) via

```sh
sudo apt install input-remapper
```

Input Remapper ≥ 2.0 requires at least Ubuntu 22.04.



### Fedora

```sh
sudo dnf install input-remapper
sudo systemctl enable --now input-remapper
```



### Arch

```sh
yay -S input-remapper-git
sudo systemctl enable --now input-remapper
```



### Void

```sh
sudo xbps-install -S input-remapper
sudo ln -s /etc/sv/input-remapper /var/service
sudo sv up input-remapper
```



### Other Distros

Figure out the packages providing those dependencies in your distro, and install them:
`python3-evdev` ≥1.3.0, `gtksourceview4`, `python3-devel`, `python3-pydantic`,
`python3-dasbus`, `python3-psutil`, `python3-gi`, `python3-gi-cairo`, `python3-cairo`, `libayatana-appindicator`

You can also use pip to install some of them. Python packages need to be installed
globally for the service to be able to import them. Don't use `--user`. Conda and such
may also cause problems due to changed python paths and versions.

```sh
sudo pip install evdev pydantic dasbus PyGObject
```

```sh
git clone https://github.com/sezanzeb/input-remapper.git
cd input-remapper
sudo python3 -m install --root /
sudo systemctl enable --now input-remapper
```

For all other options and help, check `python3 -m install --help`

To uninstall:

```sh
sudo python3 -m install.uninstall
```

---

## File: .reviewdog.yml

---
runner:
  mypy:
    name: mypy
    cmd: mypy --show-column-numbers inputremapper tests install --ignore-missing-imports
    errorformat:
      - "%f:%l:%c: %m"

  pylint:
    name: pylint
    cmd: pylint inputremapper tests --extension-pkg-whitelist=evdev
    errorformat:
      - "%f:%l:%c: %t%n: %m"

  flake8:
    cmd: flake8 inputremapper tests
    format: flake8

  black:
    cmd: black --diff --quiet --check ./inputremapper ./tests
    format: black

---

## File: readme/capabilities.md

# 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))
    ]
}
```

---

## File: readme/development.md

Development
===========

Please ping me before working on bigger changes and new features to discuss them
first. Bugfixes are always welcome of course. I try not to be nitpicky during
reviews, unless it's about the gui, UX and software architecture.

All pull requests will at some point require unittests (see below for more info).
It also has to be mostly compliant with pylint, use typehints for new code, and
pass black and ruff.

When a pull request is created, an automated pipeline will run and check for
errors and style. This pipeline has to pass before anything will be merged.

This file should give an overview of some internals of input-remapper.

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 IDE's 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

# Examples:
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 system-tests to avoid interacting with the gui,
which might make tests fail.

You can also use docker to run inputremappers unittests (without testing the gui).
This allows you to change the python version you are testing with by modifying the
dockerfile.

```bash
docker build . -f ./docker-unittest -t inputremapper-unittests
docker run -v ./:/app inputremapper-unittests
docker run inputremapper-unittests python3 -m unittest ...
```

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.

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 place. 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 git+https://github.com/jongracecox/anybadge --break-system-packages
sudo apt install python3-coverage python3-psutil pylint
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 . --break-system-packages
./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



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)

---

## File: readme/examples.md

# 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.

---

## File: readme/macros.md

# 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"`

---

## File: readme/usage.md

# 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.



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 ` + `.



## 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 Event 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

---

## File: .github/pull_request_template.md

Thank you. Pull request checklist:
- [ ] You are able to fix a failing github pipeline
- [ ] You are ready to respond to review comments and implement requested changes
- [ ] You know what you are doing and are not blindly trusting an AI assistant
- [ ] Your code contains complete typehints
- [ ] Your code is black and ruff compliant
- [ ] You are willing to implement automated tests (It's ok to add them later)

---

## File: .github/ISSUE_TEMPLATE/autoloading-not-working.md

---
name: Autoloading not working
about: "..."
title: ''
labels: ''
assignees: ''

---

Please install the newest version from source to see if the problem has already been solved.

**System Information and logs**

1. `input-remapper-control --version`
2. which linux distro (ubuntu 20.04, manjaro, etc.)
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-2/config.json` to see if the "autoload" config is written correctly
6. `systemctl status input-remapper -n 50` the service has to be running
7. `journalctl -b | grep input-remapper`

**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. `sudo udevadm control --log-priority=debug && sudo udevadm control --reload-rules && journalctl -f | grep input-remapper`, now plug in the device that should autoload

---

## File: .github/ISSUE_TEMPLATE/buttons-not-showing-up---can-t-map-a-key-in-the-gui.md

---
name: Buttons not showing up / Can't map a key in the GUI
about: "..."
title: ''
labels: ''
assignees: ''

---

Please install the newest version from source to see if the problem has already been solved.

Share some logs please:

1. `input-remapper-control --version`
2. If a button on your device doesn't show up in the GUI, verify that the button is reporting an event via `sudo evtest`. If not, input-remapper won't be able to map that button.
3. If yes, please run `input-remapper-gtk -d`, reproduce the problem and then share the logs.

---

## File: .github/ISSUE_TEMPLATE/key-not-getting-injected.md

---
name: Key not getting injected
about: "..."
title: ''
labels: ''
assignees: ''

---

Please install the newest version from source to see if the problem has already been solved.

Share some logs please:

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`

6. paste the affected preset .json file from ~/.config/input-remapper-2/presets
7. `sudo pkill -f input-remapper-service && input-remapper-gtk -d`, apply the preset and hit your key. Then share that log.
8. `sudo evtest` while the previous command is running, to see how events are injected. Devices starting with `input-remapper ...` are of interest.

---

## File: .github/ISSUE_TEMPLATE/something-else-is-not-working.md

---
name: Something else is not working
about: "..."
title: ''
labels: ''
assignees: ''

---

To help people understand your problems, run `sudo pkill -f input-remapper && input-remapper-gtk -d`, reproduce the problem and then share the output here.

---

## File: .github/workflows/installation.yml

name: Installation

on: [push, pull_request]

jobs:
  deb:
    runs-on: ubuntu-latest
    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
    steps:
    - uses: actions/checkout@v3
    - name: Install .deb
      run: |
        sudo apt update

        # build dependencies
        sudo apt install gettext -y

        sudo scripts/build-deb.sh
        find build
        sudo apt install -f ./dist/input-remapper-*.deb
    - name: DBus Hello
      run: |
        # Test if files have been placed at the correct places in ubuntus file system.
        # By sending the hello command, we tests the service file, binaries and module

        # Make sure we aren't just importing the module from the cloned repo in the
        # cwd. Commands should import the installed package
        cd /
        [ ! -d "inputremapper" ]

        # Need to use sudo, because sys.path is weird in the ci otherwise
        sudo input-remapper-control --command hello 2>&1 | grep 'Daemon answered with "hello"'

  generic:
    runs-on: ubuntu-latest
    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
    steps:
    - uses: actions/checkout@v3
    - name: Generic installation
      run: |
        sudo scripts/ci-install-deps.sh

        # Check if the generic installation method works too. Also, using `--root /`
        # should not break the system somehow
        sudo python3 -m install --root /

        sudo systemctl daemon-reload
        sudo systemctl restart input-remapper
    - name: DBus Hello
      run: |
        # Make sure we aren't just importing the module from the cloned repo in the
        # cwd. Commands should import the installed package
        cd /
        [ ! -d "inputremapper" ]
        sudo input-remapper-control --command hello 2>&1 | grep 'Daemon answered with "hello"'
    - name: uninstall
      run: |
        cd $GITHUB_WORKSPACE

        printf y | sudo python3 -m install.uninstall

        if pip show input-remapper --quiet; then
            echo "Expected input-remapper package to be uninstalled"
            exit 1
        fi

        if [ -f /usr/share/dbus-1/system.d/inputremapper.Control.conf ]; then
            echo "Expected input-remappers files to be removed"
            exit 1
        fi

        if pgrep -f input-remapper; then
            echo "Expected input-remapper processes to not be running"
            exit 1
        fi

        if systemctl list-unit-files input-remapper.service; then
            echo "Expected input-remapper service to be missing"
            exit 1
        fi
    - name: Partial generic installation
      run: |
        sudo python3 -m install --root / --components python_module

        if ! pip show input-remapper --quiet; then
            echo "Expected python module to be installed"
            exit 1
        fi

        if [ -f /usr/share/dbus-1/system.d/inputremapper.Control.conf ]; then
            echo "Expected data files not to be installed"
            exit 1
        fi

        printf y | sudo python3 -m install.uninstall
        sudo python3 -m install --root / --components data_files

        if pip show input-remapper --quiet; then
            echo "Expected python module not to be installed"
            exit 1
        fi

        if [ ! -f /usr/share/dbus-1/system.d/inputremapper.Control.conf ]; then
            echo "Expected data files to be installed"
            exit 1
        fi

        printf y | sudo python3 -m install.uninstall
        sudo python3 -m install --root / --components python_module data_files

        if ! pip show input-remapper --quiet; then
            echo "Expected python module to be installed"
            exit 1
        fi

        if [ ! -f /usr/share/dbus-1/system.d/inputremapper.Control.conf ]; then
            echo "Expected data files to be installed"
            exit 1
        fi

---

## File: .github/workflows/lint.yml

name: Lint

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
    steps:
    - uses: actions/checkout@v3
    - name: Set up Python 3.14
      uses: actions/setup-python@v4
      with:
        python-version: 3.14
        cache: 'pip'
        cache-dependency-path: pyproject.toml
    - name: Install dependencies
      run: |
        scripts/ci-install-deps.sh
        sudo apt install black
        pip install ruff
    - name: black --check --diff
      run: |
        black --version
        black --check --diff ./inputremapper ./tests
    - name: ruff
      run: |
        ruff --version
        ruff check ./inputremapper ./install

---

## File: .github/workflows/reviewdog.yml

---
name: reviewdog
# run reviewdog for PR only because "github-check" option is failing :(
# https://github.com/reviewdog/reviewdog/issues/924
on: [pull_request]

jobs:
  reviewdog_python:
    name: reviewdog - Python lint
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11"]

    steps:
      - uses: actions/checkout@v3

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
          cache: 'pip'
          cache-dependency-path: pyproject.toml
      - uses: reviewdog/action-setup@master
        with:
          reviewdog_version: latest
      - name: Install dependencies
        shell: bash
        run: |
          scripts/ci-install-deps.sh
          pip install flake8 pylint mypy black
      - name: Set env for PR
        if: github.event_name == 'pull_request'
        shell: bash
        run: echo "REWIEVDOG_REPORTER=github-pr-review" >> $GITHUB_ENV

      - name: Set env for push
        if: github.event_name != 'pull_request'
        shell: bash
        run: echo "REWIEVDOG_REPORTER=github-check" >> $GITHUB_ENV

      - name: Run reviewdog
        shell: bash
        env:
          REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          reviewdog -list
          reviewdog -tee -runners=mypy,black -reporter=${{ env.REWIEVDOG_REPORTER }} -fail-on-error=false

---

## File: .github/workflows/test.yml

name: Test

on: [push, pull_request]

jobs:
  build:
    continue-on-error: true
    runs-on: ubuntu-latest
    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
    strategy:
      matrix:
        python-version: ["3.11", "3.14"] # min and max supported versions
    steps:
    - uses: actions/checkout@v3
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v4
      with:
        python-version: ${{ matrix.python-version }}
        # broken "Error: Cache folder path is retrieved for pip but doesn't exist on disk: /home/runner/.cache/pip"
        # cache: 'pip'
        # cache-dependency-path: pyproject.toml
    - name: Install dependencies
      run: |
        # see https://pygobject.gnome.org/getting_started.html
        sudo apt update
        sudo apt install \
            libgirepository-2.0-dev gcc libcairo2-dev pkg-config python3-dev \
            libgtk-3-dev gir1.2-gtk-3.0 gir1.2-gtksource-4 gobject-introspection
        pip install -e .
    - name: Run tests
      run: |
        # Don't run tests as root, otherwise (I think) they run on the systems python
        # version (3.12 currently)
        python3 --version

        python -m unittest discover tests/unit

--- METRICS ---
- Files Extracted: 16
- Estimated Token Budget: ~15783 tokens
- Recency Window: Active (< 180 days)
- Canonical Reference: https://codewiki.google/github.com/sezanzeb/input-remapper
