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_click generates paired mouse-down and mouse-up events
  • Keyboard chords: post_keycombo builds virtual key codes via hid_usage_to_macos and flag masks via combo_flags, then posts down/up phases through post_key_phase
  • Scroll gestures: dispatch_scroll uses CGEvent::new_scroll_event with quantization helpers to preserve sub-pixel precision
  • Media keys: post_media_key handles system media controls
  • Window manager actions: Mission Control and Dock commands use the private CoreDockSendNotification SPI

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_KEY for keyboard keys and mouse buttons
  • Relative motion: EV_REL for mouse movement and scroll wheels (REL_WHEEL, REL_HWHEEL)
  • Absolute positioning: EV_ABS where 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 in inject.rs), using KEYBDINPUT structures with virtual-key codes
  • Mouse input: Constructed with MOUSEINPUT structures, utilizing flags like MOUSEEVENTF_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-inject crate to abstract platform-specific input APIs behind a unified Action interface.
  • macOS leverages CGEvent with CGEventSource, tagging synthetic events with SYNTHETIC_EVENT_USER_DATA to prevent feedback loops.
  • Linux employs uinput virtual devices emitting EV_KEY and EV_REL events through a lazy-initialized /dev/uinput node.
  • Windows utilizes SendInput with arrays of INPUT structures 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →