How OpenLogi Handles Input Capture on macOS, Linux, and Windows
OpenLogi uses a single public API openlogi_hook::Hook that abstracts platform-specific backends—CGEventTap on macOS, evdev/uinput on Linux, and WH_MOUSE_LL/WH_KEYBOARD_LL hooks on Windows—to capture low-level mouse, scroll, and keyboard events through a unified Rust interface.
The OpenLogi input capture system provides a cross-platform Rust API for monitoring low-level HID events across macOS, Linux, and Windows. According to the AprilNEA/OpenLogi source code, the implementation isolates OS-specific mechanisms behind the HookBackend trait in the crates/openlogi-hook crate, allowing applications to subscribe to mouse movements, button presses, scroll events, and keyboard input using a single consistent interface regardless of the host operating system.
The Unified Hook API
The public interface for OpenLogi input capture resides in crates/openlogi-hook/src/lib.rs. Applications interact with the Hook struct, which provides a platform-agnostic API for installing and managing input hooks.
To begin capturing events, call Hook::start() with a closure that receives HookEvent enums and returns an EventDisposition:
- HookEvent: An enum variant containing either
MouseEventorKeyEventdata structures with normalized fields like position, button states, and scroll delta. - EventDisposition: The callback must return either
PassThroughto allow the event to reach the system, orSuppressto block it from other applications.
The Hook struct uses cfg_select! at compile time to instantiate the appropriate HookBackend implementation for the target platform, ensuring zero-cost abstraction with no runtime overhead for the dispatch logic.
Platform-Specific Backend Implementations
While the API remains consistent, each operating system requires distinct low-level mechanisms for intercepting input events.
macOS: CGEventTap and Accessibility Requirements
On macOS, OpenLogi input capture relies on a CGEventTap inserted at the HID (Human Interface Device) system location, implemented in crates/openlogi-hook/src/macos.rs. The backend spawns a dedicated thread running a CFRunLoop to service the tap callback.
Key implementation details include:
- Accessibility Permissions: The hook requires the Accessibility privacy grant. The
Hook::has_accessibility()method verifies permissions by callingAXIsProcessTrustedand probing a filtering tap viacan_filter_eventsto ensure the process can actually intercept events. - Event Translation: Native
CGEventobjects are translated intoMouseEventorKeyEventstructs through thetranslateandtranslate_keyfunctions, normalizing macOS-specific data into the cross-platform format. - Watchdog Threads: To ensure reliability,
spawn_callback_watchdogmonitors callback execution time to prevent system stalls, whilespawn_lifecycle_watchdogmonitors the tap's health. - Foreground Application Tracking: The implementation observes
NSWorkspaceDidActivateApplicationNotificationviawatch_frontmost_application_activationsto track which application currently has focus, enabling context-aware input processing.
Linux: evdev Device Grabbing and uinput Re-injection
The Linux backend in crates/openlogi-hook/src/linux.rs implements OpenLogi input capture through direct evdev interface access, bypassing the display server for low-level hardware events.
The architecture functions as follows:
- Device Enumeration: The backend discovers mouse devices under
/dev/input/and spawns a worker thread for each physical device, enabling simultaneous multi-mouse support. - Event Grabbing: Each thread performs an exclusive grab on its evdev device, preventing the events from reaching the standard input subsystem while the hook is active.
- Re-injection via uinput: When the user callback returns
PassThrough, events are re-injected into the system through a uinput virtual device, ensuring other applications receive the input. IfSuppressis returned, the event is simply dropped. - Permissions Model: Unlike macOS, no special privacy permission is required; the process only needs read/write access to device nodes, typically achieved by adding the user to the
inputgroup. - Scroll Handling: Scroll-wheel events carry both line-based and pixel-based delta data via
ScrollDelta, mirroring the macOS semantics for consistent cross-platform behavior.
Windows: Low-Level Mouse and Keyboard Hooks
On Windows, OpenLogi uses WH_MOUSE_LL and WH_KEYBOARD_LL low-level hooks installed via SetWindowsHookExW, implemented across crates/openlogi-hook/src/windows.rs and crates/openlogi-hook/src/windows_worker.rs.
The Windows implementation distinguishes itself through:
- User-Mode Operation: No additional permissions are required beyond standard user privileges, as low-level hooks are standard Windows APIs available to all processes.
- State Machine Architecture: The
windows_worker.rsmodule defines a robust worker thread managed by a state machine usingWorkerPhaseandWorkerEventenums, handling thread startup, graceful shutdown, and error recovery. - Synthetic Event Filtering: To prevent recursive event loops, the backend checks for
openlogi_inject::SYNTHETIC_EVENT_USER_DATAin the event's user-data field, filtering out any events generated by OpenLogi itself while processing physical hardware input. - Message Loop Integration: The hook runs on a background thread that pumps a standard Windows message loop, required for low-level hook callbacks to function correctly.
Cross-Platform Event Flow
Regardless of the underlying platform, OpenLogi input capture follows a consistent four-phase lifecycle:
- Installation: Calling
Hook::start(cb)instantiates the platform-specific backend and begins the capture thread. The backend initializes native resources (CGEventTap, evdev files, or Windows hooks). - Event Loop: The backend thread captures native OS events, translates them into
HookEventstructures, and invokes the user-supplied callback on the hook's dedicated thread. - Disposition: The callback examines the event and returns
EventDisposition::PassThroughto allow the event to continue (or be re-injected on Linux) orEventDisposition::Suppressto block it from the rest of the system. - Shutdown: Calling
Hook::stop()or dropping theHookobject signals the backend to terminate the worker thread, release OS resources (closing taps, ungrabbing devices, or unhooking Windows procedures), and perform clean exit.
Implementation Example
The following Rust code demonstrates initializing the hook with proper macOS permission checks and handling both mouse and keyboard events:
use openlogi_hook::{Hook, HookEvent, EventDisposition};
fn main() -> Result<(), openlogi_hook::HookError> {
// Ensure the process has the required permission on macOS.
if !Hook::has_accessibility() {
Hook::prompt_accessibility();
eprintln!("Please grant Accessibility permission and restart.");
return Ok(());
}
// Install the hook. The closure runs on the hook's background thread.
let hook = Hook::start(|event| {
match event {
HookEvent::Mouse(me) => {
println!("Mouse event: {:?}", me);
EventDisposition::PassThrough
}
HookEvent::Key(ke) => {
println!("Key event: {:?}", ke);
EventDisposition::PassThrough
}
}
})?;
// The hook runs until the program exits or `hook.stop()` is called.
// … do other work here …
// When finished:
hook.stop();
Ok(())
}
You can also retrieve the current foreground application or cursor position using auxiliary functions:
// Get the frontmost application (macOS & Windows)
if let Some(app) = openlogi_hook::frontmost_application() {
println!("Frontmost app: {} ({})", app.display_name, app.id);
}
// Get cursor coordinates (macOS & Windows; None on Wayland)
if let Some(pos) = openlogi_hook::cursor_position() {
println!("Cursor at ({}, {})", pos.x, pos.y);
}
Summary
- OpenLogi exposes a unified
HookAPI incrates/openlogi-hook/src/lib.rsthat compiles to different backends per platform usingcfg_select!. - macOS requires Accessibility permissions and uses
CGEventTapwith dedicated watchdog threads to monitor callback execution and tap lifecycle. - Linux utilizes
evdevfor reading from/dev/inputanduinputfor re-injection, requiring membership in theinputgroup rather than special privacy grants. - Windows employs
WH_MOUSE_LLandWH_KEYBOARD_LLlow-level hooks managed by a state machine worker thread that filters synthetic events viaSYNTHETIC_EVENT_USER_DATA.
Frequently Asked Questions
What permissions does OpenLogi require for input capture on macOS?
On macOS, OpenLogi requires the Accessibility privacy grant. The Hook::has_accessibility() method checks AXIsProcessTrusted and probes a filtering tap via can_filter_events to verify the process can intercept events. You can prompt users with Hook::prompt_accessibility() if access is not granted, and the application should guide users to System Preferences to enable the permission.
How does OpenLogi handle multiple input devices on Linux?
OpenLogi supports simultaneous input devices by spawning a dedicated worker thread for each device discovered under /dev/input/. The implementation exclusively grabs each evdev device to prevent events from reaching other applications, then uses a separate uinput virtual device to re-inject events when the callback returns PassThrough. This architecture ensures that multiple mice can operate concurrently without event loss.
Does OpenLogi filter its own synthetic events on Windows?
Yes. On Windows, OpenLogi prevents feedback loops by checking for the openlogi_inject::SYNTHETIC_EVENT_USER_DATA marker in the event's user-data field. This identifier distinguishes between physical hardware events and events generated programmatically by OpenLogi itself, allowing the hook to suppress its own synthetic output while processing genuine user input.
Which input types does OpenLogi support on each platform?
OpenLogi captures mouse-button presses, scroll events, pointer movements, and keyboard events on macOS and Windows. On Linux, the current implementation focuses on mouse devices via evdev, capturing button states, pointer coordinates, and scroll-wheel data with both line and pixel precision through the ScrollDelta struct. Keyboard support on Linux varies based on specific evdev configurations and device permissions.
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 →