How openlogi-inject Synthesizes Input Events: Cross-Platform Implementation Guide

openlogi-inject synthesizes keyboard, mouse, and scroll events by translating high-level Actions into native OS APIs—SendInput on Windows, CGEvent on macOS, and uinput on Linux—through a unified facade that delegates to platform-specific modules.

The openlogi-inject crate in the AprilNEA/OpenLogi repository provides the low-level machinery that turns logical user actions into real operating system input events. This Rust library abstracts away platform differences to offer a consistent API for synthesizing keyboard shortcuts, mouse clicks, and scroll gestures across Windows, macOS, and Linux. Understanding how openlogi-inject synthesizes input events reveals the careful balance between unsafe system calls and safe Rust abstractions required for reliable cross-platform automation.

Architecture of the Injection Facade

The entry point for all synthetic input is crates/openlogi-inject/src/inject.rs, which exposes a platform-agnostic API through functions like execute, post_scroll, and post_smooth_scroll. This module defines the Effect enum—variants that describe keyboard presses, mouse clicks, and relative scroll movements—and uses conditional compilation (#[cfg(target_os = "...")]) to dispatch calls to the appropriate backend. Helper types such as HeldKey, HeldModifiers, KeyPhase, and SmoothScrollPhase capture high-level intent before translation into native structures.

Platform-Specific Implementation Strategies

Windows: Low-Level SendInput Integration

On Windows, the implementation resides in crates/openlogi-inject/src/inject/windows.rs. The code constructs arrays of Win32 INPUT structures that describe keyboard, mouse, or scroll events, then passes them to the SendInput API. An explicitly marked unsafe block around line 345 performs the actual system call, ensuring that raw pointer operations are contained while the public API remains safe.

macOS: CoreGraphics Event Posting

The macOS backend in crates/openlogi-inject/src/inject/macos.rs uses the CGEvent API to synthesize input. For each logical event, the code creates a CGEvent via CGEventCreateKeyboardEvent or CGEventCreateMouseEvent, sets modifier flags, and posts it with CGEventPost. To prevent feedback loops, the injector attaches a SYNTHETIC_EVENT_USER_DATA identifier to every event it creates, allowing the system-wide hook to recognize and ignore its own injections.

Linux: Virtual Device via uinput

For Linux, crates/openlogi-inject/src/inject/linux.rs opens /dev/uinput and creates a virtual input device named “OpenLogi action injector.” The backend writes input_event structures directly to the device file, which the kernel then emits as hardware events. The device is initialized lazily on first use (see the logic around line 187), and the module handles edge cases such as mouse-wheel-only devices by ensuring that injected events are not re-captured by the monitoring hook.

From Action to OS Event: The Execution Pipeline

When an Action reaches the injector, it flows through a structured pipeline:

  1. The execute function in inject.rs receives the Action and translates it into an Effect variant.
  2. Based on the target platform, the code dispatches to windows::execute, macos::execute, or linux::execute.
  3. The platform-specific module builds native event structures—INPUT for Windows, CGEvent for macOS, or input_event for Linux—and posts them to the OS.
  4. The function returns the count of successfully injected events, allowing callers to verify delivery.

For continuous gestures like smooth scrolling, the SmoothScrollPhase type drives post_smooth_scroll, which the macOS and Linux backends use to emit relative wheel events over time rather than discrete jumps.

Code Examples

Injecting a Keyboard Shortcut

use openlogi_inject::execute;
use openlogi_core::binding::action::Action;

// Inject a "Copy" (Ctrl+C) shortcut
let copy_action = Action::shortcut("Copy");
execute(copy_action);

Simulating Smooth Scrolling on macOS

use openlogi_inject::{post_smooth_scroll, SmoothScrollPhase};

let delta = 120; // typical scroll delta
post_smooth_scroll(delta, SmoothScrollPhase::Began);
post_smooth_scroll(delta, SmoothScrollPhase::Changed);
post_smooth_scroll(delta, SmoothScrollPhase::Ended);

Low-Level uinput Event Construction

// Inside crates/openlogi-inject/src/inject/linux.rs
fn inject_key(fd: RawFd, keycode: u16, press: bool) -> io::Result<()> {
    let ev = input_event {
        time: TimeVal { tv_sec: 0, tv_usec: 0 },
        type_: EV_KEY,
        code: keycode,
        value: if press { 1 } else { 0 },
    };
    // Write the event to the uinput device
    write(fd, &ev as *const _ as *const u8, mem::size_of::<input_event>())?;
    Ok(())
}

Key Source Files

File Role
crates/openlogi-inject/src/lib.rs Re-exports the public API (execute, post_scroll, etc.).
crates/openlogi-inject/src/inject.rs Core facade; defines Effect, helper types, and platform dispatch.
crates/openlogi-inject/src/inject/windows.rs Implements injection via Windows SendInput.
crates/openlogi-inject/src/inject/macos.rs Implements injection via macOS CGEvent.
crates/openlogi-inject/src/inject/linux.rs Implements injection via Linux uinput.
crates/openlogi-inject/examples/inject_action.rs Demonstrates calling the injector from a standalone binary.

Summary

  • openlogi-inject uses a facade pattern to expose platform-agnostic functions while hiding OS-specific details.
  • The Windows backend relies on Win32 SendInput with INPUT structures constructed in an unsafe block.
  • The macOS backend posts CGEvent objects and marks them with SYNTHETIC_EVENT_USER_DATA to prevent feedback loops.
  • The Linux backend creates a virtual uinput device to emit kernel-level input_event structures.
  • The pipeline converts high-level Action types into low-level Effect enums before dispatching to native code.

Frequently Asked Questions

What native APIs does openlogi-inject use to synthesize input events on each platform?

On Windows, the crate calls the Win32 SendInput API; on macOS, it uses CoreGraphics CGEventCreateKeyboardEvent and CGEventPost; on Linux, it writes input_event structures to a uinput virtual device.

How does openlogi-inject prevent infinite loops when injecting events?

The macOS implementation attaches a SYNTHETIC_EVENT_USER_DATA flag to every injected event, allowing the system hook to filter out its own events. The Linux backend similarly isolates its virtual device to avoid re-capturing synthesized input.

Can openlogi-inject simulate smooth scrolling gestures?

Yes, the crate provides post_smooth_scroll and the SmoothScrollPhase enum, which allow continuous scroll injection with distinct begin, change, and end phases, mapped to relative wheel events on macOS and Linux.

Is openlogi-inject safe to use within Rust's ownership model?

While the crate necessarily wraps unsafe OS APIs—explicitly marked in windows.rs around line 345—it exposes a memory-safe public API that manages raw file descriptors and input contexts, ensuring that callers never handle uninitialized memory or dangling pointers.

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 →