# How OpenLogi Captures and Synthesizes Input Events: Low-Level Hooks and Synthetic Injection

> Discover how OpenLogi captures and synthesizes input events. Learn about low-level hooks and synthetic injection for seamless event management.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: internals
- Published: 2026-08-21

---

**OpenLogi separates input capture from synthesis into two dedicated crates: `openlogi-hook` intercepts hardware events using platform-specific low-level APIs (`CGEventTap`, `evdev`, `WH_MOUSE_LL`), while `openlogi-inject` generates synthetic input via OS-native injection functions (`CGEventPost`, `uinput`, `SendInput`).**

OpenLogi is an open-source input remapping and automation framework that provides comprehensive control over mouse and keyboard hardware. Understanding how it **captures and synthesizes input events** requires examining its dual-crate architecture, which cleanly separates event interception from programmatic event generation. This design enables advanced features like button remapping, custom gestures, and DPI cycling while maintaining cross-platform compatibility across macOS, Linux, and Windows.

## Architecture Overview: The Hook and Inject Crates

OpenLogi organizes its input pipeline into two distinct Rust crates that handle opposing concerns. The **capture** mechanism lives in `openlogi-hook`, while the **synthesis** logic resides in `openlogi-inject`.

- **`openlogi-hook`**: Captures raw hardware events before they reach applications, using platform-specific low-level hooks.
- **`openlogi-inject`**: Generates synthetic input events that appear identical to hardware-generated events at the OS level.

This separation allows the system to observe user input, process it through custom logic, and optionally generate new events without creating infinite feedback loops.

## Input Capture with `openlogi-hook`

The capture flow begins when user code initiates a hook via `Hook::start`, defined in [`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs). This function accepts a callback that receives every intercepted event along with a disposition directive.

### Platform-Specific Hook Mechanisms

Each operating system implements the hook using native low-level APIs:

- **macOS**: Uses `CGEventTap`, the same primitive employed by Logitech Options+, to intercept events at the system level. Implementation resides in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs).
- **Linux**: Grabs input devices via `evdev` and processes events through a `uinput` re-injection path, implemented in [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs).
- **Windows**: Employs the `WH_MOUSE_LL` low-level mouse hook, with motion coordinates edge-clamped to screen boundaries, found in [`crates/openlogi-hook/src/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows.rs).

### Event Translation and Disposition

Once captured, raw OS events are normalized into the high-level `HookEvent` enum, which provides variants for `Mouse(MouseEvent)` and `Key(KeyEvent)`. The callback receives both the event and an `EventDisposition` value that determines whether the event should be **suppressed** or **passed through** to the original target.

For debugging purposes, the hook optionally feeds events into `EventMonitor` ([`crates/openlogi-agent-core/src/event_monitor.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/event_monitor.rs)), a bounded lock-free buffer that the GUI polls via `EventMonitor::record` and `EventMonitor::poll` methods.

### Capture Example

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

// Start the OS-level hook and print every captured event
let hook = Hook::start(|event| {
    match event {
        HookEvent::Mouse(m) => println!("mouse: {m:?}"),
        HookEvent::Key(k)   => println!("key: {k:?}"),
    }
    // Allow the event to continue to its original target
    EventDisposition::PassThrough
})?;

/* … later … */
hook.stop(); // Clean shutdown

```

## Input Synthesis with `openlogi-inject`

The injection crate exposes a minimal API surface for generating synthetic events. Located in [`crates/openlogi-inject/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-inject/src/lib.rs), it provides functions like `execute`, `post_horizontal_scroll`, and `ax_navigate_browser`.

### Synthetic Event Generation

Platform-specific implementations in `crates/openlogi-inject/src/inject/{macos,linux,windows}.rs` wrap OS APIs to generate mouse clicks, cursor movements, scrolls, and keyboard actions:

- **macOS**: Calls `CGEventCreateMouseEvent` and `CGEventPost` to inject events into the system event stream.
- **Linux**: Writes synthetic event structures to `uinput` device nodes.
- **Windows**: Uses the `SendInput` API to submit hardware-input simulation structures.

### Distinguishing Synthetic from Real Input

Injected events carry the `SYNTHETIC_EVENT_USER_DATA` flag in their metadata. When the hook subsequently captures these events, it identifies them as synthetic, preventing feedback loops where generated input re-triggers the capture callback.

### Synthesis Examples

Generate a left-click at the current cursor position:

```rust
use openlogi_inject::execute;
use openlogi_core::binding::ButtonId;

let click = openlogi_inject::inject::SyntheticMouseEvent::Button {
    id: ButtonId::Left,
    pressed: true,
    device: None, // None indicates synthetic origin
};
execute(click)?;

```

Send horizontal scroll events for side-scrolling gestures:

```rust
use openlogi_inject::post_horizontal_scroll;

// Scroll 3 "ticks" to the right
post_horizontal_scroll(3)?;

```

## Key Source Files

- [`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs) — Public hook API, `HookEvent` and `EventDisposition` definitions.
- [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs) — macOS `CGEventTap` implementation.
- [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs) — Linux `evdev` device grab logic.
- [`crates/openlogi-hook/src/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows.rs) — Windows `WH_MOUSE_LL` low-level hook.
- [`crates/openlogi-inject/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-inject/src/lib.rs) — Public injection API (`execute`, `post_horizontal_scroll`).
- [`crates/openlogi-inject/src/inject/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-inject/src/inject/macos.rs) — macOS synthetic event generation.
- [`crates/openlogi-inject/src/inject/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-inject/src/inject/linux.rs) — Linux `uinput` synthetic generation.
- [`crates/openlogi-inject/src/inject/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-inject/src/inject/windows.rs) — Windows `SendInput` implementation.
- [`crates/openlogi-agent-core/src/event_monitor.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/event_monitor.rs) — Lock-free event buffer for GUI monitoring.

## Summary

- **OpenLogi uses a dual-crate architecture** to separate input observation from generation, with `openlogi-hook` handling capture and `openlogi-inject` handling synthesis.
- **Platform-native APIs provide low-level access**: macOS uses `CGEventTap` and `CGEventPost`, Linux uses `evdev` and `uinput`, and Windows uses `WH_MOUSE_LL` and `SendInput`.
- **Events are normalized** to the `HookEvent` enum with `EventDisposition` controlling whether they propagate to the original target.
- **Synthetic events are tagged** with `SYNTHETIC_EVENT_USER_DATA` to distinguish them from hardware-generated input, preventing infinite loops.
- **Debugging capabilities** include `EventMonitor`, a bounded lock-free buffer for real-time event inspection without impacting the hot path.

## Frequently Asked Questions

### How does OpenLogi prevent infinite loops when synthesizing events?

The system marks every synthetic event with the `SYNTHETIC_EVENT_USER_DATA` flag during injection. When `openlogi-hook` captures events via platform-specific taps, it checks for this flag and identifies the event as synthetic, allowing the callback logic to ignore or specially handle self-generated input.

### What privileges are required to run the input hooks?

Privilege requirements vary by platform according to the underlying APIs used. macOS requires Accessibility permissions for `CGEventTap`, Linux requires root access or membership in the `input` group to open `evdev` device nodes, and Windows requires standard user privileges for `WH_MOUSE_LL` hooks but may need elevation for certain device interactions.

### Can OpenLogi distinguish between different physical input devices?

On Linux, the implementation uses `evdev` device grabs that target specific device nodes, enabling per-device capture. The macOS and Windows implementations capture global events, though additional filtering logic can identify source devices through extended event metadata where the OS provides it.

### How does EventMonitor impact system performance?

`EventMonitor` uses a bounded lock-free ring buffer to record events, minimizing latency impact on the capture hot path. The GUI polls this buffer asynchronously via `EventMonitor::poll`, ensuring that monitoring and debugging activities do not block the real-time input processing pipeline.