# How OpenLogi Captures Input on Linux Using evdev and uinput

> Discover how OpenLogi captures Linux input using evdev and uinput. Learn about its two-stage pipeline for re-injecting raw mouse events through virtual devices for efficient handling.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: how-to-guide
- Published: 2026-09-05

---

**OpenLogi uses a two-stage pipeline on Linux: raw mouse events are captured via the evdev API and re-injected through a uinput virtual device, with each physical device handled by a dedicated thread in the `openlogi-hook` crate.**

The `openlogi-hook` crate provides the cross-platform input capture foundation for OpenLogi, an open-source mouse automation and logging tool. On Linux, this crate implements a **user-space interception layer** that requires no kernel modules—only standard permissions to read `/dev/input/event*` nodes. This article explains the complete evdev-to-uinput mechanism with direct reference to the source code in `AprilNEA/OpenLogi`.

## The evdev and uinput Architecture

OpenLogi's Linux implementation runs entirely in user space through a carefully designed two-device pipeline. As documented in [[`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-hook/src/lib.rs#L8-L11), the architecture follows this principle: one thread per physical mouse device reads **evdev** events and re-injects pass-through events via a **uinput** virtual device.

The separation of capture (evdev) and injection (uinput) allows OpenLogi to inspect, modify, or block events before they reach the display server.

### Step 1: Device Enumeration with evdev

The backend first discovers available input devices. In [[`crates/openlogi-hook/src/linux/mod.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux/mod.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-hook/src/linux/mod.rs), the `Backend` type scans `/dev/input/event*` nodes to identify mouse-class devices. Each discovered device spawns its own thread for isolation—failure of one physical device does not affect others.

### Step 2: Raw Event Reading via evdev

Each dedicated thread opens its assigned device using the Linux **evdev** API through `evdev::Device`. The thread continuously reads kernel-generated `EV_REL` (relative movement) and `EV_KEY` (button) events. These raw events carry timestamps, device IDs, and unprocessed coordinate deltas.

### Step 3: Event Translation and Callback Dispatch

Every evdev event converts to OpenLogi's internal `HookEvent` type, defined in [[`crates/openlogi-hook/src/event.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/event.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-hook/src/event.rs). The user's callback function receives this structured event and returns an `EventDisposition`:

- `EventDisposition::Pass` — forward the event to the system
- `EventDisposition::Consume` — drop the event entirely
- `EventDisposition::Mutate` — replace with modified values

### Step 4: Re-injection Through uinput

For events marked `Pass` or `Mutate`, the thread writes to a **uinput** virtual mouse device created via `uinput::Device`. This virtual device registers with the kernel's input subsystem and appears to X11/Wayland as ordinary hardware. The uinput device merges seamlessly with the standard input stack, preserving compatibility with all applications.

## Practical Implementation Example

The following Rust code demonstrates the complete capture pipeline. When executed, it starts the evdev-uinput hook and filters right-click events:

```rust
use openlogi_hook::{Hook, HookEvent, EventDisposition};

fn main() -> Result<(), openlogi_hook::HookError> {
    // Start the hook; the closure receives every mouse event.
    let hook = Hook::start(|event: HookEvent| {
        // Example: block right-clicks
        if let HookEvent::ButtonPress { button, .. } = event {
            if button == 3 {
                return EventDisposition::Consume; // drop the event
            }
        }
        EventDisposition::Pass // let the event pass through
    })?;

    // The hook runs in background threads; keep the main thread alive.
    std::thread::sleep(std::time::Duration::from_secs(60));

    // Explicitly stop the hook (optional; Drop does this automatically).
    hook.stop();
    Ok(())
}

```

The `Hook::start()` call initiates the evdev enumeration, spawns per-device threads, and establishes the uinput virtual device. All heavy I/O occurs asynchronously; the callback executes synchronously to minimize latency.

## Permission Requirements and Security

Unlike macOS, which requires explicit accessibility permission grants, OpenLogi's Linux implementation needs only filesystem access. The user must have read permissions on `/dev/input/event*` nodes—typically granted by membership in the `input` group or custom udev rules. No kernel modules load, no root processes spawn, and no display server integration is required.

This design choice prioritizes portability and security auditing: the entire interception logic resides in auditable Rust code within the `openlogi-hook` crate.

## Key Source Files

| File | Purpose |
|------|---------|
| [[`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-hook/src/lib.rs) | Public `Hook` API, platform abstraction, documentation of evdev+uinput flow |
| [[`crates/openlogi-hook/src/linux/mod.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux/mod.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-hook/src/linux/mod.rs) | Linux `Backend` implementation, thread spawning, device lifecycle |
| [`crates/openlogi-hook/src/linux/`](https://github.com/AprilNEA/OpenLogi/tree/master/crates/openlogi-hook/src/linux) | Helper modules for evdev handling, uinput creation, device enumeration |
| [[`crates/openlogi-hook/src/event.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/event.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-hook/src/event.rs) | `HookEvent` and `EventDisposition` definitions |

## Summary

- **evdev** provides raw, low-latency access to physical mouse events through `/dev/input/event*` devices
- **uinput** creates a virtual mouse device that transparently injects modified events into the X11/Wayland stack
- The `openlogi-hook` crate implements one thread per device for isolation and performance
- User callbacks determine event disposition: pass, consume, or mutate
- No kernel modules or elevated privileges beyond `input` group membership are required

## Frequently Asked Questions

### Does OpenLogi require root privileges on Linux?

No. OpenLogi runs as an unprivileged user provided the account has read access to `/dev/input/event*` nodes. Grant this through the `input` group membership or targeted udev rule configuration.

### Can OpenLogi capture events from multiple mice simultaneously?

Yes. The `Backend` in [[`crates/openlogi-hook/src/linux/mod.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux/mod.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-hook/src/linux/mod.rs) spawns an independent thread for each discovered mouse device. Each thread maintains its own evdev reader and uinput injector.

### What happens if the callback function is slow?

The evdev reader blocks until the callback returns. Slow callbacks introduce latency but do not drop events—the kernel buffers evdev events up to its queue limit. For minimal latency, keep callback execution fast and defer heavy processing to other threads.

### How does uinput differ from alternative injection methods?

uinput creates a genuine kernel input device visible to all consumers of the input subsystem. Alternative methods like XTest (X11) or wayland protocols are display-server specific. uinput works uniformly across X11, Wayland, and console environments without protocol dependencies.