Platform-specific Input Handling in OpenLogi: macOS, Linux, and Windows Implementation

OpenLogi unifies cross-platform input capture and synthesis through a HookBackend trait that abstracts OS-native mechanisms including CGEventTap on macOS, evdev/uinput on Linux, and low-level Windows hooks.

OpenLogi implements a sophisticated input pipeline that bridges operating system differences while respecting platform security models. The openlogi-hook crate defines a common interface that enables consistent platform-specific input handling across macOS, Linux, and Windows without sacrificing low-level control. This article examines the architecture in the AprilNEA/OpenLogi repository, detailing how each backend captures hardware events and injects synthetic input.

The HookBackend Trait Abstraction

At the core of OpenLogi's input system lies the HookBackend trait defined in crates/openlogi-hook/src/lib.rs. This trait establishes a contract for platform-specific implementations, exposing methods for event iteration and input injection. The factory function HookBackend::new() selects the appropriate concrete backend at compile time, returning a unified interface regardless of the target operating system.

All backends implement the same event model using structs like HookEvent, MouseEvent, and KeyEvent. This design allows higher-level application code to process input uniformly while the underlying platform handles OS-specific nuances.

Platform-Specific Backend Implementations

macOS: CGEventTap and Core Graphics

The macOS backend in crates/openlogi-hook/src/macos.rs utilizes CGEventTap from Apple's Core Graphics framework. The implementation spawns a private thread running a CFRunLoop to host the event tap, which intercepts mouse and keyboard events at the HID (Human Interface Device) level.

To identify specific hardware sources, the code calls CGEventCopyIOHIDEvent and IOHIDEventGetSenderID to extract the originating device identifier. The backend includes a watchdog mechanism that monitors the tap lifecycle, automatically re-enabling it if macOS disables the tap during system sleep or permission changes. Users must grant Accessibility permissions, which the code verifies using AXIsProcessTrusted.

Linux: evdev and uinput Integration

The Linux implementation in crates/openlogi-hook/src/linux.rs employs the evdev kernel interface to grab physical mouse devices and uinput to re-inject synthetic events. The backend enumerates available input devices using evdev::enumerate(), then claims exclusive access to relevant hardware.

For output, the system constructs a virtual device via build_virtual_device and translates raw evdev::InputEvent structures into the common MouseEvent and KeyEvent abstractions. The implementation also supports high-resolution scrolling wheels. Unlike macOS, this backend requires no special user-grantable permissions beyond standard device file access for /dev/input/* nodes.

Windows: Low-Level Hook APIs

The Windows backend in crates/openlogi-hook/src/windows.rs installs WH_MOUSE_LL and WH_KEYBOARD_LL hooks via SetWindowsHookExW. These low-level hooks run on a dedicated thread that pumps a Windows message loop, capturing input before it reaches target applications.

For synthetic input generation, the backend uses the Win32 SendInput API. The implementation can distinguish between physical and synthetic events by examining parameters passed to the hook callbacks, preventing infinite loops when injecting events. No additional privacy permissions are required beyond standard user privileges.

Cross-Platform Event Model

All three backends translate native OS events into a shared representation. The HookEvent enum variants wrap platform-specific data into MouseEvent and KeyEvent structs containing normalized fields like position coordinates, key states, and device identifiers. This abstraction ensures that calling code in crates/openlogi-hook/src/lib.rs remains agnostic to the underlying platform while receiving rich, structured input data.

Implementing Input Capture and Injection

Initializing the Platform Backend

The following example demonstrates starting a hook and processing events across all supported platforms:

use openlogi_hook::{HookBackend, HookEvent, HookError};

fn main() -> Result<(), HookError> {
    // Create the platform-specific backend; the concrete type is selected at compile time
    let mut backend = HookBackend::new()?;

    // Run the event loop; each iteration yields a high-level HookEvent
    while let Some(event) = backend.next_event()? {
        match event {
            HookEvent::Mouse(m) => println!("mouse: {:?} at {:?}", m.kind, m.position),
            HookEvent::Key(k)   => println!("key:   {:?} {}", k.kind, k.is_down),
        }
    }
    Ok(())
}

Synthesizing macOS Input Events

For injecting keyboard shortcuts on macOS, the implementation uses Core Graphics events:

use openlogi_inject::inject::Inject;
use core_graphics::event::{CGEvent, CGEventFlags, CGEventTapLocation};

fn send_cmd_shift_c() {
    // Create a source for HID events
    let src = core_graphics::event_source::CGEventSource::new(
        core_graphics::event_source::CGEventSourceStateID::HIDSystemState,
    ).unwrap();

    // Command + Shift + C (keycode 8)
    let mut ev = CGEvent::new_keyboard_event(&src, 8, true).unwrap();
    ev.set_flags(CGEventFlags::CGEventFlagCommand | CGEventFlags::CGEventFlagShift);
    ev.post(CGEventTapLocation::HID);

    // Release the keys
    let mut ev_up = CGEvent::new_keyboard_event(&src, 8, false).unwrap();
    ev_up.set_flags(CGEventFlags::CGEventFlagCommand | CGEventFlags::CGEventFlagShift);
    ev_up.post(CGEventTapLocation::HID);
}

Injecting Linux Input Events

On Linux, synthetic input utilizes the evdev and uinput interfaces:

use openlogi_inject::inject::Inject;
use evdev::{InputEvent, InputEventKind};

fn click_left() {
    // Build a synthetic button press
    let press = InputEvent::new(
        evdev::EventType::KEY,
        evdev::KeyCode::BTN_LEFT.0,
        InputEventKind::Key(evdev::KeyState::Pressed),
    );
    
    // Followed by a release
    let release = InputEvent::new(
        evdev::EventType::KEY,
        evdev::KeyCode::BTN_LEFT.0,
        InputEventKind::Key(evdev::KeyState::Released),
    );

    // Forward these to the uinput device
    Inject::inject_linux(&[press, release]).unwrap();
}

Summary

  • OpenLogi uses a trait-based abstraction in crates/openlogi-hook/src/lib.rs to unify platform-specific input handling across operating systems.
  • The macOS backend leverages CGEventTap with Core Graphics APIs and requires Accessibility permissions verified via AXIsProcessTrusted.
  • The Linux implementation utilizes evdev for hardware capture and uinput for injection, requiring only standard device file access without elevated privileges.
  • The Windows backend employs WH_MOUSE_LL and WH_KEYBOARD_LL hooks with SendInput for synthetic event generation and differentiates physical from synthetic input.
  • All platforms share common event structs (HookEvent, MouseEvent, KeyEvent) for consistent higher-level processing regardless of OS.

Frequently Asked Questions

Does OpenLogi require root privileges on Linux?

No, OpenLogi on Linux only requires standard device file access permissions for evdev and uinput nodes. While some distributions may require adding the user to the input group, the implementation does not require full root privileges to capture or synthesize events.

How does the macOS backend handle system sleep and accessibility permission changes?

The macOS implementation includes a watchdog mechanism that monitors the CGEventTap lifecycle. If the system disables the tap during sleep or due to permission revocation, the code automatically re-enables the tap to ensure continuous input handling without requiring application restart.

Can the Windows backend distinguish between physical and synthetic input events?

Yes, the Windows implementation in crates/openlogi-hook/src/windows.rs uses hook callback parameters to differentiate between real hardware events and synthetic events generated via the SendInput API. This prevents the system from processing its own injected input as new raw events.

What Rust trait enables cross-platform input handling in OpenLogi?

The HookBackend trait defined in crates/openlogi-hook/src/lib.rs provides the common interface for all platforms. The factory method HookBackend::new() selects the appropriate concrete implementation at compile time, enabling zero-cost abstraction across macOS, Linux, and Windows.

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 →