# OpenLogi Platform-Specific Input Hook Implementation: Cross-Platform Event Capture Architecture

> Explore OpenLogi's platform-specific input hook implementation for cross-platform event capture. Learn how Rust abstracts macOS, Windows, and Linux event handling into a unified API.

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

---

**OpenLogi captures raw mouse and keyboard events across macOS, Windows, and Linux through a unified Rust API that abstracts CGEventTap, WinAPI low-level hooks, and evdev into a single channel-based event stream.**

The AprilNEA/OpenLogi repository implements a sophisticated input interception layer that enables low-latency device control across operating systems. This OpenLogi platform-specific input hook implementation consolidates disparate OS mechanisms into the `openlogi-hook` crate, allowing the agent core and overlay components to consume normalized input events without platform-specific dependencies.

## Architectural Overview of the Hook Layer

### macOS: CGEventTap Implementation

On macOS, the implementation in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs) utilizes **CGEventTap** to intercept hardware events at the system level before they reach foreground applications. Because macOS may terminate event taps when the user switches applications or under power management, a dedicated watchdog in [`watchdog.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/watchdog.rs) monitors the tap's health and automatically restarts it if the system invalidates the connection.

### Windows: Low-Level WinAPI Hooks

The Windows backend in [`crates/openlogi-hook/src/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows.rs) registers **WH_MOUSE_LL** and **WH_KEYBOARD_LL** low-level hooks through the WinAPI, capturing input within the same process before distribution to other windows. Events flow through a thread-safe channel implemented with `crossbeam`, ensuring lock-free communication between the hook thread and the consumer.

### Linux: evdev and Desktop Environment Helpers

Linux systems use [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs) to read raw events directly from **evdev** devices in `/dev/input/*`, while synthetic actions are injected via **uinput**. To handle compositor-specific behaviors on modern desktops, the crate includes [`crates/openlogi-hook/src/linux/gnome_shell.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux/gnome_shell.rs) for GNOME Shell integration and [`crates/openlogi-hook/src/linux/wlr_foreign_toplevel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux/wlr_foreign_toplevel.rs) for Wayland window management under wlroots-based compositors.

## Core API Design in `openlogi-hook`

All three implementations expose a uniform interface defined in [`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs). The public API centers on three primary methods:

- **`Hook::new()`** instantiates the platform-appropriate backend.
- **`Hook::start()`** spawns a background thread to begin event capture.
- **`Hook::receiver()`** returns a `crossbeam::channel::Receiver<Event>` that downstream components poll for normalized input data.

This design encapsulates platform differences behind a single crate boundary. Downstream modules in `openlogi-agent-core` and `openlogi-overlay` interact solely with the `Event` enum and channel receiver, remaining ignorant of whether the underlying source is CGEventTap or evdev.

## Platform-Specific Compilation with cfg Gates

The crate uses conditional compilation to include only the relevant implementation for the target operating system. As implemented in AprilNEA/OpenLogi, the module hierarchy in [`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs) applies `#[cfg(target_os = "macos")]`, `#[cfg(target_os = "windows")]`, and `#[cfg(target_os = "linux")]` attributes to gate the platform modules.

This ensures that Windows-specific WinAPI calls are not compiled on Linux, and that macOS-specific Core Graphics frameworks are excluded from non-Apple builds. The strategy follows the cross-platform rules outlined in [`.claude/rules/cross-platform.md`](https://github.com/AprilNEA/OpenLogi/blob/main/.claude/rules/cross-platform.md), guaranteeing that modifications to the hook's public contract trigger recompilation of all dependent crates across every supported platform.

## Integration with the OpenLogi Agent and Overlay

The hook runs within the **agent process**, which possesses the necessary privileges to access low-level input APIs. When `openlogi-agent-core` initializes, it creates the hook via `openlogi_hook::Hook::new()` and forwards received events to [`src/event_monitor.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/event_monitor.rs).

The event monitor normalizes raw platform events into OpenLogi's internal `InputEvent` type, which the **action ring** ([`src/action_ring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/action_ring.rs)) processes to trigger DPI adjustments, button remaps, or custom macros. Separately, the **overlay** (`openlogi-overlay`) subscribes to the same `crossbeam` channel, enabling visual feedback such as the Actions Ring without duplicating hook logic or requiring elevated permissions in the GUI process.

## Minimal Usage Example

The following Rust code demonstrates how to instantiate and consume events from the hook:

```rust
use openlogi_hook::{Hook, Event};

fn main() -> anyhow::Result<()> {
    // Create the platform-specific hook (macOS, Windows, or Linux)
    let hook = Hook::new()?;
    
    // Start capturing events in a background thread
    hook.start()?;

    // Receive normalized events from the channel
    let rx = hook.receiver();
    while let Ok(event) = rx.recv() {
        match event {
            Event::MouseMove { dx, dy } => println!("Delta: {dx}, {dy}"),
            Event::KeyPress { keycode } => println!("Key: {keycode}"),
            _ => {}
        }
    }
    Ok(())
}

```

This identical source compiles and executes across all supported platforms because `Hook::new()` automatically selects the appropriate backend implementation at compile time.

## Summary

- OpenLogi consolidates macOS CGEventTap, Windows low-level hooks, and Linux evdev into a single crate (`openlogi-hook`) with a unified API.
- The implementation uses `#[cfg]` gates to compile only platform-relevant code, ensuring clean separation and minimal binary size.
- Events flow through a `crossbeam` channel, allowing the agent core, event monitor, action ring, and overlay to consume input without platform-specific dependencies.
- macOS requires a watchdog mechanism to handle system-initiated tap terminations, while Linux requires separate helpers for GNOME and Wayland compositor integration.

## Frequently Asked Questions

### How does OpenLogi capture input without platform-specific code in the main application?

The `openlogi-hook` crate abstracts OS-specific mechanisms behind a common `Hook` struct and `Event` enum. Downstream crates like `openlogi-agent-core` call `Hook::new()` and receive a `crossbeam::channel::Receiver<Event>`, allowing them to process mouse and keyboard data without importing CGEventTap, WinAPI, or evdev directly.

### What mechanisms prevent the macOS event tap from being terminated by the OS?

The macOS implementation includes a watchdog module that monitors the CGEventTap's health. If the system invalidates the tap—commonly occurring during application switching or power transitions—the watchdog detects the failure and automatically respawns the tap to maintain continuous input capture.

### Why does the Linux implementation require both evdev and uinput?

The Linux backend uses **evdev** to read raw hardware events from `/dev/input/*` devices, providing unfiltered access to mouse and keyboard data. It uses **uinput** to inject synthetic events, enabling features like remapped buttons or DPI shifts that require the operating system to perceive hardware-generated input.

### Which components consume the event stream from `openlogi-hook`?

Three primary consumers receive the channel receiver: the **event monitor** ([`src/event_monitor.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/event_monitor.rs)) for normalization, the **action ring** ([`src/action_ring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/action_ring.rs)) for executing remaps and macros, and the **overlay** (`openlogi-overlay`) for rendering visual feedback like the Actions Ring.