How OpenLogi Handles Input Hooking Across Windows, macOS, and Linux

OpenLogi implements OS-specific low-level input hooks in the openlogi-hook crate, using SetWindowsHookExW on Windows, CGEventTap on macOS, and evdev/uinput on Linux, unified behind a platform-agnostic Backend API.

OpenLogi is an open-source input management framework that abstracts the complexities of cross-platform input hooking. The openlogi-hook crate contains the platform-specific implementations that capture mouse and keyboard events while exposing a common Rust API through conditional compilation via cfg(target_os = "...") directives.

Windows Input Hooking Implementation

Low-Level System Hooks

On Windows, OpenLogi utilizes SetWindowsHookExW with low-level hooks (WH_MOUSE_LL and WH_KEYBOARD_LL) to capture system-wide input events. The implementation in crates/openlogi-hook/src/windows/hook.rs spawns a dedicated thread that runs a standard Win32 message loop, ensuring the hooks operate in a single-threaded environment as required by the Windows API.

The hook installation follows this pattern: the dedicated thread calls SetWindowsHookExW, forwards captured events to the consumer callback, and ensures clean teardown via UnhookWindowsHookEx when the Backend handle is dropped. This approach prevents the application from requiring elevated privileges while still capturing global input.

Foreground Application Monitoring

Beyond raw input capture, the Windows module tracks the currently active application using a WinEventHook foreground observer. Located in crates/openlogi-hook/src/windows/foreground.rs, this component monitors window focus changes through the EVENT_SYSTEM_FOREGROUND event, allowing OpenLogi to contextually adjust input handling based on which application currently has focus.

macOS Input Hooking Implementation

CoreGraphics Event Taps

The macOS implementation in crates/openlogi-hook/src/macos.rs leverages CGEventTap, the same CoreGraphics primitive used by commercial software like Logitech Options+. This creates a tap on the HID event stream that intercepts system input events before they reach applications.

The implementation creates the event tap via CGEventTapCreate, runs it on a private CFRunLoop thread to avoid blocking the main thread, and translates native CGEvent objects into the unified OpenLogi event format. When CGEventTapCreate returns null, the error indicates missing Accessibility permissions, which users must grant in Security & Privacy settings.

Tap Health Monitoring

macOS automatically disables event taps when the application lacks focus or exhibits performance issues. To handle this, crates/openlogi-hook/src/macos/watchdog.rs implements a watchdog thread that monitors the tap's health and automatically re-arms the tap when the OS disables it. This ensures continuous input capture even when the system attempts to suspend the tap.

Linux Input Hooking Implementation

evdev Event Capture

On Linux, OpenLogi takes a direct approach by reading from /dev/input/event* devices using the evdev protocol. The implementation in crates/openlogi-hook/src/linux.rs opens physical input devices with appropriate permissions and reads raw kernel input events through the evdev crate.

uinput Injection and Loop Prevention

For output functionality, the Linux backend creates a synthetic device via uinput. The implementation uses a specific device name prefix ("openlogi-") when creating the virtual input device. The enumeration logic explicitly ignores any devices carrying this prefix when scanning for input sources, preventing recursive input loops where OpenLogi would capture its own injected events.

Platform-Agnostic API Usage

All three implementations expose a common interface through openlogi_hook::Backend, allowing dependent crates (agent, GUI, overlay) to consume input events without platform-specific code. The lib.rs file documents this public interface, while platform modules compile conditionally based on the target OS.

The following example demonstrates installing a hook that works identically across all supported platforms:

use openlogi_hook::{Backend, HookError};

fn main() -> Result<(), HookError> {
    let callback = |event| {
        println!("Received event: {:?}", event);
    };

    // Automatically selects the correct OS implementation
    let hook = Backend::new(callback)?;
    
    // Hook runs in background; keep handle alive
    std::thread::sleep(std::time::Duration::from_secs(10));
    Ok(())
}

OpenLogi also provides example programs in crates/openlogi-hook/examples/ for debugging and development:

// Print raw hook events
// Usage: cargo run --example print_events
fn main() {
    openlogi_hook::print_events::run();
}
// List active input taps
// Usage: cargo run --example list_taps
fn main() {
    openlogi_hook::list_taps::run();
}

Summary

  • Windows uses SetWindowsHookExW with low-level mouse and keyboard hooks in a dedicated message-loop thread, plus WinEventHook for foreground tracking in foreground.rs.
  • macOS relies on CGEventTap running on a private CFRunLoop, with a watchdog thread in watchdog.rs to handle OS-enforced tap disabling and re-arming.
  • Linux implements raw evdev reading from /dev/input/event* and uinput injection with a "openlogi-" prefix to prevent recursive event loops.
  • All platforms unify behind the Backend API in lib.rs, enabling the rest of the OpenLogi ecosystem to remain platform-agnostic.

Frequently Asked Questions

Does OpenLogi require elevated privileges or root access to capture input?

On Windows and macOS, OpenLogi does not require administrator privileges for basic input hooking, though macOS requires granting Accessibility permissions in Security & Privacy settings. On Linux, reading from /dev/input/event* devices typically requires membership in the input group or root privileges, depending on system udev rules.

How does OpenLogi prevent capturing its own injected events on Linux?

The Linux implementation in linux.rs filters devices by name, specifically ignoring any input devices prefixed with "openlogi-". Since synthetic events injected via uinput use this prefix, the hook logic skips these devices during enumeration, effectively breaking potential feedback loops.

What happens if macOS disables the CGEventTap during operation?

According to the source code in macos/watchdog.rs, a dedicated watchdog thread monitors the event tap's state. If macOS disables the tap—commonly due to timeout or security policies—the watchdog detects the failure and re-arms the tap by recreating it, ensuring continuous operation without user intervention.

Can I use the hook functionality without the rest of the OpenLogi application?

Yes. The openlogi-hook crate is designed as a standalone library with no dependencies on the OpenLogi agent or GUI. You can import the crate directly and use Backend::new() to receive platform-agnostic input events in any Rust application, as demonstrated in the print_events.rs and list_taps.rs examples.

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 →