How OpenLogi Handles Input Hooks Across macOS, Linux, and Windows: A Deep Dive into Cross-Platform Input Handling

OpenLogi abstracts OS-specific input capture mechanisms behind a unified HookBackend trait, implementing CGEventTap on macOS, evdev/uinput on Linux, and WH_MOUSE_LL on Windows to intercept low-level mouse and keyboard events.

OpenLogi requires low-level access to mouse and keyboard events to enable features like button remapping, DPI modification, and per-application profiles. Because each operating system exposes fundamentally different APIs for input interception, the openlogi-hook crate provides platform-specific backends that conform to a single trait interface. This architecture allows the rest of the OpenLogi ecosystem— whether the agent, UI, or CLI tools—to remain completely OS-agnostic while the hook layer handles the complexities of macOS accessibility permissions, Linux device grabbing, and Windows global hooks.

The HookBackend Trait: A Cross-Platform Contract

All three operating system implementations share a common public API defined in crates/openlogi-hook/src/lib.rs. The HookBackend trait specifies exactly four capabilities that every platform must provide:

pub trait HookBackend {
    type Running;                     // opaque handle for the running hook
    fn start(cb: impl Fn(HookEvent) -> EventDisposition + Send + Sync + 'static)
        -> Result<Self::Running, HookError>;
    fn stop(inner: Self::Running);
    fn frontmost_app() -> Option<ForegroundApp>;
    fn cursor_position() -> Option<CursorPosition>;
}

The agent initializes the appropriate backend at runtime in crates/openlogi-agent/src/hook/mod.rs, storing the returned HookInner handle and forwarding events to the rest of the system. This design ensures that calling code never needs conditional compilation for platform logic; the concrete backend is selected at compile time via cfg flags while presenting an identical interface.

macOS Hook Implementation: CGEventTap with Safety Watchdogs

The macOS backend in crates/openlogi-hook/src/macos.rs utilizes Core Graphics Event Taps (CGEventTap) to sit in the HID event stream and intercept mouse buttons, scroll wheels, and keyboard events before they reach applications.

Accessibility Permission Handling

Before creating the tap, Backend::has_accessibility performs a dual verification by calling both AXIsProcessTrusted and probing an actual filtering tap via can_filter_events. If the user revokes accessibility permissions while the hook is active, the service_tap function detects this revocation and immediately disables the tap to prevent system-wide input freezing.

Run-Loop Thread Architecture

The HookInner struct owns a dedicated background thread that creates the CGEventTap, registers it as a run-loop source, and enters a 500-millisecond polling loop via CFRunLoopRunInMode. After each iteration, the thread checks a stop latch and the current accessibility state, allowing for graceful shutdown without blocking the main thread.

Event Translation and Device Identification

The translate function converts CGEvent types—including mouse movements, button presses, scroll deltas, and key states—into OpenLogi’s internal MouseEvent and KeyEvent structs. Crucially, it extracts the event_sender_id to distinguish between physical devices (e.g., Logitech MX Master vs. built-in trackpad), enabling per-device profiles.

System Safety Mechanisms

To prevent system lock-ups from stuck callbacks or TCC (Transparency, Consent, and Control) permission revocations, the macOS implementation spawns two watchdog threads:

  • spawn_callback_watchdog: Monitors for unresponsive event callbacks
  • spawn_lifecycle_watchdog: Detects accessibility permission loss that could stall the event tap

Both watchdogs force-exit the process with specific exit codes if they detect unrecoverable hazards, ensuring the system never loses input capabilities permanently.

Linux Hook Implementation: evdev Grabbing and Virtual uinput Devices

The Linux backend in crates/openlogi-hook/src/linux.rs takes a different approach due to the kernel's input subsystem architecture. Rather than using a global hook API, it employs exclusive device grabbing and virtual device injection.

Device Discovery and Filtering

The find_mouse_devices function enumerates all /dev/input/event* devices using evdev::enumerate(), applying strict filters to identify relevant Logitech hardware:

  • Includes only relative-pointer mice
  • Excludes touch-driven devices and pointing sticks
  • Ignores virtual devices created by OpenLogi itself (identified by the OPENLOGI_DEVICE_PREFIX constant)

Exclusive Grab and Event Pass-Through

For each valid device, the backend spawns a thread that calls device.grab() to obtain exclusive access, preventing the events from reaching the display server or other applications. Simultaneously, it creates a corresponding virtual mouse using VirtualDevice::builder(). The thread loop reads raw events from the physical device, passes them through the user-supplied callback, and re-emits them via the virtual device unless the callback returns EventDisposition::Suppress.

High-Resolution Scroll Handling

Modern Logitech mice report scroll data via REL_WHEEL_HI_RES and REL_HWHEEL_HI_RES event codes. The Linux backend detects these high-resolution events and converts the raw units into fractional wheel-ticks using ScrollDelta::wheel_ticks. When high-resolution scrolling is active, the implementation suppresses corresponding low-resolution events to prevent double-counting scroll actions.

Front-Most Application Detection

Because Linux lacks a unified API for foreground window detection, the backend initializes a lazily-loaded FRONTMOST_SOURCE that selects the appropriate backend for the current session:

Windows Hook Implementation: WH_MOUSE_LL Global Hooks

The Windows backend in crates/openlogi-hook/src/windows.rs utilizes the Win32 Low-Level Mouse Hook (WH_MOUSE_LL) to intercept mouse events system-wide without requiring elevated privileges for basic operation.

Hook Registration and Message Loop

The implementation calls SetWindowsHookExW with the WH_MOUSE_LL hook type on a dedicated background thread. The hook procedure receives MSLLHOOKSTRUCT events containing mouse coordinates, button states, and injected flags, which it translates into the standard MouseEvent enum used across all platforms.

Graceful Shutdown Mechanism

Unlike macOS and Linux, Windows hooks require message queue processing. The HookBackend::stop implementation posts a WM_QUIT message to the hook thread, ensuring UnhookWindowsHookEx is called and resources are released cleanly without dangling callbacks.

Cursor Position Queries

The Windows backend implements cursor_position() using the native GetCursorPos API, returning a CursorPosition struct containing screen coordinates. This complements the event stream by allowing the agent to query absolute cursor state at any time, not just during events.

Cross-Platform Event Unification

Despite the architectural differences between CGEventTap, evdev, and Win32 hooks, all backends normalize native events into OpenLogi’s internal representation. The following example demonstrates installing a hook that works identically across all three platforms:

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

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Platform-specific backend selected at compile time
    let hook = HookBackend::start(|event| {
        match event {
            HookEvent::Mouse(me) => {
                println!("Mouse event: {me:?}");
                EventDisposition::PassThrough
            }
            HookEvent::Key(ke) => {
                println!("Key event: {ke:?}");
                EventDisposition::PassThrough
            }
        }
    })?;

    std::thread::sleep(std::time::Duration::from_secs(10));
    HookBackend::stop(hook);
    Ok(())
}
  • On macOS, this spawns a CGEventTap in a run-loop thread with accessibility monitoring
  • On Linux, this grabs all Logitech mice, creates virtual uinput devices, and starts threads per device
  • On Windows, this registers a global WH_MOUSE_LL hook with message-queue processing

Summary

OpenLogi implements a sophisticated abstraction layer for cross-platform input hooking that accounts for the unique security models and APIs of each operating system:

  • macOS uses CGEventTap with accessibility permission monitoring and dual watchdog threads to prevent system freezes
  • Linux employs exclusive evdev grabbing paired with uinput virtual devices, supporting high-resolution scrolling and multiple front-most-application detection backends (X11, Wayland, GNOME)
  • Windows utilizes the global WH_MOUSE_LL hook with proper message-loop lifecycle management
  • All platforms implement the HookBackend trait defined in crates/openlogi-hook/src/lib.rs, exposing start, stop, frontmost_app, and cursor_position methods
  • Native events are translated into unified HookEvent enums, allowing OpenLogi’s business logic to remain platform-agnostic

Frequently Asked Questions

How does OpenLogi handle missing accessibility permissions on macOS?

According to the source code in crates/openlogi-hook/src/macos.rs, the backend calls AXIsProcessTrusted and probes a filtering tap before starting. If permissions are missing or revoked during operation, the service_tap function detects the change and disables the event tap to prevent system lock-up. A dedicated lifecycle watchdog monitors for TCC revocation and force-exits the process if the system becomes unresponsive.

Why does the Linux backend use virtual uinput devices instead of a global hook?

Linux lacks a centralized "global hook" API like macOS or Windows. Instead, the implementation in crates/openlogi-hook/src/linux.rs uses evdev to exclusively grab physical Logitech mice, preventing events from reaching the display server. It then creates virtual uinput devices to re-inject events that should pass through to applications. This approach provides the necessary isolation to remap buttons and modify scroll behavior while maintaining compatibility with both X11 and Wayland compositors.

What happens when multiple Logitech mice are connected on Linux?

The find_mouse_devices function enumerates all input devices and filters for Logitech relative-pointer mice. For each valid device found, the Linux backend spawns a separate thread that grabs the device and creates a corresponding virtual mouse. This allows OpenLogi to apply different profiles to different physical devices simultaneously while tracking which device generated each event via the event_sender_id equivalent in the evdev subsystem.

Does the Windows backend require administrator privileges?

The Windows implementation uses WH_MOUSE_LL, which is a low-level hook that does not require elevated privileges for basic mouse event interception. However, certain operations like injecting input or querying specific foreground window information may behave differently depending on the process integrity level. The hook registration via SetWindowsHookExW and the cleanup via UnhookWindowsHookEx function correctly in both standard user and elevated contexts according to the implementation in crates/openlogi-hook/src/windows.rs.

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 →