How OpenLogi Synthesizes Input Using CGEvent, uinput, and SendInput to Replay Remapped Actions
OpenLogi synthesizes native OS input events by translating abstract Action types into platform-specific APIs—CGEvent on macOS, uinput on Linux, and SendInput on Windows—to replay remapped actions with sub-pixel precision.
OpenLogi's input injection pipeline lives in the openlogi-inject crate, which serves as the bridge between high-level gesture remapping logic and low-level operating system input queues. When the GUI or a gesture engine triggers a remapped action, the system calls execute() with an Action from openlogi_core::binding::Action, dispatching to platform-specific backends that synthesize hardware-level events.
The Central Dispatch Architecture
The synthesis pipeline begins in crates/openlogi-inject/src/inject.rs with the execute function:
pub fn execute(action: &Action) { … }
This function handles special cases (like opening applications) and then routes to the appropriate backend using the cfg_select! macro:
cfg_select! {
target_os = "macos" => macos::execute(action),
target_os = "linux" => linux::execute(action),
target_os = "windows" => windows::execute(action),
_ => { … }
}
Each backend translates the abstract Action into native OS event structures, ensuring consistent behavior across macOS, Linux, and Windows.
macOS: CGEvent Synthesis
On macOS, the implementation in crates/openlogi-inject/src/inject/macos.rs leverages Core Graphics (core_graphics crate) to create synthetic hardware events.
Event Source Creation
The system initializes a CGEventSource with CGEventSourceStateID::HIDSystemState to mirror hardware input states. When generating events, the code creates CGEvent objects and posts them to the system event tap.
Handling Different Action Types
The macos::execute function classifies actions into Effect types and dispatches to specialized helpers:
- Mouse clicks:
post_clickgenerates paired mouse-down and mouse-up events - Keyboard chords:
post_keycombobuilds virtual key codes viahid_usage_to_macosand flag masks viacombo_flags, then posts down/up phases throughpost_key_phase - Scroll gestures:
dispatch_scrollusesCGEvent::new_scroll_eventwith quantization helpers to preserve sub-pixel precision - Media keys:
post_media_keyhandles system media controls - Window manager actions: Mission Control and Dock commands use the private
CoreDockSendNotificationSPI
Preventing Feedback Loops
Critical to the implementation, synthetic events are tagged with a unique user-data constant SYNTHETIC_EVENT_USER_DATA. OpenLogi's own event tap checks for this marker to ignore self-generated events, preventing infinite recursion when monitoring global input.
Linux: uinput Virtual Device
The Linux backend in crates/openlogi-inject/src/inject/linux.rs utilizes the uinput subsystem to create a virtual input device.
Device Initialization
The system lazy-initializes a /dev/uinput device node via linux::device_node(), obtaining a file descriptor for emitting kernel input events.
Event Emission
Linux input events are structured using standard event types:
- Key events:
EV_KEYfor keyboard keys and mouse buttons - Relative motion:
EV_RELfor mouse movement and scroll wheels (REL_WHEEL,REL_HWHEEL) - Absolute positioning:
EV_ABSwhere required by the input subsystem
Scroll gestures apply the same quantization logic used on macOS, ensuring consistent scroll distances across platforms when replaying remapped actions.
Windows: SendInput Implementation
On Windows, crates/openlogi-inject/src/inject/windows.rs utilizes the Win32 SendInput API to inject events into the system input queue.
INPUT Structure Construction
The windows::execute function builds arrays of INPUT structures containing:
- Keyboard input: Translated via
hid_usage_to_windows(mapping table defined ininject.rs), usingKEYBDINPUTstructures with virtual-key codes - Mouse input: Constructed with
MOUSEINPUTstructures, utilizing flags likeMOUSEEVENTF_LEFTDOWN,MOUSEEVENTF_LEFTUP,MOUSEEVENTF_RIGHTDOWN, etc.
Atomic Injection
The entire array of INPUT structures passes atomically to SendInput, ensuring that complex chord sequences (like "Win + D") inject as a single transaction rather than interleaved with physical hardware events.
Practical Code Examples
Replaying a Remapped Shortcut on macOS
use openlogi_core::binding::{Action, Shortcut};
let action = Action::from(Shortcut::Copy);
openlogi_inject::execute(&action);
// Injects native CGEvent for Command+C
Injecting a Mouse Button on Linux
use openlogi_core::binding::{Action, MouseButton};
let action = Action::from(MouseButton::Back);
openlogi_inject::execute(&action);
// Writes BTN_SIDE event to /dev/uinput
Sending a Windows Native Action
use openlogi_core::binding::NativeAction;
let action = Action::from(NativeAction::ShowDesktop);
openlogi_inject::execute(&action);
// Calls SendInput with VK_LWIN + 'D' chord
Summary
- OpenLogi uses the
openlogi-injectcrate to abstract platform-specific input APIs behind a unifiedActioninterface. - macOS leverages CGEvent with CGEventSource, tagging synthetic events with
SYNTHETIC_EVENT_USER_DATAto prevent feedback loops. - Linux employs uinput virtual devices emitting
EV_KEYandEV_RELevents through a lazy-initialized/dev/uinputnode. - Windows utilizes
SendInputwith arrays ofINPUTstructures to atomically inject keyboard and mouse events. - All platforms share quantization logic for scroll gestures, ensuring consistent behavior when replaying remapped actions across operating systems.
Frequently Asked Questions
How does OpenLogi prevent infinite loops when synthesizing input?
OpenLogi tags every synthetic event with a unique constant SYNTHETIC_EVENT_USER_DATA when creating CGEvent objects on macOS. The system's own event tap checks for this marker and ignores flagged events, ensuring that injected input does not trigger the gesture engine recursively. This feedback prevention is critical for stable remapping of system shortcuts.
What abstraction layer converts high-level gestures into OS-specific input?
The openlogi_core::binding::Action type serves as the hardware-agnostic intermediate representation. Defined in crates/openlogi-core/src/binding.rs, this enum encompasses shortcuts, mouse buttons, media keys, and native window manager commands. The openlogi_inject::execute function consumes these Action types and dispatches to platform-specific backends that handle CGEvent, uinput, and SendInput translation.
Why does OpenLogi use SendInput on Windows instead of keybd_event?
SendInput supersedes the legacy keybd_event and mouse_event APIs by injecting input at a lower level of the system input queue. Unlike the older functions, SendInput accepts an array of INPUT structures that inject atomically, preventing race conditions with physical hardware. This ensures that complex remapped chords (such as "Alt+Tab" or "Win+D") execute as coherent sequences rather than interleaved with user keypresses.
How does OpenLogi ensure consistent scroll behavior across macOS and Linux?
Both platforms implement shared quantization helpers that convert high-level scroll distances into platform-specific units while preserving sub-pixel precision. On macOS, this involves CGEvent::new_scroll_event with calibrated wheel units; on Linux, the system emits EV_REL events with REL_WHEEL values using the same quantization algorithm. This unified approach ensures that remapped scroll gestures feel identical regardless of the underlying operating system.
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 →