How OpenLogi Captures Input Events on macOS, Linux, and Windows
OpenLogi implements a low-level, cross-platform input-capture layer in the openlogi-hook crate, utilizing CGEventTap on macOS, evdev with uinput on Linux, and WH_MOUSE_LL hooks on Windows to capture device-specific mouse and keyboard events.
OpenLogi captures input events using native OS primitives that provide reliable, low-latency streams of mouse and keyboard data. The project achieves this through the openlogi-hook crate, which abstracts platform-specific APIs into a unified Rust interface that the openlogi-agent consumes. Each operating system implementation runs on a dedicated thread with its own event loop, translating raw system events into a common HookEvent enum.
Cross-Platform Architecture with HookBackend
The HookBackend type defined in crates/openlogi-hook/src/lib.rs serves as the abstraction layer that compiles to platform-specific implementations at build time. When the openlogi-agent initializes, it creates a HookBackend instance that spawns the appropriate OS-specific thread and forwards captured events to the gesture watcher and DPI controller.
All three backends emit events through the HookEvent enum, which normalizes platform differences into uniform MouseEvent, KeyEvent, and ScrollDelta variants. This allows the rest of the system to consume input uniformly regardless of the underlying operating system.
macOS Input Capture Using CGEventTap
On macOS, OpenLogi captures events using CGEventTap from the CoreGraphics framework. The implementation in crates/openlogi-hook/src/macos.rs creates a dedicated thread that runs a CFRunLoop owning the event tap.
The tap is created with CGEventTap::new(CGEventTapLocation::HID, …) and runs in HeadInsertEventTap mode, ensuring OpenLogi receives every mouse event before the system processes it. To differentiate input devices—such as distinguishing a high-resolution mouse wheel from a trackpad—the code calls CGEventCopyIOHIDEvent followed by IOHIDEventGetSenderID to map the CG event back to its originating HID device.
Additionally, a foreground-application observer monitors NSWorkspaceDidActivateApplicationNotification to track the active application, enabling per-application action scoping.
Linux Input Capture Using evdev and uinput
The Linux implementation in crates/openlogi-hook/src/linux.rs captures events through the evdev interface. For each mouse device, a thread opens the device using evdev::enumerate() and calls grab() to take exclusive ownership of the event stream, preventing other applications from receiving the raw input.
Events pass through a translate() function that converts raw evdev data into the unified MouseEvent enum, handling buttons, motion, and scroll data. After processing, OpenLogi re-injects events through a virtual uinput::VirtualDevice so that other applications continue to receive the same input. The implementation specifically distinguishes between wheel devices and trackpad devices to handle high-resolution scroll data correctly.
Windows Input Capture Using Low-Level Hooks
On Windows, OpenLogi installs low-level hooks via SetWindowsHookExW with the WH_MOUSE_LL and WH_KEYBOARD_LL flags, as implemented in crates/openlogi-hook/src/windows.rs. A dedicated hook thread runs a message pump using GetMessageW and DispatchMessageW to keep the hooks alive and receive system callbacks.
The hook callbacks—mouse_proc and keyboard_proc—receive raw MSLLHOOKSTRUCT and KBDLLHOOKSTRUCT data, which the backend converts into OpenLogi’s internal event types. Unlike macOS and Linux, Windows does not expose a device ID through these hooks, so events are treated as coming from the default mouse. The implementation requires no additional permissions but monitors hook enable state and gracefully tears down the hooks on shutdown.
Safety, Device Identification, and Watchdog Logic
Each backend implements device identification appropriate to its platform. macOS extracts the HID sender ID via CGEventCopyIOHIDEvent; Linux reads the input_id from the evdev node; Windows lacks device granularity and aggregates all input.
Lifecycle management is handled by a watchdog defined in crates/openlogi-hook/src/watchdog.rs. The watchdog monitors the hook’s health, re-enabling the tap if macOS disables it, or restarting the thread if the Linux or Windows hook crashes. This ensures continuous input capture even when the OS interferes with the monitoring layer.
Implementation Example
The following Rust code demonstrates how the openlogi-agent initializes the input hook and subscribes to events:
use openlogi_hook::HookBackend;
/// Initialise the OS-level input hook. This spawns the platform-specific
/// thread and returns a handle that can be used to stop the hook.
fn start_input_hook() -> Result<HookBackend, openlogi_hook::HookError> {
// `HookBackend::new()` chooses the implementation at compile time
// (macOS → CGEventTap, Linux → evdev, Windows → WH_MOUSE_LL).
let backend = HookBackend::new()?;
Ok(backend)
}
// In the agent (simplified):
fn main() -> Result<(), Box<dyn std::error::Error>> {
let hook = start_input_hook()?;
// Subscribe to events.
hook.set_event_callback(|event| {
// `event` is a `HookEvent` (MouseEvent, KeyEvent, …)
println!("Captured: {:?}", event);
});
// Run until the agent shuts down…
hook.wait_until_stopped();
Ok(())
}
Summary
- OpenLogi captures input events through the
openlogi-hookcrate, which provides a unified interface over OS-specific APIs. - macOS uses
CGEventTapinHeadInsertEventTapmode withCFRunLoop, extracting device IDs viaIOHIDEventGetSenderIDincrates/openlogi-hook/src/macos.rs. - Linux uses
evdevwith exclusivegrab()and re-injects events viauinput, handling device-specific translation incrates/openlogi-hook/src/linux.rs. - Windows uses
SetWindowsHookExWwithWH_MOUSE_LLandWH_KEYBOARD_LLhooks, running a message pump incrates/openlogi-hook/src/windows.rs. - A watchdog in
crates/openlogi-hook/src/watchdog.rsmonitors hook health and handles re-arming or crash recovery across all platforms. - The
HookBackendAPI incrates/openlogi-hook/src/lib.rsnormalizes all events into theHookEventenum for consumption byopenlogi-agent.
Frequently Asked Questions
How does OpenLogi differentiate between a mouse and a trackpad?
On macOS, OpenLogi calls CGEventCopyIOHIDEvent and IOHIDEventGetSenderID to extract the originating HID device ID from the CGEventTap stream, allowing it to distinguish high-resolution mouse wheels from trackpads. On Linux, the implementation reads the input_id from the evdev node to identify the specific device. Windows does not expose device IDs through the WH_MOUSE_LL hook, so all events are treated as generic mouse input.
Why does OpenLogi need to re-inject events on Linux?
On Linux, OpenLogi calls grab() on the evdev device to take exclusive ownership of the input stream, which prevents other applications from receiving events. To ensure the system remains usable, the backend creates a virtual uinput::VirtualDevice and re-injects processed events back into the kernel, allowing the original input to reach other applications while OpenLogi monitors or modifies the stream.
What happens if the OS disables the input hook?
OpenLogi implements a watchdog mechanism in crates/openlogi-hook/src/watchdog.rs that monitors the health of the input capture layer. If macOS disables the CGEventTap due to permission changes or if the hook thread crashes on Linux or Windows, the watchdog detects the failure and re-enables or restarts the appropriate capture mechanism automatically.
Does OpenLogi require elevated permissions to capture input?
macOS requires Accessibility permissions for CGEventTap to function. Linux requires read/write access to /dev/input/event* devices, typically granted via the input group or running as root. Windows does not require additional permissions for WH_MOUSE_LL or WH_KEYBOARD_LL hooks, though the application must run in the user's session to install the hooks successfully.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →