evsieve

repository·main·Indexed 43 days ago

https://github.com/karsmulder/evsieve

A low-level Linux utility (v1.4.0) for reading input events from evdev devices and writing them to uinput virtual devices. It enables real-time event manipulation, including key remapping, hotkey execution via shell scripts, and splitting events across multiple virtual devices. It is particularly useful for Qemu evdev-passthrough scenarios to manage input between host and guest VMs.

Tokens
19.1K
Snippets
60
Records
82
Agent score
48%

What's inside evsieve

  1. What is evsieve?

    main
    Evsieve is a low-level Linux utility that reads events from evdev devices (found in /dev/input) and writes them to uinput virtual devices. It is designed for simple event manipulations such as remapping keys, splitting events across multiple virtual devices, or turning joysticks into virtual keyboards. It is particularly useful for Qemu evdev-passthrough scenarios, allowing users to keep certain keys on the host while passing others to a guest VM.
  2. Use the `yield` flag in `--map` to prevent re-mapping

    main

    By default, the output events of one argument are the input events for the next. If you have multiple --map arguments, a key might be re-mapped multiple times in a single pass (e.g., A -> B then B -> A).

    To prevent this, use the yield flag within the --map argument. Events generated by a --map yield are considered "yielded" and will not trigger any subsequent maps or arguments, except for the --output argument.

    evsieve --input /dev/input/by-id/keyboard grab \
            --map yield key:leftctrl  key:leftshift \
            --map yield key:leftshift key:leftctrl \
            --output
  3. Choose between consistent and passive toggle modes

    main

    When using --toggle, you can choose between two modes of operation via the mode= clause. This determines how the toggle behaves if the active target changes while a key is being held down.

    mode=passive

    In passive mode, every source event is mapped to the currently active target event at the exact moment the event occurs.

    Warning: This can lead to "stuck" keys. For example, if you press A (mapping to B), then change the toggle target to C before releasing A, the release event for A will be mapped to C. This leaves the B key in a permanent key_down state because its corresponding key_up event was never sent.

    mode=consistent (Default)

    Consistent mode prevents stuck keys by tracking the state of individual keys. For every key, the toggle remembers which target was active when the key_down event was received. It will then map all subsequent events for that specific key (like key_up) to that same target, even if the global active target for the toggle changes in the meantime.

  4. Use ranges and transitions for advanced matching

    main

    For input events, you can use ranges and transitions to create complex triggers:

    • Ranges: key:a:1~2 matches value 1 or 2. abs:x:10~50 matches values between 10 and 50. abs:x:51~ matches 51 or higher.
    • Transitions: key:a:1..2 matches a value of 2 (repeat) only if the previous value for that key/device was 1 (down).

    Variables for output events: Since output events cannot use ranges/transitions, use these variables to make the output dependent on the input:

    • x: Represents the value of the source event.
    • d: Represents the difference between the current value and the previous value of the same type/code/device (useful for mapping abs to rel).
    # Map X axis movement to relative movement using the difference (d)
    --map abs:x rel:x:d
    
    # Invert the Y axis using the source value (x)
    --map rel:y rel:y:-x
    
    # Press A when X axis goes over 200, release when under 200
    --map abs:x:~199..200~ key:a:1
    --map abs:x:200~..~199 key:a:0
  5. How evsieve event processing works

    main

    Evsieve processes events in a sequential pipeline where the order of arguments is critical. It follows a pattern similar to ImageMagick rather than POSIX conventions:

    1. --input reads events from a device and starts the stream.
    2. Subsequent arguments (like --map or --hook) modify that stream.
    3. --output consumes the final stream and writes it to a virtual device.

    Think of it as a pipe: input | transformations | output. Each argument acts as a stage in the pipeline.

    Important distinction: Evsieve operates on events, not keys. For example, a --map command doesn't just change a key identity; it transforms every associated event (e.g., key_down for capslock becomes key_down for backspace).

  6. Understand Domains and the `--toggle` argument

    main

    Domains

    A domain is a user-defined string attached to events to facilitate complex routing. You can assign a domain to an input device using the domain=name clause. Domains act as filters: @name matches all events with that domain.

    The --toggle argument

    A toggle behaves like a map, but it cycles through multiple output keys. A --hook toggle triggers the switch between these keys.

    For example, --toggle @domain @target1 @target2 will map events from @domain to @target1 by default. When a toggle hook is triggered, it switches to mapping them to @target2, and vice versa.

  7. Toggle keyboard layouts using domains and toggles

    main

    You can use toggle hooks and domains to switch between different key mappings at runtime. By using a toggle hook, you can switch the domain tag applied to events. Subsequent --map commands can then be scoped to specific domains using the @domain syntax. This allows a single script to manage multiple layouts (e.g., Qwerty and Colemak) that all output to the same device.

    evsieve --input /dev/input/by-id/keyboard grab \
            --hook key:leftctrl key:rightctrl toggle \
            --toggle "" @qwerty @colemak \
            --map yield key:e@colemak key:f \
            --map yield key:r@colemak key:p \
            --output
  8. Intercept and block keys with --withhold

    main

    The --withhold [KEY...] argument is used to prevent key combinations from reaching the output device if they trigger a preceding hook.

    How it works: --withhold must follow one or more --hook arguments. It intercepts any event that might trigger those preceding hooks. The event is held until it is clear it won't trigger a hook (in which case it is released) or it does trigger a hook (in which case it is dropped).

    Important: The 'Absolutist' Problem If you use --withhold without specifying keys, evsieve might withhold a modifier key (like Ctrl) until it is released, making it impossible to use other combinations like Ctrl+X.

    Best Practices to avoid unintended withholding:

    1. Specify keys in withhold: Instead of a bare --withhold, use --withhold key:a key:b to only intercept specific keys.
    2. Use sequential: This ensures keys are only withheld if the modifier is already pressed.
    3. Use breaks-on: This allows the withheld modifier to be released immediately if a non-matching key is pressed.

    Example (Optimized Interception):

    evsieve --input /dev/input/by-id/my-keyboard grab \
            --hook key:leftctrl key:a exec-shell="echo A" sequential breaks-on=key::1 \
            --hook key:leftctrl key:b exec-shell="echo B" sequential breaks-on=key::1 \
            --withhold key:a key:b \
            --output
  9. Process multiple input devices simultaneously

    main

    Evsieve can handle multiple input devices. How they are handled depends on how you structure your --output arguments:

    1. Separate Virtual Devices: If you place an --output after each input group, you create distinct virtual devices for each input type (e.g., one for keyboard, one for mouse).
    2. Single Hybrid Device: If you place a single --output after multiple --input arguments, all events from all inputs are merged into a single virtual hybrid device.
    # Create two separate virtual devices
    evsieve --input /dev/input/by-id/keyboard grab \
            --output                               \
            --input /dev/input/by-id/mouse grab    \
            --output
    
    # Create one single hybrid device
    evsieve --input /dev/input/by-id/keyboard grab \
            --input /dev/input/by-id/mouse grab    \
            --output
  10. Map a controller to a virtual keyboard

    main

    To use a joystick or controller as a keyboard in applications that don't support them, use evsieve to map controller events to keyboard keys.

    Key concepts for controller mapping:

    • Transitions: A transition like ~216..217~ matches an event with a value of 217 or greater, provided the previous event with the same type/code had a value of 216 or less. This is ideal for detecting when an absolute axis (like a joystick stick) crosses a threshold.
    • --copy vs --map:
      • --map: Source events matching the pattern are removed from the processing stream.
      • --copy: Source events are matched but remain in the stream. This is useful when multiple mappings might need to trigger simultaneously (e.g., one event releasing one key and pressing another).
    • repeat flag: Adding repeat to the --output command makes the virtual keyboard emit key repeat events when a button is held, mimicking standard keyboard behavior.
    • Discovery: Use the --print argument or the Linux utility evtest to find the specific event codes for your controller buttons.
    # Example for DualSense controller
    evsieve --input /dev/input/by-id/usb-Sony_Interactive_Entertainment_Wireless_Controller-if03-event-joystick \ 
            --copy abs:hat0x:-1      key:left:1@kb  \ 
            --copy abs:hat0x:-1..0~  key:left:0@kb  \ 
            --copy abs:hat0x:1       key:right:1@kb \ 
            --copy abs:hat0x:1..~0   key:right:0@kb \ 
            --copy btn:c             key:z@kb       \ 
            --output @kb repeat
  11. Filter events on `--output` for selective passthrough

    main

    You can specify event filters on --output arguments. While a standard --output writes all events to a device, adding filters (like key:f9) ensures that only events matching those filters are written to that specific output. This is useful for keeping certain keys on the host while passing the rest to a guest (e.g., via Qemu evdev passthrough).

    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
  12. Execute shell commands on toggle using `exec-shell`

    main

    You can trigger external scripts or utilities whenever a toggle state changes by using the exec-shell= clause within a --hook. This is useful for automating hardware changes, such as switching monitor inputs via ddcutil when switching between host and guest machines.

    evsieve --input  /dev/input/by-id/keyboard domain=kb grab \
            --input  /dev/input/by-id/mouse    domain=ms grab \
            --hook   key:kpenter toggle=:1 exec-shell="ddcutil setvcp 60 0x11" \
            --hook   key:kpdot   toggle=:2 exec-shell="ddcutil setvcp 60 0x0f" \
            --block  key:kpenter key:kpdot  \
            --toggle @kb @guest-kb @host-kb \
            --toggle @ms @guest-ms @host-ms \
            --output @host-kb create-link=/dev/input/by-id/host-keyboard   \
            --output @host-ms create-link=/dev/input/by-id/host-mouse      \
            --output @guest-kb create-link=/dev/input/by-id/guest-keyboard \
            --output @guest-ms create-link=/dev/input/by-id/guest-mouse