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

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. 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, 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 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 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:

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 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
  • Linux Hardware Control – Implements evdev device enumeration, exclusive grabbing via device.grab(), and uinput virtual device proxying in 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, 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →