How OpenLogi Implements Cross-Platform Input Synthesis: Windows, macOS, and Linux
OpenLogi synthesizes input across operating systems through platform-specific backends in the openlogi-inject crate, utilizing WinAPI SendInput on Windows, Quartz CGEvent on macOS, and uinput virtual devices on Linux.
OpenLogi is an open-source input remapping and automation framework that requires robust cross-platform input synthesis capabilities. The project handles input synthesis through a unified abstraction layer that translates platform-neutral Effect descriptions into native OS APIs, ensuring consistent behavior whether injecting keyboard events on Windows or scrolling gestures on macOS.
Architecture of the Input Synthesis Layer
The openlogi-inject crate provides a single public entry point for all synthetic input operations. Located in crates/openlogi-inject/src/lib.rs, the core execute() function accepts a platform-neutral Effect enum and dispatches to OS-specific implementations:
/// Execute a platform-neutral `Effect` (key press, mouse click, scroll, etc.).
pub fn execute(effect: Effect) -> Result<(), InjectError>;
The Effect type, defined in the openlogi-core crate, describes actions such as KeyPress, MouseClick, Scroll, and SmoothScroll. When a binding triggers, the agent calls openlogi_inject::execute(), which routes to the appropriate backend module based on the target operating system.
Windows Input Synthesis via SendInput
On Windows, OpenLogi utilizes the WinAPI SendInput function to inject keyboard and mouse events. The implementation resides in crates/openlogi-inject/src/inject/windows.rs, where the backend constructs INPUT structures containing either KI (keyboard input) or MI (mouse input) unions.
This approach allows the framework to synthesize hardware scan codes and virtual key codes directly into the system message queue, supporting both instantaneous keystrokes and complex mouse movements with scroll wheel deltas.
macOS Input Synthesis via Quartz CGEvent
For macOS, the framework leverages the Quartz Event Services API, specifically CGEvent creation and posting functions. The implementation in crates/openlogi-inject/src/inject/macos.rs generates events of types such as kCGEventKeyDown, kCGEventKeyUp, and kCGEventScrollWheel.
The macOS backend also provides specialized handling for smooth scrolling through the post_smooth_scroll() function, which constructs scroll events with precise delta values and phase information (Began, Changed, Ended) to mimic trackpad gesture behavior.
Linux Input Synthesis via uinput and evdev
On Linux, OpenLogi implements input synthesis through the uinput kernel interface. The code in crates/openlogi-inject/src/inject/linux.rs manages a lazily-initialized virtual input device that emits EV_KEY, EV_REL, and EV_ABS events via the event device subsystem.
The backend first attempts to read from physical evdev devices to understand the current input state, then creates a virtual device with a unique DEVICE_NAME identifier to emit synthetic events. This method provides low-level kernel integration without requiring X11 or Wayland-specific protocols.
Preventing Event Feedback Loops
A critical requirement for any input synthesis system is preventing injected events from re-triggering the application's own input hooks. OpenLogi solves this by tagging synthetic events with platform-specific markers that the hook subsystem recognizes and filters.
Each backend implements a distinct tagging strategy:
- Windows: Flags injected events in the
LowLevelKeyboardProcandLowLevelMouseProchooks withincrates/openlogi-hook/src/windows.rs - macOS: Assigns a custom
SYNTHETIC_EVENT_USER_DATAvalue toCGEventobjects, checked by the event tap callback incrates/openlogi-hook/src/macos.rs - Linux: Identifies events originating from the virtual device's unique name in
crates/openlogi-hook/src/linux.rs
These checks ensure that synthetic input never circulates back through the capture loop, eliminating infinite recursion during complex remapping operations.
Practical Implementation Examples
The following examples demonstrate input synthesis using the unified OpenLogi API across different platforms.
Injecting a Keyboard Keystroke
This cross-platform example presses and releases the "A" key:
use openlogi_inject::{self, Effect};
fn main() {
// Press and release the "A" key (HID usage ID 0x04)
let effect = Effect::KeyPress {
usage_id: 0x04,
modifiers: 0,
};
openlogi_inject::execute(effect).expect("input synthesis failed");
}
The same code executes on all three operating systems; the openlogi-inject crate automatically selects the appropriate backend.
Synthesizing Mouse Scroll on Linux
To inject relative scroll events on Linux:
use openlogi_inject::{self, Effect};
fn main() {
// Scroll up by 3 lines
let effect = Effect::Scroll {
delta_x: 0,
delta_y: -3,
};
openlogi_inject::execute(effect).unwrap();
}
On Linux, this creates EV_REL events with REL_WHEEL codes through the uinput virtual device initialized in crates/openlogi-inject/src/inject/linux.rs. The device is created on first use to minimize system resource consumption.
Smooth Scrolling on macOS
For gesture-based smooth scrolling on macOS:
use openlogi_inject::{self, SmoothScrollPhase};
fn main() {
// Begin the scroll gesture
openlogi_inject::post_smooth_scroll(5, SmoothScrollPhase::Began).unwrap();
// Continue scrolling
openlogi_inject::post_smooth_scroll(5, SmoothScrollPhase::Changed).unwrap();
// Terminate the gesture
openlogi_inject::post_smooth_scroll(0, SmoothScrollPhase::Ended).unwrap();
}
The macOS backend in crates/openlogi-inject/src/inject/macos.rs translates these calls into CGEvent objects with appropriate scroll wheel deltas and flags them with the synthetic event user data to prevent hook re-processing.
Summary
- OpenLogi centralizes input synthesis in the
openlogi-injectcrate, exposing a singleexecute()function that works across Windows, macOS, and Linux. - Windows uses WinAPI
SendInputwithINPUTstructures, implemented incrates/openlogi-inject/src/inject/windows.rs. - macOS uses Quartz
CGEventAPIs for keyboard, mouse, and smooth scroll injection, implemented incrates/openlogi-inject/src/inject/macos.rs. - Linux uses the
uinputkernel interface for virtual device event emission, implemented incrates/openlogi-inject/src/inject/linux.rs. - The framework prevents feedback loops by tagging synthetic events with OS-specific markers detected by the separate
openlogi-hookcrate (windows.rs,macos.rs, andlinux.rs).
Frequently Asked Questions
How does OpenLogi prevent synthetic events from triggering infinite loops?
OpenLogi tags every synthesized event with a platform-specific identifier before injection. On macOS, this is the SYNTHETIC_EVENT_USER_DATA field in CGEvent objects; on Linux, it is the unique DEVICE_NAME of the virtual uinput device; on Windows, it is a flag checked in the low-level hook procedures. The openlogi-hook crate checks for these markers and filters out tagged events before they reach the processing pipeline.
Can OpenLogi synthesize input on Wayland-based Linux distributions?
Yes. Because the Linux input synthesis backend operates at the kernel level using uinput and evdev interfaces, it functions independently of display servers. The implementation in crates/openlogi-inject/src/inject/linux.rs does not rely on X11 or Wayland protocols, ensuring compatibility across all Linux environments that support the input event subsystem.
What is the performance overhead of using CGEvent versus SendInput?
Both APIs provide low-latency input synthesis suitable for real-time remapping. SendInput on Windows and CGEvent on macOS both operate within the operating system's event processing queues with microsecond-level latency. The primary overhead in OpenLogi comes from the Rust-to-FFI boundary and the Effect translation layer, which remains negligible for human-input speeds (under 1 ms per event).
Does the execute() function block until the input is delivered?
The execute() function in crates/openlogi-inject/src/lib.rs returns a Result immediately after queuing the event with the OS. On Windows and macOS, this corresponds to the return of SendInput or CGEventPost. On Linux, it occurs after writing to the uinput device file descriptor. The function does not wait for the event to be processed by target applications, ensuring non-blocking operation suitable for high-frequency input automation.
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 →