How openlogi-inject Synthesizes OS Input for Action Replay

TLDR: openlogi-inject is the platform-specific crate inside the OpenLogi repository that translates high-level Action enums into native OS events via three backends (macOS CGEvent, Linux uinput, Windows SendInput), allowing OpenLogi to replay user actions across all major operating systems.

OpenLogi is an open-source input automation project by AprilNEA that records and replays user actions. The crate responsible for turning abstract actions into real OS-level events is openlogi-inject. As implemented in the AprilNEA/OpenLogi repository, this crate keeps the core engine platform-agnostic and IO-free, delegating all native input synthesis to platform-specific modules behind a single unified API.

How openlogi-inject Splits Platform Logic from the Core

The architecture deliberately isolates OS-specific code so that the rest of OpenLogi never needs to care about the host operating system. According to the source in crates/openlogi-inject/src/inject.rs, the crate exposes a small public surface:

  • execute(action: &Action) — replays a recorded action.
  • ax_navigate_browser(pid, forward) — triggers browser back/forward via the macOS Accessibility API.
  • post_horizontal_scroll(delta) — re-injects horizontal wheel events.

All three functions are re-exported from crates/openlogi-inject/src/lib.rs, which means every caller in OpenLogi uses the same uniform API regardless of whether it runs on macOS, Linux, or Windows.

Entry Point: The execute() Function and Backend Selection

The public function execute(action: &Action) is defined in crates/openlogi-inject/src/inject.rs. It uses the cfg_select! macro at compile time to route to one of three implementations:

  • macOS → macos::execute
  • Linux → linux::execute
  • Windows → windows::execute

Each backend receives the same input — an Action from openlogi_core::binding::Action — and produces the native events the host OS expects. This design is what makes openlogi-inject action replay work transparently across platforms.

use openlogi_inject::{execute, ax_navigate_browser, post_horizontal_scroll};
use openlogi_core::binding::Action;

/// Replay a stored Action – e.g. a button bound to "Copy".
fn replay_copy() {
    let action = Action::Copy;            // from openlogi_core::binding
    execute(&action);                    // Sends native key-presses
}

macOS Backend: Synthesizing OS Input with CGEvent

The macOS implementation in macos.rs is the most feature-complete because it has direct access to the Cocoa and Accessibility APIs. Here's how it synthesizes OS input:

  • Keyboard events are posted using CGEventPost(kCGHIDEventTap, …) with virtual-key codes from the standard US layout.
  • Mouse clicks (Left/Right/Middle) generate a CGEvent at the current cursor location.
  • Window-manager actions like MissionControl are sent directly to the Dock via CoreDockSendNotification.

A constant SYNTHETIC_EVENT_USER_DATA (value 0x4F4C_4749 — "OLGI") is embedded in every mouse event. This marker is the key to preventing recursive loops: OpenLogi's own CGEventTap reads this value and ignores its own injected events, ensuring the system never re-records its own playback.

Linux Backend: Virtual Devices via uinput

The Linux module in crates/openlogi-inject/src/inject/linux.rs takes a different approach. Because the X11 and Wayland APIs are fragmented, OpenLogi relies on a lazily-created uinput virtual device.

  • Keyboard and scroll events are emitted through uinput (or the evdev EV_REL API for horizontal scroll).
  • Mouse clicks generate BTN_* events.
  • macOS-only window-manager actions (like MissionControl) have no universal Linux equivalent, so they are logged and silently ignored.

Debugging the uinput Device on Linux

On Linux only, a helper function action_device_path() returns the path of the uinput device (e.g., /dev/input/eventN). This is invaluable when you want to verify injected events with tools like evtest while developing gesture-watcher code.

Windows Backend: Native SendInput and Key Mapping

The Windows implementation in crates/openlogi-inject/src/inject/windows.rs uses the native SendInput API to synthesize both keyboard and mouse events. Since Windows has no direct equivalent of macOS's MissionControl or Show Desktop notions, the crate maps them to their Windows counterparts:

  • MissionControl → Win + Tab
  • ShowDesktop → Win + D
  • CustomShortcut converts macOS kVK_* codes to Win32 virtual-key codes via the helper function hid_usage_to_windows.

Supporting Helpers for Gesture-Driven Replay

Two additional functions round out the crate's public API and are especially relevant to gesture watchers:

  • ax_navigate_browser(pid, forward) — uses the macOS Accessibility API (AXPress) to trigger the browser's back/forward toolbar buttons. This is a must-use function when a gesture-watcher wants to navigate history without simulating keystrokes.
  • post_horizontal_scroll(delta) — re-injects horizontal wheel movements, required for MX thumb-wheel gestures on trackpads and thumb-wheels.
/// Move the browser back one page while a gesture is active (macOS only).
fn go_back_in_browser(pid: i32) {
    if ax_navigate_browser(pid, false) {
        println!("Browser back navigation succeeded");
    }
}

/// Re-inject a horizontal scroll generated by a thumb-wheel gesture.
fn inject_scroll(delta: i32) {
    post_horizontal_scroll(delta);       // Platform-specific scroll event
}

Key Source Files in openlogi-inject

File Role
crates/openlogi-inject/src/lib.rs Re-exports the public API (execute, ax_navigate_browser, etc.)
crates/openlogi-inject/src/inject.rs Central dispatcher; defines execute, post_horizontal_scroll, and the synthetic-event marker
crates/openlogi-inject/src/inject/macos.rs macOS implementation – CGEvent generation, Accessibility API, Dock notifications
crates/openlogi-inject/src/inject/linux.rs Linux implementation – uinput device handling, evdev scroll, mouse button events
crates/openlogi-inject/src/inject/windows.rs Windows implementation – SendInput usage, virtual-key mapping
crates/openlogi-inject/examples/inject_action.rs Minimal example showing how to call execute from a standalone binary

Summary

  • openlogi-inject turns the platform-neutral Action enum from openlogi_core into native OS events.
  • The execute() function dispatches at compile time to macOS (CGEvent), Linux (uinput), and Windows (SendInput) backends.
  • macOS embeds a SYNTHETIC_EVENT_USER_DATA marker in all mouse events to prevent infinite loops with its own CGEventTap.
  • Linux replicates the input stream via a lazily-created uinput virtual device; unsupported window-manager features are logged and ignored.
  • Windows maps macOS window-manager semantics (MissionControl, ShowDesktop) to Win32 hotkeys.
  • The same three public functions (execute, ax_navigate_browser, post_horizontal_scroll) are offered everywhere, so the rest of OpenLogi remains uniform across platforms.

Frequently Asked Questions

How does openlogi-inject avoid replaying its own injected events?

On macOS, every injected mouse event carries a constant SYNTHETIC_EVENT_USER_DATA value (0x4F4C_4747). OpenLogi's CGEventTap checks this field and won't process events tagged with it, which prevents recursive loops during action replay.

Is openlogi-inject used only for replaying keyboard and mouse actions?

It controls keyboard, mouse, and window-manager actions. It also exposes ax_navigate_browser() for browser-specific back/forward navigation (macOS only) and post_horizontal_scroll() for thumb-wheel gestures, both of which are used by gesture-watcher code.

What is the actual entry point of openlogi-inject?

The public execute(action: &Action) function in crates/openlogi-inject/src/inject.rs is the central dispatcher. It uses the cfg_select! macro to route to the appropriate platform backend at compile time.

How do I test input injection on Linux?

Use the action_device_path() helper, which is exposed only when target_os = "linux", to get the uinput device path (like /dev/input/eventN). Then you can inspect events with tools like evtest.

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 →