# How OpenLogi Uses an OS Input Hook to Capture Mouse Events on macOS, Linux, and Windows

> Discover how OpenLogi captures mouse events across macOS, Linux, and Windows using OS input hooks and native low-level APIs. Learn about its unified Rust API implementation.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: how-to-guide
- Published: 2026-08-26

---

**OpenLogi implements a platform-specific input hook in the `openlogi-hook` crate that uses native low-level APIs—`CGEventTap` on macOS, `evdev`/`epoll` on Linux, and `WH_MOUSE_LL` on Windows—to capture global mouse events and deliver them via a unified Rust API.**

The OpenLogi project provides a cross-platform input monitoring solution through its dedicated `openlogi-hook` crate. This library abstracts the operating system's raw input mechanisms into a consistent interface, allowing the agent process to capture mouse movements, button presses, and scroll events regardless of the underlying platform. Each implementation runs in a dedicated background thread to ensure non-blocking event delivery to the main application runtime.

## Cross-Platform Hook Architecture

The public API defined in [`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs) exposes a single `Hook` struct that works identically across all three operating systems. This abstraction layer ensures that consuming code in the agent does not need platform-specific conditional compilation.

The core interface consists of three primary components:

- **`Hook::new(callback: HookCallback) -> Result<Self>`** – Spawns a platform-specific background thread and registers the user-provided callback to receive `HookEvent` enums.
- **`Hook::stop(&self)`** – Signals the background thread to uninstall the native hook and join cleanly.
- **`cursor_position()`** – A standalone helper function that returns the current global cursor coordinates using native APIs.

All implementations deliver events through the `HookEvent` enum, which includes variants for `MouseMoved`, `ButtonPressed`, `ButtonReleased`, and `Scroll`.

## macOS Implementation via CGEventTap

On macOS, OpenLogi utilizes **Core Graphics Event Taps** (`CGEventTap`) to intercept system-wide input before it reaches applications. The implementation resides in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs).

### Event Tap Creation and Configuration

The macOS hook creates a tap using `CGEventTapCreate` with specific parameters to ensure it receives all HID (Human Interface Device) events:

- **Location**: `CGEventTapLocation::HID` – Listens at the hardware layer
- **Placement**: `CGEventTapPlacement::Head` – Inserts the tap at the front of the event chain
- **Options**: `CGEventTapOptions::ListenOnly` – Observes without modifying events

The tap runs on a dedicated thread hosting a `CFRunLoop`. According to the source in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs), the callback translates each `CGEvent` into an `openlogi-hook::HookEvent` by examining the event type and extracting coordinates.

### Device Differentiation

To distinguish between physical mouse wheels and trackpad gestures, the implementation extracts the originating device via `CGEventCopyIOHIDEvent` followed by `IOHIDEventGetSenderID`. This allows the hook to tag events with their source device type for downstream consumers.

### Watchdog Protection

The macOS implementation includes a watchdog mechanism in [`crates/openlogi-hook/src/macos/watchdog.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos/watchdog.rs) that monitors the health of the `CFRunLoop` thread. If the tap becomes unresponsive or is disabled by the system (for example, if the user removes Accessibility permissions), the watchdog detects the failure and can trigger a restart.

## Linux Implementation via evdev and epoll

The Linux implementation in [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs) takes a direct approach by reading from the kernel's input event interface rather than using X11 or Wayland client libraries.

### Device Enumeration and Polling

The hook performs the following steps to capture mouse events:

1. **Scans `/dev/input`** for devices exposing mouse-related capabilities (`EV_REL` for relative motion or `EV_KEY` for buttons).
2. **Opens each device** with a non-blocking file descriptor.
3. **Registers descriptors** with an `epoll` loop for efficient I/O multiplexing.
4. **Parses input events** into `HookEvent` structures when data becomes available.

This approach works independently of the display server, functioning correctly under both X11 and Wayland environments.

### Wayland Integration

For Wayland systems where global coordinates may not be available through `evdev` alone, OpenLogi includes supplementary implementations in platform-specific submodules:

- **[`crates/openlogi-hook/src/linux/gnome_shell.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux/gnome_shell.rs)** – Interfaces with GNOME Shell via D-Bus or private protocols when available.
- **[`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)** – Uses the **wlroots foreign-toplevel** protocol to obtain cursor position and window information from compositors based on wlroots.

These helpers ensure that the `cursor_position()` helper returns accurate global coordinates even in Wayland's security-constrained environment.

## Windows Implementation via Low-Level Hooks

On Windows, OpenLogi uses the **low-level mouse hook** (`WH_MOUSE_LL`) and optionally the keyboard hook (`WH_KEYBOARD_LL`) as implemented in [`crates/openlogi-hook/src/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows.rs).

### Hook Installation and Message Processing

The Windows implementation follows this sequence:

1. A dedicated background thread calls `SetWindowsHookExW` with the `WH_MOUSE_LL` identifier to register a system-wide hook procedure.
2. The hook procedure (`mouse_proc`) receives a `MSLLHOOKSTRUCT` containing the mouse coordinates, button states, and timestamp from the OS.
3. The structure is converted into a `HookEvent` and forwarded through an `mpsc` channel to the agent runtime.
4. The thread monitors a stop flag; when `Hook::stop()` is called, it invokes `UnhookWindowsHookEx` to remove the hook and cleans up resources.

This mechanism requires the process to maintain a message loop on the thread that installed the hook, which the implementation handles internally.

## Agent Integration and IPC

The hook is instantiated and managed by the agent process in [`crates/openlogi-agent-core/src/runtime/hook.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/runtime/hook.rs). This module contains `HookInner`, which owns the background thread and provides lifecycle management.

The agent exposes hook status to the user interface through the IPC contract defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs). This allows the GUI to display real-time indicators such as "Accessibility / hook installed" and stream live mouse events for debugging purposes. The separation between the low-level hook crate and the agent runtime ensures that crashes in the input handling layer do not bring down the entire application.

## Practical Implementation Examples

### Installing the Hook

The following example demonstrates platform-agnostic hook installation:

```rust
use openlogi_hook::{Hook, HookEvent, HookCallback};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Callback receives every low-level mouse event.
    let callback: HookCallback = Box::new(|event: HookEvent| {
        match event {
            HookEvent::MouseMoved { x, y, .. } => println!("mouse moved to ({x}, {y})"),
            HookEvent::ButtonPressed { button, .. } => println!("button {button:?} down"),
            HookEvent::ButtonReleased { button, .. } => println!("button {button:?} up"),
            HookEvent::Scroll { delta, .. } => println!("scroll {delta:?}"),
            _ => {}
        }
    });

    // Install the OS-specific hook.
    let hook = Hook::new(callback)?;
    println!("Hook installed, press Ctrl-C to stop…");
    
    // Block the main thread; the hook runs in the background.
    std::thread::park();
    
    // Cleanup when finished.
    hook.stop()?;
    Ok(())
}

```

### Querying Cursor Position

To retrieve the current global cursor coordinates on any supported platform:

```rust
use openlogi_hook::cursor_position;

fn print_cursor() {
    if let Some((x, y)) = cursor_position() {
        println!("Current cursor: ({x}, {y})");
    } else {
        println!("Unable to read cursor position");
    }
}

```

## Summary

- OpenLogi captures mouse events through the `openlogi-hook` crate, which provides a unified Rust API across macOS, Linux, and Windows.
- **macOS** uses `CGEventTap` with `CGEventTapLocation::HID` in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs), including device identification via `IOHIDEventGetSenderID` and a watchdog for reliability.
- **Linux** reads directly from `/dev/input/event*` using `evdev` and `epoll` as implemented in [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs), with supplementary Wayland support through wlroots protocols.
- **Windows** installs a low-level hook via `SetWindowsHookExW` with `WH_MOUSE_LL` in [`crates/openlogi-hook/src/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows.rs), processing `MSLLHOOKSTRUCT` messages on a dedicated thread.
- The agent process orchestrates the hook lifecycle in [`crates/openlogi-agent-core/src/runtime/hook.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/runtime/hook.rs) and exposes status via IPC in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs).

## Frequently Asked Questions

### Does the OpenLogi hook require elevated privileges to capture mouse events?

On **macOS**, the hook requires Accessibility permissions, which the user must grant in System Settings; the `CGEventTap` will fail to create otherwise. On **Windows**, administrator privileges are not typically required for `WH_MOUSE_LL` hooks, though some antivirus software may flag the behavior. On **Linux**, reading from `/dev/input/event*` usually requires membership in the `input` group or root privileges, though udev rules can grant specific users access.

### How does OpenLogi distinguish between a physical mouse and a trackpad on macOS?

The macOS implementation calls `CGEventCopyIOHIDEvent` on the `CGEvent` to obtain the underlying `IOHIDEvent`, then uses `IOHIDEventGetSenderID` to retrieve a unique device identifier. This sender ID allows the hook to differentiate between built-in trackpads, external mice, and other pointing devices when generating `HookEvent` structures.

### Can the hook capture events while the application is not focused?

Yes. Because the implementations use system-wide mechanisms—`CGEventTap` on macOS, global low-level hooks on Windows, and direct `evdev` access on Linux—the hook captures mouse events regardless of which application currently has focus. This is essential for OpenLogi's agent functionality, which must monitor input across the entire desktop session.

### What happens if the macOS Accessibility permission is revoked while the hook is running?

The macOS implementation includes a watchdog in [`crates/openlogi-hook/src/macos/watchdog.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos/watchdog.rs) that monitors the `CFRunLoop` health. If the system disables the event tap (which occurs when Accessibility permissions are removed), the watchdog detects that the tap has stopped delivering events and can trigger a recovery routine or notify the agent to update the hook status via IPC.