# OpenLogi Input Capture Mechanisms: How It Works on macOS, Linux, and Windows

> Discover OpenLogi's cross-platform input capture mechanisms, using CGEventTap on macOS, evdev on Linux, and Windows hooks to intercept mouse and keyboard events.

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

---

**OpenLogi implements a cross-platform input capture subsystem that uses CGEventTap on macOS, evdev on Linux, and low-level Windows hooks to intercept system-wide mouse and keyboard events.**

The AprilNEA/OpenLogi repository provides a unified input capture layer through the `openlogi-hook` crate. Depending on the operating system, it leverages native low-level APIs to receive raw events before they reach other applications, then translates them into a common `HookEvent` format for the rest of the system.

## Overview of the Input Capture Subsystem

OpenLogi's input capture logic resides entirely within the **`openlogi-hook`** crate. Each platform-specific implementation lives in its own source file under `crates/openlogi-hook/src/`, yet all expose the same public API through the **`HookBackend`** trait. This abstraction allows the agent, GUI, and overlay components to treat input uniformly regardless of whether the underlying system uses Core Graphics, evdev, or Windows hooks.

The trait defines essential methods including `start()`, `stop()`, `cursor_position()`, and `frontmost_app()`, ensuring consistent behavior across macOS, Linux, and Windows.

## macOS Input Capture with CGEventTap

On macOS, OpenLogi taps into the event stream using **Core Graphics** APIs that require Accessibility permissions.

### Core Graphics Event Tap Implementation

In [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs), the system registers a **system-wide `CGEventTap`** that intercepts mouse and keyboard events before the OS delivers them to other applications. The implementation calls `CGEventTapCreate` to establish a passive listener that receives raw `CGEvent` objects. Because this requires elevated privileges, the code checks for `AXIsProcessTrustedWithOptions` to ensure Accessibility permissions are granted before initializing the tap.

### Device Identification and Foreground App Monitoring

Beyond capturing events, the macOS implementation identifies the physical source device using undocumented but stable symbols: **`CGEventCopyIOHIDEvent`** and **`IOHIDEventGetSenderID`**. This allows OpenLogi to distinguish between different keyboards or mice attached to the system.

For tracking context, the code observes **`NSWorkspaceDidActivateApplicationNotification`** via the `watch_frontmost_application_activations` function. This keeps the foreground application information up-to-date, enabling the system to associate input events with the currently focused app.

## Linux Input Capture with evdev and uinput

The Linux implementation takes a direct approach to hardware access through the kernel's input event interface.

### Grabbing Physical Input Devices

Located in [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs), the Linux backend enumerates all input devices via `/dev/input/event*`. For each physical mouse or keyboard detected, it calls **`evdev::Device::grab`** to claim exclusive access to the device. This prevents the events from propagating to the standard input subsystem while OpenLogi processes them.

The code reads raw `evdev` events and translates them into internal `MouseEvent` or `KeyEvent` structures. This low-level access provides precise timing and hardware-specific metadata that higher-level APIs cannot offer.

### Event Re-injection via uinput

When the system needs to inject synthetic events or pass through modified inputs, it creates a virtual device using **`evdev::uinput::VirtualDevice`**. This allows OpenLogi to re-inject processed events back into the system stream so that other applications can receive them. The combination of evdev grab and uinput re-injection creates a complete input pipeline where OpenLogi acts as an intermediary layer.

## Windows Input Capture with Low-Level Hooks

Windows utilizes a message-based hook system rather than direct hardware access.

### WH_MOUSE_LL and WH_KEYBOARD_LL Implementation

In [`crates/openlogi-hook/src/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows.rs), OpenLogi installs global hooks using **`SetWindowsHookExW`** with the **`WH_MOUSE_LL`** and **`WH_KEYBOARD_LL`** constants. These low-level hooks run on a dedicated worker thread (implemented in [`windows_worker.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/windows_worker.rs)) that maintains its own message pump via `GetMessage`/`DispatchMessage`.

When the operating system generates mouse or keyboard input, it delivers the event to OpenLogi's hook procedure before dispatching it to the target window. The hook can then forward the event to the `HookBackend` callback while deciding whether to allow the event to pass through or block it.

### Foreground Application Detection

To determine which application is currently active, the Windows backend calls **`GetForegroundWindow`** followed by **`GetWindowThreadProcessId`** and **`QueryFullProcessImageNameW`**. This chain of API calls resolves the window handle into a full process path and name, matching the functionality provided on macOS through `NSWorkspace` notifications.

## Unified API Across Platforms

Despite the platform differences, all implementations expose identical functionality through the `HookBackend` trait. You can start the capture system with a callback that receives `HookEvent` enums and returns an `EventDisposition` indicating whether to pass the event through or consume it.

```rust
use openlogi_hook::HookBackend;

fn main() {
    // Start the hook; each event is printed to stdout.
    let hook = openlogi_hook::Hook::start(|event| {
        println!("{:?}", event);
        // Let the event pass through unchanged.
        openlogi_hook::EventDisposition::PassThrough
    }).expect("failed to start hook");

    // Keep the process alive while the hook runs.
    std::thread::park();
}

```

Source: [`crates/openlogi-hook/examples/print_events.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/examples/print_events.rs)

The crate also provides utility functions like `list_devices()` (Linux) and `frontmost_app()` (macOS/Windows) for debugging and context-aware automation.

## Summary

- OpenLogi centralizes input capture in the `openlogi-hook` crate, providing a unified `HookBackend` trait across all platforms.
- **macOS** uses `CGEventTap` with Accessibility permissions, device identification via `IOHIDEventGetSenderID`, and `NSWorkspace` for foreground app tracking.
- **Linux** employs `evdev` device grabbing and `uinput` re-injection to intercept and optionally resubmit hardware events.
- **Windows** relies on `SetWindowsHookExW` with `WH_MOUSE_LL` and `WH_KEYBOARD_LL` hooks running on a dedicated message-pump thread.
- All three implementations support synthetic event injection and foreground application detection through their respective native APIs.

## Frequently Asked Questions

### What permissions does OpenLogi require for input capture on macOS?

The macOS implementation requires **Accessibility permissions** (`AXIsProcessTrusted`) to register a system-wide `CGEventTap`. Without this permission, the `CGEventTapCreate` call fails and the hook cannot intercept events. The application must prompt the user to enable this permission in System Settings before the capture mechanism can initialize.

### Why does the Linux implementation use both evdev and uinput?

The Linux backend uses **evdev** to grab exclusive access to physical hardware devices and read raw input events, while **uinput** creates virtual devices to re-inject processed or synthetic events back into the system. This dual approach allows OpenLogi to intercept events before they reach applications (evdev) while still permitting the injection of modified or automated inputs (uinput) that the rest of the system can process normally.

### How does OpenLogi identify which physical device generated an input event?

On macOS, the code uses **`CGEventCopyIOHIDEvent`** and **`IOHIDEventGetSenderID`** to extract the hardware sender ID from the Core Graphics event. On Linux, the `evdev` interface naturally exposes device-specific file descriptors (`/dev/input/event*`), allowing direct correlation between events and their source devices. Windows low-level hooks do not provide native device IDs in the hook structure, so device-specific identification relies on additional enumeration methods outside the hook callback.

### Can OpenLogi filter or modify events before they reach other applications?

Yes. The `Hook::start` callback returns an **`EventDisposition`** enum value (`PassThrough` or `Suppress`). By returning `Suppress`, the implementation can prevent the event from propagating to other applications. On Linux, this works through the `evdev` grab mechanism; on Windows, it uses the hook procedure's return value; on macOS, it depends on the `CGEventTap` callback's ability to return `NULL` or a modified `CGEvent` object.