# How OpenLogi-Hook Captures OS Input Events on macOS, Linux, and Windows

> Discover how OpenLogi-Hook captures OS input events on macOS Linux and Windows using native APIs for seamless integration. Control mouse and keyboard events with this powerful tool.

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

---

**OpenLogi-Hook uses platform-specific native APIs—CGEventTap for macOS, evdev/uinput for Linux, and low-level Win32 hooks for Windows—to intercept mouse and keyboard events before they reach applications, forwarding them to a user-supplied callback that can choose to pass through or suppress each event.**

OpenLogi-Hook, part of the AprilNEA/OpenLogi repository, provides a unified Rust interface for capturing low-level input events across desktop operating systems. The library abstracts the significant differences between platform APIs into a common `Hook` interface that delivers `HookEvent` structures to a user callback, allowing applications to monitor or intercept mouse movements, clicks, and keystrokes. Each platform implementation lives in its own source file under `crates/openlogi-hook/src/`, selected at compile time via `cfg` attributes.

## macOS: CGEventTap and IOKit Integration

On macOS, OpenLogi-Hook installs a **CGEventTap** in the Core Graphics event stream to receive every mouse and keyboard event before it reaches the Window Server. This mechanism allows the library to inspect or suppress events at the system level.

The implementation resides in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs). The `Backend` struct implements the `HookBackend` trait, providing the `start`, `frontmost_app`, and `cursor_position` methods. The event tap is created using `CGEventTapCreate` and enabled with `CGEventTapEnable`, running on a dedicated **CFRunLoop** thread to prevent blocking the main application (see lines 1-10).

For scroll events, the tap queries the underlying **IOHIDEvent** to determine the originating device—distinguishing between a physical mouse wheel and a trackpad—via the IOKit registry. This device-origin lookup occurs in the `event_sender_id` and `sender_device_info` functions (lines 60-80). To ensure reliability, [`crates/openlogi-hook/src/watchdog.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/watchdog.rs) implements a monitoring mechanism that restarts the tap if it stops unexpectedly.

## Linux: evdev Capture and uinput Replay

The Linux implementation takes a different approach, utilizing the **evdev** interface to open Logitech mice exclusively while replaying events through a **uinput** virtual device. This ensures suppressed events never reach the desktop, while allowed events are faithfully re-injected into the system stream.

Found in [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs), the `Backend::start` function begins by enumerating available devices via `find_mouse_devices`. It then creates a virtual uinput device using `build_virtual_device` and spawns a dedicated thread for each physical device via `device_thread`. Each thread reads raw evdev events, translates them into `HookEvent` structures, and writes them back through the virtual device if the callback returns `PassThrough` (see lines 1-10).

Clean shutdown is handled through a **pipe-based wake-up mechanism** that signals all device threads to terminate gracefully (lines 45-55). This prevents resource leaks when the hook stops.

## Windows: Low-Level Win32 Hooks

On Windows, OpenLogi-Hook employs the **low-level mouse and keyboard hooks** (`WH_MOUSE_LL` and `WH_KEYBOARD_LL`) installed via the Win32 API. These hooks run on a dedicated thread that maintains a classic message pump to receive system input notifications.

The implementation in [`crates/openlogi-hook/src/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows.rs) spawns a `hook_thread` that registers the callbacks using `SetWindowsHookExW`. The thread then enters a `GetMessageW`/`DispatchMessageW` loop to receive `MSLLHOOKSTRUCT` (mouse) and `KBDLLHOOKSTRUCT` (keyboard) records. These raw structures are converted into `HookEvent` enums, and the callback's `EventDisposition` determines whether the event is passed through or suppressed (see lines 1-10).

To stop the hook, the library posts a `WM_QUIT` message to the dedicated thread (lines 90-100), causing the message pump to exit and the hooks to unhook cleanly.

## Shared API Architecture

All three backends conform to a common trait defined in [`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs). The public entry point is `Hook::start`, which instantiates the appropriate `Backend` implementation based on the target OS. 

The callback receives a `HookEvent` enum containing either `Mouse` or `Key` variants and must return an `EventDisposition`—either `PassThrough` to allow the event to continue or `Suppress` to block it from reaching other applications.

## Practical Implementation Example

The following example demonstrates starting the hook and printing all captured events across platforms:

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

fn main() {
    // Ensure the OS hook has the required permissions (e.g. Accessibility on macOS)
    if !Hook::has_accessibility() {
        eprintln!("Please grant Accessibility access and restart.");
        return;
    }

    // Start the hook; the closure runs for each captured event.
    let hook = Hook::start(|event: HookEvent| {
        println!("{event:?}");
        EventDisposition::PassThrough   // let the event continue to its original target
    })
    .expect("Failed to start OpenLogi hook");

    // Keep the process alive while the hook runs.
    std::thread::park();
    // hook.stop() will be called automatically on drop.
}

```

To query the frontmost application using platform-specific implementations:

```rust
use openlogi_hook::Hook;

fn main() {
    if let Some(app) = Hook::frontmost_app() {
        println!("Frontmost application: {app}");
    } else {
        println!("No frontmost app information available");
    }
}

```

## Summary

- **macOS**: Uses `CGEventTapCreate` in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs) to tap the Core Graphics event stream, with IOKit registry lookups in `event_sender_id` and `sender_device_info` (lines 60-80) to identify scroll event sources, and a watchdog to monitor tap health.
- **Linux**: Implements exclusive **evdev** device access with **uinput** replay in [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs), using per-device threads and pipe-based signaling (lines 45-55) for clean shutdown.
- **Windows**: Leverages `WH_MOUSE_LL` and `WH_KEYBOARD_LL` hooks in [`crates/openlogi-hook/src/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows.rs), running a dedicated message pump thread that can be terminated via `WM_QUIT` (lines 90-100).
- **Common Interface**: All platforms expose the same `Hook::start` API from [`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs), returning `HookEvent` structures and respecting `EventDisposition` responses.

## Frequently Asked Questions

### How does OpenLogi-Hook distinguish between trackpad and mouse wheel events on macOS?

According to the [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs) implementation, when processing scroll events, the tap extracts the underlying `IOHIDEvent` and queries the IOKit registry via `event_sender_id` and `sender_device_info` (lines 60-80). This allows the hook to identify the physical device type, distinguishing between a traditional mouse wheel and a trackpad gesture before forwarding the event to the callback.

### Why does the Linux backend use both evdev and uinput instead of simple interception?

The Linux implementation in [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs) grabs input devices exclusively using **evdev** to ensure events cannot reach the display server while the hook is processing them. However, to maintain system functionality for events the user wishes to allow, the library re-injects them through a **uinput** virtual device. This dual-device approach ensures clean suppression of blocked events while preserving the behavior of permitted ones.

### What system permissions are required to run OpenLogi-Hook on macOS?

macOS requires the application to have **Accessibility permissions** granted in System Preferences. The `Hook::has_accessibility()` method checks for these rights before initializing the `CGEventTap`. Without this permission, `CGEventTapCreate` will fail silently or return null, preventing the hook from receiving system events.

### How do I safely stop the hook on Windows without crashing the application?

The Windows backend in [`crates/openlogi-hook/src/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows.rs) handles shutdown by posting a `WM_QUIT` message to the dedicated `hook_thread` (lines 90-100). When the `Hook` object is dropped, it triggers this mechanism automatically, causing the `GetMessageW` loop to exit and `SetWindowsHookExW` hooks to unhook. Manual interruption of the thread is unnecessary and potentially unsafe; rely on the RAII pattern via `hook.stop()` or allow the `Hook` to drop naturally.