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

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

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

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.

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

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:

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

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

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 →