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

> Explore how OpenLogi's input hook architecture differs across macOS, Linux, and Windows. Discover the unique implementation for each OS to capture input events effectively.

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

---

**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`](https://github.com/AprilNEA/OpenLogi/blob/main/macos.rs)

The macOS hook centers on `CGEventTap`, a system-wide event interception mechanism. In [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.

```rust
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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/linux.rs)

The primary implementation in [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/linux/gnome_shell.rs) | DBus interface to GNOME Shell |
| Wayland (wlroots-based) | [`linux/wlr_foreign_toplevel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/linux/wlr_foreign_toplevel.rs) | `wlr_foreign_toplevel` protocol |

The [`wlr_foreign_toplevel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/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.

```rust
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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/windows.rs)

The Windows implementation in [`crates/openlogi-hook/src/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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

```rust
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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs) | macOS | CGEventTap initialization and event loop |
| [`crates/openlogi-hook/src/macos/watchdog.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos/watchdog.rs) | macOS | TCC permission monitoring and tap recovery |
| [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs) | Linux | evdev device enumeration and raw event reading |
| [`crates/openlogi-hook/src/linux/gnome_shell.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux/wlr_foreign_toplevel.rs) | Linux | Wayland foreign-toplevel protocol implementation |
| [`crates/openlogi-hook/src/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows.rs) | Windows | WH_MOUSE_LL/WH_KEYBOARD_LL hook registration |
| [`crates/openlogi-hook/src/windows_worker.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows_worker.rs) | Windows | Non-blocking hook callback processing |
| [`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs) | All | Compile-time platform selection via `#[cfg(target_os = …)]` |

The unified public API in [`lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/gnome_shell.rs) and the **wlr_foreign_toplevel protocol** in [`wlr_foreign_toplevel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.