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

> Discover how OpenLogi achieves cross-platform input handling on macOS Linux and Windows by abstracting OS-specific mechanisms behind a unified HookBackend trait.

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

---

**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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs). The `HookBackend` trait specifies exactly four capabilities that every platform must provide:

```rust
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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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:

- **X11**: Queries `_NET_ACTIVE_WINDOW` and maps it to `WM_CLASS`
- **Wayland (wlroots)**: Uses the `wlr_foreign_toplevel` protocol via [`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)
- **Wayland (GNOME)**: Communicates with GNOME Shell via D-Bus using [`crates/openlogi-hook/src/linux/gnome_shell.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux/gnome_shell.rs)
- **Fallback**: Returns `NullSource` when no display server is available

## Windows Hook Implementation: WH_MOUSE_LL Global Hooks

The Windows backend in [`crates/openlogi-hook/src/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/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:

```rust
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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows.rs).