MCPcopy Index your code
hub / github.com/KarsMulder/evsieve

github.com/KarsMulder/evsieve @v1.4.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v1.4.0 ↗ · + Follow
589 symbols 1,344 edges 62 files 209 documented · 35%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

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 program (such as Qemu) claims exclusive access to your keyboard and you still want to be able to listen to hotkeys, you can generate a virtual device and let Qemu claim access to the virtual device instead:

evsieve --input /dev/input/by-id/keyboard grab \
        --hook key:leftctrl key:h exec-shell="echo Hello, world!" \
        --output create-link=/dev/input/by-id/virtual-keyboard

This will generate a virtual device and create a link to it at /dev/input/by-id/virtual-keyboard, which you can pass to Qemu or whatever program demands exclusive access.

Remap your favourite key to lctrl+rctrl

evsieve --input /dev/input/by-id/keyboard grab \
        --map key:scrolllock key:leftctrl key:rightctrl \
        --output create-link=/dev/input/by-id/virtual-keyboard

In case you want to use Qemu evdev passthrough and use some other key than lctrl+rctrl to switch between guest and host, this is the second-easiest solution. (The easiest solution, of course, is using Qemu's built-in functionality for this purpose.)

Swap the control and the shift keys

evsieve --input /dev/input/by-id/keyboard grab    \
        --map yield key:leftctrl   key:leftshift  \
        --map yield key:leftshift  key:leftctrl   \
        --map yield key:rightctrl  key:rightshift \
        --map yield key:rightshift key:rightctrl  \
        --output

In previous examples, we mapped a single key using --map. In this example we do the same, except this time we also pass the yield flag to the --map arguments.

Remember that the output events of one argument are input events for the next argument. The --input argument emits a constant stream of events that are processed by all the maps until they encounter an --output event.

A first map like --map key:leftctrl key:leftshift could turn a control key event into a shift key event without problem. However, if the next argument were to naively be --map key:leftshift key:leftctrl, then the second map would turn the shift key event back into a control key—undoing the effect of the first map.

There are use cases where this behaviour is useful. For cases where it isn't, the --map argument accepts a yield flag. All events that are generated by a --map yield are considered "yielded" and will not trigger any further maps, nor any other arguments except for the --output argument. This prevents the shift event generated by the first map from getting turned back to a ctrl event by later maps.

Process two devices simultaneously

evsieve --input /dev/input/by-id/keyboard grab \
        --output                               \
        --input /dev/input/by-id/mouse grab    \
        --output

Evsieve is not restricted to a single input or output, and is also not restricted to only keyboards.

This results into two virtual devices, one virtual device for the keyboard event (created by the first --output) and a virtual device for all mouse events (created by the second --output). The keyboard arguments are not sent to the virtual mouse because no event in the event processing stream gets past the first --output argument.

It is also possible to map both the keyboard and the mouse to a single virtual hybrid key-mouse device in the following way.:

evsieve --input /dev/input/by-id/keyboard grab \
        --input /dev/input/by-id/mouse grab    \
        --output

Keeping certain keyboard keys on the host after passing a keyboard to the guest

evsieve --input /dev/input/by-id/keyboard grab \
        --output key:f9 key:f10 key:f11 key:f12 \
        --output create-link=/dev/input/by-id/virtual-keyboard

This example is useful for those who wish to do evdev passthrough using Qemu.

It is possible to specify event filters on --output arguments. By default an --output argument will take all events and write them to an output device, but if one or m

Extension points exported contracts — how you extend this code

HasFixedFd (Interface)
# Safety An unsafe marker trait: if a structure implements this trait, it promises that its file descriptor will cannot [10 …
src/io/fd.rs
Serializable (Interface)
(no doc) [4 implementers]
src/persist/bytestream.rs
Context (Interface)
A trait for errors similiar in concept to anyhow::Error. [1 implementers]
src/error.rs
LineRead (Interface)
(no doc) [1 implementers]
src/io/fifo.rs

Core symbols most depended-on inside this repo

clone
called by 35
src/loopback.rs
contains
called by 33
src/range.rs
ev_type
called by 32
src/event.rs
parse
called by 30
src/arguments/parser.rs
with_context
called by 20
src/error.rs
get
called by 18
src/io/epoll.rs
is_empty
called by 17
src/io/epoll.rs
code
called by 15
src/event.rs

Shape

Method 316
Function 147
Class 87
Enum 35
Interface 4

Languages

Rust94%
Python6%

Modules by API surface

src/key.rs33 symbols
unittest/unittest.py29 symbols
src/stream/hook.rs28 symbols
src/range.rs26 symbols
src/capability.rs24 symbols
src/io/output.rs21 symbols
src/io/input.rs20 symbols
src/persist/subsystem.rs17 symbols
src/event.rs17 symbols
src/io/epoll.rs16 symbols
src/loopback.rs15 symbols
src/stream.rs14 symbols

For agents

$ claude mcp add evsieve \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact