Evsieve
A utility for mapping events from Linux event devices.
Install / Use
/learn @KarsMulder/EvsieveREADME
What is evsieve?
Evsieve (from "event sieve") is a low-level utility that can read events from Linux event devices (evdev) and write them to virtual event devices (uinput), performing simple manipulations on the events along the way. Examples of things evsieve can do are:
- Remap certain keyboard keys to others;
- Send some keyboard keys to one virtual device and other keyboard keys to another virtual device;
- Turn a joystick into a virtual keyboard.
Evsieve is particularly intended to be used in conjunction with the evdev-passthrough functionality of Qemu. For other purposes, you may be better off using a higher-level utility than evsieve.
Examples of things that evsieve can achieve that are useful for evdev-passthrough are:
- Keeping certain keys on the host while other keys are passed through to the guest;
- Run scripts based on hotkeys even if your keyboard is passed through to the guest;
- Remap another key to lctrl+rctrl, so you can use another key to attach/detach your keyboard to your VM;
- Split the events over multiple virtual devices, possibly passing some of those devices to different VMs or keeping one of them on the host;
- Remap keys on your keyboard on such a low level that even the guest OS can't tell the difference between real and mapped keys.
Evsieve is intended to make simple manipulations easy. It is not a tool for keyboard macro's or complex event manipulation. In case you want to do manipulations that are beyond the scope of evsieve, we recommend using the Python-evdev library instead.
Compilation
Compiling this program requires the Rust toolchain and the libevdev library. To install these dependencies:
- Ubuntu:
sudo apt install cargo libevdev2 libevdev-dev - Debian 12 (Bookworm):
sudo apt install cargo libevdev2 libevdev-dev - Debian 11 (Bullseye):
sudo apt install rustc-mozilla cargo libevdev2 libevdev-dev - Fedora:
sudo dnf install cargo libevdev libevdev-devel - Arch Linux:
sudo pacman -S rust libevdev
After you've installed the dependencies, you can obtain a copy of the source code and compile the program using:
wget https://github.com/KarsMulder/evsieve/archive/v1.4.0.tar.gz -O evsieve-1.4.0.tar.gz
tar -xzf evsieve-1.4.0.tar.gz && cd evsieve-1.4.0
cargo build --release
An executable binary can then be found in target/release/evsieve. You can either execute it as-is, or copy it to somewhere in your PATH for your convenience, e.g.:
sudo install -m 755 -t /usr/local/bin target/release/evsieve
The source code repository contains some files which were automatically generated. You can optionally regenerate these files before compilation. To regenerate these files, run the generate_bindings.sh script before running cargo build. This script requires the rust-bindgen tool to be installed on your system.
Usage: Basic concepts
Linux event devices are a tool the kernel uses to inform userspace applications (such as Xorg, Wayland) about input events such as keyboard presses, mouse movements. Event devices reside in /dev/input and usually have named symlinks pointed to them in /dev/input/by-id.
Event devices emit a stream of events. For example, when you press the A key on your key board, it will emit an (EV_KEY, KEY_A, 1) event, and when you release it it will emit an (EV_KEY, KEY_A, 0) event.
The quickest way to get an idea of how it works is to just look at it. You can either install the utility evtest (shipped by most major Linux distributions), run it as sudo evtest, select your keyboard/mouse/whatever and look at the events it emits, or run evsieve with the following arguments to see all events that are emitted on your computer:
sudo evsieve --input /dev/input/event* --print
Reading and writing
Evsieve is a command-line program designed to read and transform input events emitted by (real) event devices and write them to virtual event devices. It most likely needs to run as root for most features.
Assume you have a keyboard, and a symlink to its event device can be found at /dev/input/by-id/keyboard. A very basic usage of evsieve would be the following:
evsieve --input /dev/input/by-id/keyboard grab --output
In this example, evsieve will open the event device /dev/input/by-id/keyboard for exclusive read access (specified by the grab flag), read events from it, and write those same events to an unnamed virtual event device. This changes the flow of events from:
- Keyboard → Kernel → Event Device → Xorg/Wayland → Applications
To:
- Keyboard → Kernel → Event Device → Evsieve → Virtual Event Device → Xorg/Wayland → Applications
This has effectively accomplished nothing besides adding about 0.15ms of latency. However, if we add additional arguments to evsieve, we can use this construction to add, change, or suppress events. For example, the following script will turn the capslock into a second backspace:
evsieve --input /dev/input/by-id/keyboard grab \
--map key:capslock key:backspace \
--output
Since evsieve has opened your keyboard /dev/input/by-id/keyboard with exclusive access due to the grab flag, the userspace programs won't notice when you press capslock on your real keyboard. Evsieve will then read this capslock event, turn it into a backspace key event and write that to the virtual device, tricking userspace into thinking you just pressed the backspace button instead of the capslock button.
Evsieve actively maps events from the input devices to the output devices as long as evsieve runs. When evsieve exits, the virtual devices will be removed and any grabbed input devices will be released. You can exit evsieve by sending it a SIGINT or SIGTERM signal, for example by pressing Ctrl+C in the terminal where evsieve runs.
Take note that evsieve deals with events, not keys. This doesn't turn the capslock key into a backspace key, but rather turns every event associated with capslock into an event associated with backspace, e.g. a key_down for capslock is turned into a key_down for backspace, and a key_up event for capslock is turned into a key_up event for backspace. This distinction is important for some of the more advanced uses of evsieve.
Sequential event processing
The order of the arguments provided to evsieve matters. This is similar to how ImageMagick works and unlike the POSIX convention.
The --input argument reads events from the disk and generates a stream of events that will be processed by the arguments that come after. Each argument modifies the stream of events generated by previous argument. The --map arguments modify the stream of events by turning some events into other events. The --output argument consumes the events and writes them to a virtual event device.
In bash terms, you could think of all evsieve's arguments as if they were mini programs which have their outputs piped to each other. The previous script can be thought of as if it were something like the following:
# Note: this is not an actually valid evsieve script.
evsieve-input --grab /dev/input/by-id/keyboard | evsieve-map key:capslock key:backspace | evsieve-output
Due to performance concerns and some technical reasons, evsieve is not structured as a set of mini-programs but rather as a single monolithic process, but the idea is the same.
Usage: Examples
In this section we'll introduce the capabilities of evsieve through example scripts. All examples will assume that your keyboard can be found at /dev/input/by-id/keyboard and your mouse can be found at /dev/input/by-id/mouse. If you want to use these scripts, you need to replace these placeholder paths with the real paths to your keyboard and mouse.
All these scripts need to run with a certain amount of privileges. That usually means you'll need to execute them as root using sudo. If you want to put effort in making it run with more minimal permissions using ACLs, it is sufficient for most purposes to have evsieve run as a user with read/write access to /dev/uinput and all devices you try to open.
See which events are being emitted by your keyboard
evsieve --input /dev/input/by-id/keyboard --print
You can insert a --print argument at any point in your script to see which events are being processed by evsieve at that point. The events are printed the way they are seen by evsieve at that point; if you applied some maps or other transformations before the --print statement, then the events printed are the mapped events, not the original ones.
This is useful for debugging your scripts, and is also useful if you want to know what a certain key or button is called by evsieve. You can alternatively use the standard utility evtest (shipped by most major Linux distributions), which will provide a bit more detailed information using the Linux kernel's standard terminology.
Execute a script when some hotkey is pressed
evsieve --input /dev/input/by-id/keyboard \
--hook key:leftctrl key:h exec-shell="echo Hello, world!"
You can execute a certain script when a certain key or combination of keys are pressed simultaneously using the --hook argument.
Each time the left control and the H key are pressed simultaneously, evsieve will execute the command echo Hello, world! using your OS' default POSIX shell, i.e. /bin/sh -c "echo Hello, world!". Note that the script will be executed as the same user as evsieve runs as, which may be root.
Since we did not need to modify the events and merely read them, there is no grab flag on the input device, causing evsieve to open it in nonexclusive mode. Since we didn't claim exclusive access to the device, there is no need to generate a virtual device using --output either.
In case some other
