# How OpenLogi Implements Cross-Platform Input Synthesis: Windows, macOS, and Linux

> Explore how OpenLogi achieves cross-platform input synthesis on Windows macOS and Linux using WinAPI SendInput Quartz CGEvent and uinput virtual devices. Learn the technical details.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: internals
- Published: 2026-08-26

---

**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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-inject/src/lib.rs), the core `execute()` function accepts a platform-neutral `Effect` enum and dispatches to OS-specific implementations:

```rust
/// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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 `LowLevelKeyboardProc` and `LowLevelMouseProc` hooks within [`crates/openlogi-hook/src/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows.rs)
- **macOS**: Assigns a custom **`SYNTHETIC_EVENT_USER_DATA`** value to `CGEvent` objects, checked by the event tap callback in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs)
- **Linux**: Identifies events originating from the virtual device's unique name in [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/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:

```rust
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:

```rust
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`](https://github.com/AprilNEA/OpenLogi/blob/main/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:

```rust
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`](https://github.com/AprilNEA/OpenLogi/blob/main/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-inject` crate, exposing a single `execute()` function that works across Windows, macOS, and Linux.
- **Windows** uses WinAPI `SendInput` with `INPUT` structures, implemented in [`crates/openlogi-inject/src/inject/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-inject/src/inject/windows.rs).
- **macOS** uses Quartz `CGEvent` APIs for keyboard, mouse, and smooth scroll injection, implemented in [`crates/openlogi-inject/src/inject/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-inject/src/inject/macos.rs).
- **Linux** uses the `uinput` kernel interface for virtual device event emission, implemented in [`crates/openlogi-inject/src/inject/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-inject/src/inject/linux.rs).
- The framework prevents feedback loops by tagging synthetic events with OS-specific markers detected by the separate `openlogi-hook` crate ([`windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/windows.rs), [`macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/macos.rs), and [`linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/linux.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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.