How OpenLogi's Input Hook Architecture Differs Across macOS, Linux, and Windows

OpenLogi implements three completely different input capture pipelines—CGEventTap on macOS, evdev on Linux, and low-level Windows hooks—each tailored to the native event APIs of the host OS.

The crates/openlogi-hook crate provides the low-level input hook that powers OpenLogi's cross-platform device management. Rather than forcing a single abstraction onto every operating system, the architecture embraces platform-native mechanisms. This article examines how the OpenLogi input hook architecture differs on macOS, Linux, and Windows, with reference to specific source files and implementation details from the AprilNEA/OpenLogi repository.

macOS Input Hook: CGEventTap Event Tapping

On macOS, OpenLogi leverages Quartz Event Services to intercept hardware events before applications receive them.

Core Implementation in macos.rs

The macOS hook centers on CGEventTap, a system-wide event interception mechanism. In crates/openlogi-hook/src/macos.rs, the implementation creates a low-level event tap at the kCGHIDEventTap location—the earliest possible injection point in macOS's event stream.

Key characteristics of the macOS approach:

  • Pre-delivery interception: The tap receives raw CGEvent structures for all mouse and keyboard activity before any app processes them
  • HID++ device visibility: Positioning at kCGHIDEventTap ensures OpenLogi captures events from Logitech's HID++ protocol devices
  • TCC permission awareness: macOS requires explicit user consent for accessibility features; the hook must handle permission revocation gracefully

Watchdog Architecture in macos/watchdog.rs

To address macOS's Transparency, Consent, and Control (TCC) requirements, OpenLogi includes a dedicated watchdog in crates/openlogi-hook/src/macos/watchdog.rs. This helper monitors the tap's health and automatically restarts it if the system revokes permissions—ensuring the input hook remains resilient against macOS security policy changes.

use openlogi_hook::macos::MacosHook;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialise the global CGEvent tap; this blocks until the tap is ready.
    let hook = MacosHook::new()?;
    hook.run()?;               // Runs the event‑loop on the current thread.
    Ok(())
}

(Source: crates/openlogi-hook/src/macos.rs)

Linux Input Hook: Direct evdev Access with Compositor Integration

Linux presents a fundamentally different challenge: no single "global hook" API exists. OpenLogi's Linux input hook architecture operates at the kernel level by reading directly from device nodes.

evdev-Based Event Collection in linux.rs

The primary implementation in crates/openlogi-hook/src/linux.rs opens and reads from /dev/input/event* nodes. This approach:

  • Bypasses the display server entirely for raw hardware access
  • Reads input_event structs directly from the kernel's input subsystem
  • Relies on openlogi-device-registry for device filtering and discovery

Wayland Window Focus Integration

Unlike macOS and Windows, Linux window management is fragmented across display servers. OpenLogi implements compositor-specific protocols to determine the active window:

Compositor Source File Mechanism
GNOME (X11/Wayland) linux/gnome_shell.rs DBus interface to GNOME Shell
Wayland (wlroots-based) linux/wlr_foreign_toplevel.rs wlr_foreign_toplevel protocol

The wlr_foreign_toplevel.rs module implements the Wayland foreign-toplevel protocol, allowing OpenLogi to discover which window has focus without relying on X11-specific APIs.

Synthetic Input via uinput

When OpenLogi needs to emit synthesized events—such as DPI-cycle commands or profile switches—it writes to the uinput subsystem rather than evdev. This creates virtual input devices that applications cannot distinguish from physical hardware.

use openlogi_hook::linux::LinuxHook;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Open the first matching evdev node for the Logitech device.
    let mut hook = LinuxHook::new()?;
    // Process events until the program is stopped.
    hook.run()?;
    Ok(())
}

(Source: crates/openlogi-hook/src/linux.rs)

Windows Input Hook: Low-Level Message Hooks

Windows provides no equivalent to macOS's system-wide event taps or Linux's raw device nodes. Instead, OpenLogi relies on user-mode hook callbacks for input capture.

WH_MOUSE_LL and WH_KEYBOARD_LL in windows.rs

The Windows implementation in crates/openlogi-hook/src/windows.rs registers two distinct hooks via SetWindowsHookExW:

  • WH_MOUSE_LL: Captures low-level mouse events before they reach any window procedure
  • WH_KEYBOARD_LL: Captures low-level keyboard events system-wide

These hooks operate at the Windows message level, not the HID protocol level. This architectural constraint means:

  • OpenLogi sees Windows messages (WM_MOUSEMOVE, WM_KEYDOWN) rather than raw HID reports
  • No driver installation is required for basic operation
  • The hook must run in a process with UI access rights

Threading Model in windows_worker.rs

Windows low-level hooks impose a critical requirement: the hook procedure must not block, or the entire system's input processing stalls. OpenLogi's solution in crates/openlogi-hook/src/windows_worker.rs uses a dedicated worker thread that:

  • Receives callbacks from the Windows hook infrastructure
  • Immediately forwards events to the shared IPC channel
  • Maintains responsiveness to prevent Windows from unhooking the application
use openlogi_hook::windows::WindowsHook;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Register the WH_MOUSE_LL and WH_KEYBOARD_LL callbacks.
    let hook = WindowsHook::new()?;
    // The worker thread will keep the hook alive.
    hook.run()?;
    Ok(())
}

(Source: crates/openlogi-hook/src/windows.rs)

Platform Comparison: Why the Architectures Differ

The OpenLogi input hook architecture diverges across platforms due to fundamental OS design differences:

Factor macOS Linux Windows
Native API CGEventTap evdev + uinput WH_MOUSE_LL / WH_KEYBOARD_LL
Abstraction Level System event stream Kernel device nodes User-mode message hooks
Permission Model TCC (watchdog-monitored) udev rules for /dev/input UI access rights
Window Focus CGWindowListCopyWindowInfo Compositor-specific protocols GetForegroundWindow
Synthetic Input CGEvent posting uinput device creation SendInput API

API Availability Drives Design

  • macOS provides a purpose-built, system-wide event interception API (CGEventTap) that no other platform offers
  • Linux exposes everything through the filesystem (/dev/input/*), requiring direct kernel communication
  • Windows restricts user-mode code to message-level hooks, preventing raw HID access without kernel drivers

Permission and Security Models

Each OpenLogi input hook architecture reflects its host OS's security philosophy:

  • macOS uses explicit user consent with runtime revocation—hence the watchdog pattern
  • Linux relies on traditional Unix permissions on device nodes
  • Windows uses capability-based access through UI privileges

Key Source Files and Module Structure

File Platform Responsibility
crates/openlogi-hook/src/macos.rs macOS CGEventTap initialization and event loop
crates/openlogi-hook/src/macos/watchdog.rs macOS TCC permission monitoring and tap recovery
crates/openlogi-hook/src/linux.rs Linux evdev device enumeration and raw event reading
crates/openlogi-hook/src/linux/gnome_shell.rs Linux GNOME Shell active window detection via DBus
crates/openlogi-hook/src/linux/wlr_foreign_toplevel.rs Linux Wayland foreign-toplevel protocol implementation
crates/openlogi-hook/src/windows.rs Windows WH_MOUSE_LL/WH_KEYBOARD_LL hook registration
crates/openlogi-hook/src/windows_worker.rs Windows Non-blocking hook callback processing
crates/openlogi-hook/src/lib.rs All Compile-time platform selection via #[cfg(target_os = …)]

The unified public API in lib.rs selects the appropriate module at compile time, presenting a consistent interface despite the divergent implementations.

Summary

  • macOS uses CGEventTap at kCGHIDEventTap for pre-delivery interception, with a watchdog for TCC resilience
  • Linux reads raw events from /dev/input/event* nodes via evdev, integrating with compositor-specific protocols for window focus
  • Windows relies on WH_MOUSE_LL and WH_KEYBOARD_LL message hooks running in a dedicated worker thread
  • All three implementations feed into a shared IPC channel, maintaining architectural consistency at the integration layer
  • The crates/openlogi-hook source files reflect each platform's native capabilities rather than forcing abstraction where none exists

Frequently Asked Questions

What makes macOS's input hook different from Windows?

macOS provides CGEventTap, which intercepts raw HID events before any application receives them. Windows only offers message-level hooks (WH_MOUSE_LL/WH_KEYBOARD_LL) that see Windows messages rather than raw hardware events. This means OpenLogi on macOS captures events earlier in the pipeline than on Windows.

Why does OpenLogi need compositor-specific code on Linux?

Linux has no standardized API for querying the active window. X11 provided this historically, but Wayland intentionally omits it for security. OpenLogi implements GNOME Shell DBus integration in gnome_shell.rs and the wlr_foreign_toplevel protocol in wlr_foreign_toplevel.rs to support modern Wayland compositors.

Can the Windows hook capture events without administrator privileges?

Yes, the low-level hooks (WH_MOUSE_LL/WH_KEYBOARD_LL) do not require administrator rights. However, the process must have UI access rights—typically meaning it runs in an interactive session with a visible window station. The hook cannot capture events across secure desktop boundaries, such as during UAC prompts.

How does OpenLogi handle permission revocation on macOS?

The macos/watchdog.rs module implements a health monitoring thread that detects when macOS's TCC subsystem revokes accessibility permissions. Upon detection, it automatically attempts to re-establish the CGEvent tap, allowing OpenLogi to recover when the user re-grants permission without requiring a full application restart.

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 →