interception-tools
interception-tools is a set of utilities to control and customize the behavior of keyboard input mappings. For most users it is a backend, and would use it with one of its plug-ins.
Interception-tools operates at a lower level compared to older similar tools (xcape, xmodmap) by using libevdev and libudev(3). It thus works across X11, Wayland, and (even though not official documented) the Linux console.
Installation
Many plugins are available:
-
interception-caps2esc to switch
CapsLockwithCtrl/Esc - interception-caps2esc-delay-gitAUR
- interception-caps2esc-nocaps-gitAUR
- interception-dual-function-keys to modify the behavior of a key when held.
- interception-hideawayAUR
- interception-k2k-gitAUR
- interception-ralt2hyperAUR
- interception-space2metaAUR
- interception-uswitchAUR
- interception-vimproved-gitAUR
- interception-xswitchAUR
Basics
How it works
Interception-tool makes use of libevdev, which can sit between the kernel and the process handling an event. Schematically it looks like these:
kernel > libevdev > console kernel > libevdev > xf86-input-evdev > X server > X client kernel > libevdev > Compositor > Wayland client
libevdev is low level and it does not know any about X/Wayland.
More precisely, input devices are exposed as /dev/input/eventN, and basically X or Wayland for example read them directly. With libevdev, they can be easily "grabbed"—not read by X/Wayland any more—then it creates a new virtual device, again as /dev/input/eventN. Then X/Wayland read them instead.
Components
Interception-tools provides 4 executables. The two most important ones are intercept and uinput; the former redirects device input events to stdout, and the latter creates a new virtual device, to which redirect events from stdin.
However they are low-level, and usually you don't have to run them manually. Instead udevmon coordinates; users write config files, and accordingly udevmon runs intercept, uinput and plug-ins. When udevmon is killed, all underlying processes are killed too.
There's also mux for complex cases, which combines streams of input events. (The word mux stands for multiplexer.)
Raw example 1: do nothing
This virtually does nothing:
$ intercept -g DEVNODE | uinput -d DEVNODE
where DEVNODE is the path to the actual device: e.g. /dev/input/by-path/platform-i8042-serio-0-event-kbd for a typical laptop keyboard.
Here intercept grabs the device, and redirects to stdout. uinput creates a new virtual device which is almost a copy of DEVNODE, and redirects stdin to that device. The new device has the same name, and the same abilities as the original device.
Raw example 2: Use a plugin
To actually perform an operation in between the key event and the input, simply pipe it in between intercept and uinput.
E.g. with the interception-caps2esc plugin installed:
$ intercept -g DEVNODE | caps2esc | uinput -d DEVNODE
If we omitted the -g flag, then device event would have been just observed, not grabbed.
Usage
Configuration is done by yaml files in /etc/interception/udevmon.d/. Then users can run udevmon from the command line or by starting udevmon.service.
/etc/interception/udevmon.yaml.Notes
This applies only when a user runs udevmon manually. If it is run by systemd, this can be ignored.
Since the tool has to appear to lie almost at the kernel level, ensure consistent behavior by increasing udevmon priority:
# nice -n -20 udevmon -c udevmon.yaml > udevmon.log 2> udevmon.err &
udevmon systemd service runs with Nice=-20.Misconfiguration can screw the keyboard so that you won't be able to stop interception-tools. To avoid it, you can test by running udevmon from timeout:
# timeout 10 udevmon &
This will kill udevmon (consequently intercept and uinput run by udevmon) in 10 seconds.
Configuration
Basics
Config files are written in the yaml format. One simplest example is this:
- JOB: intercept -g $DEVNODE | <plug-in> | uinput -d $DEVNODE
DEVICE:
LINK: /dev/input/by-path/platform-i8042-serio-0-event-kbd
The JOB line designates how it should be run.
The LINK configuration will match a device with a specific name, but it will accept also a regex option.
This can be combined with multiple job specifications to create a default behavior, in each case only the first matching job is going to be executed:
- JOB: intercept -g $DEVNODE | caps2esc -m 2 | uinput -d $DEVNODE
DEVICE:
LINK: /dev/input/by-id/usb-SEMITEK_USB-HID_Gaming_Keyboard_SN0000000001-event-kbd
- JOB: intercept -g $DEVNODE | caps2esc | uinput -d $DEVNODE
DEVICE:
EVENTS:
EV_KEY: [[KEY_CAPSLOCK, KEY_ESC]]
LINK: .*-event-kbd
Save it in e.g. /etc/interception/udevmon.d/simple.yaml. Then you can activate it by running $ udevmon.
Combine devices
Beside input emulation, the uinput tool also serves purpose to print a device's description in YAML format:
$ uinput -p -d /dev/input/by-id/my-kbd
which itself can be fed back to uinput as:
$ uinput -c my-kbd.yaml
It can also merge device and YAML characteristics, which can be used for instance to combine events coming from keyboard and mouse:
e.g. instance CapsLock+Click as Ctrl+Click
$ uinput -p -d /dev/input/by-id/my-kbd -d /dev/input/by-id/my-mouse -c my-extra.yaml
Handle multiple jobs
The mux is used to combine multiple pipelines into a single one.
A muxer needs to be created first,
and it can later be used as the input or the output of a given pipeline.
In a YAML specification file, the muxer is created using the CMD key:
- CMD: mux -c caps2esc_mixer
- JOB: mux -i caps2esc_mixer | caps2esc | uinput -c /etc/interception/gaming-keyboard.yaml
- JOB: intercept -g $DEVNODE | mux -o caps2esc_mixer
DEVICE:
LINK: /dev/input/by-id/my-kbd
- JOB: intercept $DEVNODE | mux -o caps2esc_mixer
DEVICE:
LINK: /dev/input/by-id/my-mouse
In the example above, when the keyboard is connected, it's grabbed and its input events are sent to the caps2esc muxer that was initially created. Observed input (not grabbed) from mouse is also sent to the same muxer. The buttons of the mouse generate EV_KEY events, so caps2esc will accept them.