# How OpenLogi Handles OS-Specific Input Capture: A Deep Dive into Cross-Platform Hook Architecture

> Discover how OpenLogi masterfully handles OS specific input capture. Explore its cross platform hook architecture, abstracting macOS and Linux event systems for unified input handling. Learn more!

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

---

**OpenLogi captures low-level input events through a platform-specific hook backend that abstracts macOS CGEventTap and Linux evdev/uinput implementations behind a unified `HookBackend` trait.**

OpenLogi, an open-source input automation framework maintained in the AprilNEA/OpenLogi repository, solves the challenge of **OS-specific input capture** by providing a cross-platform abstraction layer. The system intercepts mouse, keyboard, and scroll events at the OS kernel level while respecting platform security models and permission systems. This architectural approach allows developers to write unified event-handling logic that compiles and runs correctly on both macOS and Linux without platform-specific code changes.

## The HookBackend Trait: Cross-Platform Abstraction

At the core of OpenLogi's input capture system lies the `HookBackend` trait, defined in [`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs). This interface standardizes how the application starts and stops input monitoring, retrieves the frontmost application, and queries cursor position across disparate operating systems.

The trait exposes three critical methods that each OS backend must implement:

- **`start(cb)`** – Initializes the low-level hook with a callback function that receives `HookEvent` enums (mouse or keyboard) and returns an `EventDisposition` (suppress or pass-through)
- **`stop(inner)`** – Performs clean shutdown of the hook infrastructure and releases OS resources
- **`frontmost_app()`** – Returns the currently focused application's identifier and name
- **`cursor_position()`** – Retrieves the current mouse coordinates

Each method is implemented using compile-time conditional compilation via `cfg(target_os = "...")` gates, ensuring that only the relevant platform code is included in the final binary.

## macOS Implementation: CoreGraphics Event Taps

The macOS backend, located in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs), leverages Apple's CoreGraphics framework to intercept input events at the HID (Human Interface Device) system level.

### CGEventTap and HID Stream Monitoring

The implementation creates a **CGEventTap** using `CGEventTap::new` positioned at the `HIDSystemState` location. This tap intercepts raw hardware events before they reach user-space applications. The code specifically monitors the HID event stream by instantiating `CGEventSource::new(CGEventSourceStateID::HIDSystemState)`, allowing it to capture mouse movements, button presses, and keyboard input with minimal latency.

Before activation, the backend verifies **Accessibility permissions** through `AXIsProcessTrusted` paired with a custom `can_filter_events()` check. This validates that the user has granted the application permission to monitor input events through System Preferences.

### Watchdogs and Lifecycle Management

To prevent system deadlocks and detect permission revocations, the macOS implementation spawns two supervisory threads:

- **`spawn_callback_watchdog`** – Monitors the event processing callback to detect stuck or slow handlers that could block the event stream
- **`spawn_lifecycle_watchdog`** – Tracks the CFRunLoop health and accessibility permission status, triggering graceful shutdown if permissions are revoked mid-execution

The tap runs inside a dedicated thread containing a private **CFRunLoop**, ensuring that event processing never blocks the main application thread.

### Event Translation and Filtering

Raw `CGEvent` objects are translated into OpenLogi's internal `MouseEvent` and `KeyEvent` enums. The implementation specifically handles **synthetic event detection** to differentiate between physical hardware input and software-generated events, and identifies trackpad gestures versus traditional mouse wheel movements through CoreGraphics event flags.

## Linux Implementation: evdev and uinput

The Linux backend in [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs) operates through the kernel's input event device interface, providing granular control over input hardware without requiring X11 or Wayland cooperation.

### Device Discovery and Exclusive Grabbing

Upon initialization, the backend enumerates all `/dev/input/event*` devices using the **evdev** crate. It filters for Logitech relative-pointer mice using the `is_hookable_mouse()` helper function, which checks device vendor IDs and capabilities.

For each valid device, the implementation:

1. Creates a **uinput** virtual device via `build_virtual_device()` that mirrors the physical mouse's capabilities
2. **Exclusively grabs** the physical device using `device.grab()`, preventing other applications (including X11/Wayland) from receiving events
3. Spawns a dedicated per-device thread to handle that specific hardware stream

This exclusive grab ensures that OpenLogi can intercept and optionally suppress events before the operating system window manager processes them.

### Per-Device Threading and Event Handling

Each device thread runs a blocking loop calling `device.fetch_events()`, reading raw input events from the kernel. When events arrive, the thread either:

- **Re-injects** them through the uinput virtual device to maintain system cursor movement while allowing inspection
- **Suppresses** the event entirely if the user callback returns `EventDisposition::Suppress`

The uinput virtual device acts as a proxy, allowing OpenLogi to filter or modify input streams transparently to the rest of the system.

### High-Resolution Scroll Handling

The Linux backend specifically handles modern high-resolution mouse wheels through the `REL_WHEEL_HI_RES` and `REL_HWHEEL_HI_RES` event codes. When high-resolution scrolling is active, the implementation filters out corresponding low-resolution `REL_WHEEL` events to prevent double-counting scroll increments, ensuring smooth scrolling behavior in target applications.

## Frontmost Application Detection

Determining which application currently has focus requires entirely different approaches across platforms, handled through the `frontmost_app()` method.

### macOS Frontmost Detection

On macOS, the implementation calls `NSWorkspace::frontmostApplication()` through the Cocoa framework. This returns the bundle identifier (e.g., `com.apple.Safari`) and localized application name with native system integration, requiring only the Accessibility permissions already established for input capture.

### Linux Frontmost Sources

Linux presents a fragmented ecosystem, so OpenLogi implements a pluggable `FrontmostSource` trait with multiple backends:

- **X11 (`X11Source`)** – Queries the `_NET_ACTIVE_WINDOW` property from the root window, then extracts the `WM_CLASS` and window title via Xlib calls in [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs) around line 665
- **Wayland wlroots** – Implements the `wlr_foreign_toplevel` protocol for compositors based on wlroots (Sway, Hyprland, etc.)
- **GNOME Shell** – Communicates via D-Bus extensions specific to GNOME's window manager when running on GNOME Shell

The backend attempts detection mechanisms in order, falling back gracefully when specific desktop environment APIs are unavailable.

## Cursor Position Queries

### macOS Cursor Position

The macOS backend queries cursor coordinates through `CGEventSource::new(CGEventSourceStateID::HIDSystemState).location()`, which returns the current mouse position in screen coordinates directly from the HID subsystem. This method remains accurate even when the event tap is suppressing movement events.

### Linux Cursor Position

On X11 systems, the implementation uses `query_pointer` Xlib calls to determine cursor coordinates relative to the root window. However, **Wayland intentionally provides no global cursor API** for security reasons; applications only receive relative motion events when their surface has focus. Consequently, the Linux backend returns `None` for cursor position on pure Wayland sessions, and client code must track relative deltas to estimate position.

## Practical Implementation Example

The following Rust code demonstrates the platform-agnostic API for registering input hooks that works identically on macOS and Linux:

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

fn main() -> Result<(), openlogi_hook::HookError> {
    // Start the OS‑specific hook. The callback receives a `HookEvent`
    // (Mouse or Key) and decides whether to suppress or let the event pass.
    let inner = openlogi_hook::Backend::start(|event| {
        match event {
            HookEvent::Mouse(mouse) => {
                println!("Got mouse event: {mouse:?}");
                // Example: suppress all scroll events
                if let openlogi::MouseEvent::Scroll { .. } = mouse {
                    EventDisposition::Suppress
                } else {
                    EventDisposition::PassThrough
                }
            }
            HookEvent::Key(key) => {
                println!("Got key event: {key:?}");
                EventDisposition::PassThrough
            }
        }
    })?;

    // Query frontmost app and cursor position (platform‑agnostic)
    if let Some(app) = openlogi_hook::Backend::frontmost_app() {
        println!("Frontmost app: {}", app.id);
    }
    if let Some(pos) = openlogi_hook::Backend::cursor_position() {
        println!("Cursor at ({}, {})", pos.x, pos.y);
    }

    // … run the rest of the program …

    // Clean shutdown
    openlogi_hook::Backend::stop(inner);
    Ok(())
}

```

The `Backend` type aliases to the appropriate platform implementation at compile time, ensuring zero-cost abstractions without runtime overhead.

## Summary

- **Unified Trait Architecture** – The `HookBackend` trait in [`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs) abstracts platform differences, exposing consistent methods for `start()`, `stop()`, `frontmost_app()`, and `cursor_position()`
- **macOS Security Integration** – Uses `CGEventTap` with `AXIsProcessTrusted` validation, running within a dedicated CFRunLoop thread monitored by callback and lifecycle watchdogs defined in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs)
- **Linux Hardware Control** – Implements evdev device enumeration, exclusive grabbing via `device.grab()`, and uinput virtual device proxying in [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs), with per-device threading for concurrent mouse handling
- **High-Resolution Input Support** – Specifically handles `REL_WHEEL_HI_RES` on Linux and synthetic event filtering on macOS to ensure accurate scroll and gesture recognition
- **Desktop Environment Flexibility** – Supports X11 (`_NET_ACTIVE_WINDOW`), Wayland wlroots (`wlr_foreign_toplevel`), and GNOME Shell D-Bus for frontmost application detection on Linux

## Frequently Asked Questions

### How does OpenLogi handle Accessibility permissions on macOS?

According to the OpenLogi source code in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs), the backend checks `AXIsProcessTrusted` before initializing the `CGEventTap` and maintains a **lifecycle watchdog** that monitors permission status throughout runtime. If the user revokes Accessibility permissions via System Preferences, the watchdog detects this and triggers a graceful shutdown rather than crashing or deadlocking the system.

### Why does the Linux backend only capture Logitech mice?

The `is_hookable_mouse()` function in [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs) specifically filters devices to ensure OpenLogi only attempts to grab hardware it can reliably proxy through uinput. This limitation exists because the evdev grab mechanism requires exclusive device access; grabbing non-Logitech devices could interfere with system input in unexpected ways. The implementation focuses on Logitech relative-pointer devices to ensure consistent behavior across the supported hardware ecosystem.

### Can OpenLogi capture input on Wayland without XWayland?

Yes, but with limitations. The input capture mechanism works through evdev devices independently of the display server, allowing the hook to function on pure Wayland compositors. However, **frontmost application detection** requires specific compositor support through protocols like `wlr_foreign_toplevel` or GNOME Shell D-Bus extensions, and **cursor position queries** return `None` because Wayland intentionally restricts global cursor coordinate access for security reasons.

### What prevents the uinput proxy from creating input loops on Linux?

The architecture prevents loops through two mechanisms: the **exclusive grab** (`device.grab()`) ensures the physical device events only reach OpenLogi and not the system, while the **uinput virtual device** emits events that are distinct from the physical device stream. The code explicitly filters events by source device ID, ensuring that events re-injected through uinput are not re-captured by the hook thread, preventing feedback loops that could render the system unusable.