How OpenLogi Synthesizes Input Events on macOS, Linux, and Windows

OpenLogi delegates cross-platform input event synthesis to the openlogi-inject crate, which translates abstract actions into native OS API calls—Core Graphics on macOS, uinput on Linux, and SendInput on Windows.

The AprilNEA/OpenLogi project implements a unified input remapping system that requires robust synthesizing input events across heterogeneous platforms. By isolating platform-specific generation logic into dedicated modules within the openlogi-inject crate, the codebase maintains clean separation between core binding logic and low-level system interaction.

macOS: Core Graphics Event Injection

On macOS, the implementation resides in crates/openlogi-inject/src/inject/macos.rs. This module leverages the Core Graphics framework through the core_graphics crate to construct and post CGEvent structures directly to the window server.

Keyboard and Media Key Synthesis

Standard key events are generated using CGEvent::new_keyboard_event with virtual-key codes and modifier flags (CGEventFlags). For media keys—including volume controls and play/pause—the code utilizes special NX key constants (NX_KEYTYPE_*) that map to macOS system media controls.

Mouse and Scroll Events

Mouse clicks require creating paired mouse-down and mouse-up events using CGEvent::new_mouse_event, posted at the current cursor location via CGEventTapLocation::HID. Scroll events support three quantization strategies through dedicated ScrollQuantizer instances—LINE_SCROLL_QUANTIZER, PIXEL_SCROLL_QUANTIZER, and SMOOTH_SCROLL_QUANTIZER—which convert abstract deltas into CGEventType::ScrollWheel events with custom phase constants (SCROLL_PHASE, MOMENTUM_PHASE).

Native System Actions

Functions such as mission_control(), show_desktop(), and sleep_system() invoke private macOS SPIs (System Programming Interfaces) or external utilities like pmset, as no public CGEvent types exist for these workspace-level actions.

Linux: Virtual uinput Devices

Linux synthesis is handled in crates/openlogi-inject/src/inject/linux.rs using the uinput subsystem. The crate creates a virtual input device (via the uinput crate) that appears as legitimate hardware under /dev/input.

The implementation writes uinput::event::Keyboard and uinput::event::Relative events to this virtual device, conforming to the standard Linux input-event protocol. This ensures compatibility with both Xorg and Wayland compositors. Scroll deltas are transmitted as relative wheel events (Relative::Wheel). The module exports action_device_path, allowing users to configure the specific device file path for the synthetic input device.

Windows: SendInput API Implementation

Windows event generation lives in crates/openlogi-inject/src/inject/windows.rs, utilizing the native SendInput API through the winapi crate.

For keyboard synthesis, the code constructs INPUT structures of type INPUT_KEYBOARD populated with virtual-key codes and modifier flags. Mouse events use INPUT_MOUSE structures with button flags such as MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, and coordinate data. Scroll events are emitted by setting MOUSEEVENTF_WHEEL with the appropriate delta value.

Architecture Flow: From Action to OS Event

The synthesis pipeline follows a strict dispatch pattern defined in crates/openlogi-core/src/binding.rs:

  1. Action Definition – User remappings or scripts instantiate openlogi_core::binding::Action variants (shortcuts, mouse buttons, scroll deltas).
  2. Execution Dispatch – The execute method routes the action to openlogi_inject::execute.
  3. Platform Selection – Inside the injector, a #[cfg(target_os = "...")] match selects the appropriate platform module (macos, linux, or windows).
  4. Event Posting – The selected module builds native event structures and posts them to the OS. Events may be tagged with SYNTHETIC_EVENT_USER_DATA to allow OpenLogi’s own event tap to identify and filter out recursive inputs.

Practical Code Examples

use openlogi_core::binding::{Action, Shortcut};
use openlogi_inject::execute;

// Simulate Cmd+C (copy) on macOS or Ctrl+C on Linux/Windows
let action = Action::from(Shortcut::Copy);
execute(&action);
use openlogi_core::binding::{Action, MouseButton};

// Simulate a middle-click (button 2) across all platforms
let action = Action::from(MouseButton::Middle);
execute(&action);
use openlogi_core::binding::{Action, ScrollDelta};

// Simulate smooth scrolling of 3 vertical lines
let action = Action::Scroll { dx: 0.0, dy: 3.0 };
execute(&action);

Summary

  • OpenLogi isolates platform-specific input generation in the openlogi-inject crate, enabling consistent cross-platform behavior.
  • macOS utilizes the Core Graphics framework (core_graphics crate) with CGEvent structures, supporting keyboard, mouse, and quantized scroll events.
  • Linux creates virtual input devices via the uinput subsystem, writing standard kernel input events compatible with major display servers.
  • Windows calls the native SendInput API to synthesize INPUT_KEYBOARD and INPUT_MOUSE structures directly into the message queue.
  • The architecture supports synthetic event tagging (SYNTHETIC_EVENT_USER_DATA) to prevent input loops when OpenLogi is monitoring its own output.

Frequently Asked Questions

How does OpenLogi prevent infinite loops when synthesizing its own input events?

OpenLogi tags synthetic events with SYNTHETIC_EVENT_USER_DATA during the posting phase. When the system’s event tap encounters an input bearing this metadata, it recognizes the event as internally generated and selectively ignores it, preventing recursive processing cycles.

Can OpenLogi synthesize macOS-specific system actions like Mission Control?

Yes. While standard keys and mouse events use public Core Graphics APIs, native macOS workspace actions such as mission_control(), show_desktop(), and sleep_system() are implemented in crates/openlogi-inject/src/inject/macos.rs by invoking private SPIs or external utilities like AppleScript and pmset, as no public CGEvent types exist for these functions.

What permissions are required for Linux input synthesis?

The uinput-based implementation requires write access to /dev/uinput or the specific device path configured via action_device_path. Typically, this necessitates either running the application with elevated privileges or adding the user to the input group, depending on the distribution’s udev rules.

Why does OpenLogi use SendInput rather than SendMessage on Windows?

SendInput inserts events directly into the system’s raw input stream at a low level, ensuring that synthetic inputs are indistinguishable from physical hardware to most applications and games. In contrast, SendMessage targets specific window handles and operates at the message queue level, which many modern applications ignore or handle differently than physical input.

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 →