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

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

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

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.

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 →