# OpenLogi Architecture: A Deep Dive into the Three-Tier Rust Implementation

> Explore the OpenLogi architecture, a three-tier Rust implementation with a GUI client, background Agent for device I/O, and shared logic connected via tarpc IPC.

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

---

**OpenLogi implements a three-tier native application architecture in Rust, separating concerns into a GUI IPC client, a background Agent service for device I/O, and shared orchestration logic connected via versioned tarpc IPC.**

The AprilNEA/OpenLogi repository provides a sophisticated framework for customizing Logitech input devices through a strictly modular design. Understanding the OpenLogi architecture reveals how the project achieves cross-platform compatibility while maintaining strict separation between user interface rendering and privileged hardware operations. The codebase organizes functionality into discrete crates that communicate through well-defined, versioned interfaces rather than direct coupling.

## The Three-Tier Architectural Model

OpenLogi organizes its runtime into three distinct tiers that communicate exclusively through Inter-Process Communication (IPC) rather than shared memory or direct linking.

**GUI Tier** – A pure IPC client built with GPUI that renders both the desktop interface and the overlay. According to the implementation in [`crates/openlogi-desktop/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/main.rs), this process polls the Agent for device state but never performs direct hardware I/O, ensuring UI responsiveness remains decoupled from blocking device operations.

**Agent Tier** – A background server process that owns all input hooks and device I/O. Implemented in [`crates/openlogi-agent/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/main.rs), this binary runs the `openlogi-agent-core` runtime, managing the input hook lifecycle, HID++ writes, DPI cycling, and Actions-Ring session state in a privileged context separate from the user interface.

**Shared Orchestration** – Core logic residing in `crates/openlogi-core` and `crates/openlogi-agent-core` that both GUI and Agent depend upon. This tier contains pure data types, TOML configuration parsing, and device models, deliberately excluding async I/O dependencies to maximize testability and compile-time safety.

## Modular Crate Organization

The implementation follows a "single responsibility" principle, with each crate under the `crates/` directory serving a specific technical function documented in the repository's [`AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/AGENTS.md).

### Data and Configuration

- **`crates/openlogi-core`** – Contains pure data types including TOML configuration structures, device models, and the action catalog. This crate performs no I/O and contains no async code, making it suitable for unit testing without hardware dependencies.

- **`crates/openlogi-assets`** – Manages the device-render registry and cached asset fetching, separating resource management from business logic.

### Hardware and Protocol Layers

- **`crates/openlogi-hidpp`** – A vendored fork of the HID++ protocol crate providing low-level device communication primitives.

- **`crates/openlogi-hid`** – Handles device discovery and HID++ writes using the `async-hid` library, abstracting the async I/O layer from higher-level business logic.

- **`crates/openlogi-permissions`** – Reads system-level permission status, including macOS TCC database checks and Linux device-file probes, ensuring the Agent can verify accessibility privileges before attempting hardware access.

### Platform-Specific Input Handling

- **`crates/openlogi-hook`** – Implements OS-specific input capture using compile-time target selection. The source code utilizes `#[cfg(target_os = "macos")]` for CGEventTap, Linux for evdev+uinput, and Windows for WH_MOUSE_LL hooks.

- **`crates/openlogi-inject`** – Provides input synthesis capabilities across platforms, implementing CGEvent for macOS, uinput+MPRIS for Linux, and SendInput for Windows.

### Communication and Interface

- **`crates/openlogi-ipc`** – Defines the tarpc-based IPC contract in [`src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/ipc.rs). This crate implements the local-socket transport used by all tiers, with a wire format that is versioned and append-only to ensure backward compatibility.

- **`crates/openlogi-cli`** – Provides the command-line interface using `clap` subcommands (`list`, `assets`, `diag`), implemented in [`src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/main.rs).

- **`crates/openlogi-ui`** – Contains shared UI code including ring geometry, icons, GPUI asset sources, and locale negotiation, used by both the desktop application and overlay.

### Application Binaries

- **`crates/openlogi`** – A thin CLI wrapper providing the `openlogi-cli` binary entry point.

- **`crates/openlogi-agent`** – The background service binary that runs the hook and device I/O server.

- **`crates/openlogi-desktop`** – The primary desktop GUI application built with GPUI.

- **`crates/openlogi-overlay`** – A cursor-centered Actions-Ring client that operates as a pure IPC client without direct hardware access.

- **`xtask`** – Workspace-wide build and packaging automation helper invoked via `cargo xtask`.

## Inter-Process Communication Design

The coordination between GUI, Agent, and Overlay relies on a robust IPC mechanism defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs). The system uses **tarpc** for RPC communication over local Unix sockets, establishing a client-server model where the Agent acts as the single source of truth for device state.

The wire protocol follows an append-only versioning strategy. Because new fields are only added and never removed, newer clients maintain interoperability with older agents. This design decouples release cycles between the GUI and Agent components while preventing breaking changes in production environments.

## Cross-Platform Compilation Strategy

All platform-specific code is gated behind conditional compilation attributes rather than runtime checks. The architecture uses `#[cfg(target_os = …)]` directives to select appropriate backends at compile time.

For example, [`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs) selects CGEventTap on macOS, evdev+uinput on Linux, and WH_MOUSE_LL on Windows during the compilation phase. This approach eliminates runtime overhead for platform detection and allows the compiler to exclude irrelevant code paths, producing smaller binaries with fewer dependencies per target platform.

## Practical Implementation Examples

### Listing Devices via the CLI Interface

The `openlogi-cli` crate provides immediate device introspection without requiring the GUI or long-running Agent service:

```bash

# List all discovered Logitech HID++ devices

openlogi list

```

This subcommand is implemented in [`crates/openlogi-cli/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-cli/src/main.rs), leveraging the core device discovery logic to enumerate connected hardware.

### Querying the Agent via IPC Client

Applications can interact with the running agent through the typed IPC client provided by `crates/openlogi-ipc`:

```rust
use openlogi_ipc::client::OpenLogiClient;
use openlogi_ipc::protocol::DeviceInfo;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Connect to the local socket that the agent creates
    let mut client = OpenLogiClient::connect().await?;

    // Ask the agent for the current device list
    let devices: Vec<DeviceInfo> = client.get_devices().await?;
    for d in devices {
        println!("{} – {}", d.serial_number, d.model);
    }
    Ok(())
}

```

The `OpenLogiClient::connect()` method handles local socket discovery, while the protocol definitions in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) ensure type safety across process boundaries.

### Creating Custom Actions in the UI

The `openlogi-ui` crate provides primitives for extending the Actions-Ring with custom behavior without modifying hardware-level code:

```rust
use openlogi_ui::action::Action;
use openlogi_ui::ring::Ring;

fn main() {
    // Create a new action that runs a command
    let my_action = Action::ShellCommand("xdotool key ctrl+alt+T".into());

    // Insert it into the ring UI (shown by the overlay)
    let mut ring = Ring::new();
    ring.add_action(my_action);
    ring.show();
}

```

This example demonstrates how UI components remain decoupled from hardware operations, relying entirely on the IPC layer to execute privileged actions through the Agent.

## Summary

- OpenLogi implements a **three-tier architecture** separating GUI presentation, Agent-based device I/O, and shared orchestration logic into distinct processes.
- **Modular crate organization** under `crates/` enforces single-responsibility principles, with specific crates handling HID++ protocols (`openlogi-hidpp`), input hooks (`openlogi-hook`), and UI rendering (`openlogi-desktop`).
- **Versioned IPC via tarpc** over local sockets ensures stable communication between components, with append-only wire formats in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) maintaining backward compatibility.
- **Compile-time platform abstraction** using `#[cfg(target_os = …)]` enables native performance on macOS, Linux, and Windows without runtime overhead.
- The Agent binary maintains exclusive control over input hooks and device writes, while the GUI operates as an unprivileged IPC client, preventing UI latency from affecting input capture performance.

## Frequently Asked Questions

### How does OpenLogi maintain compatibility between GUI and Agent versions?

The IPC protocol defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) uses an append-only versioning strategy for its wire format. New fields are added to the protocol definitions without removing existing ones, allowing newer GUI clients to communicate with older Agent servers without breaking changes.

### What prevents the GUI from causing input latency?

The architecture strictly separates device I/O into the Agent tier. The GUI operates as a **pure IPC client** that polls the Agent but never performs direct hardware operations. By isolating input hook processing to the background Agent process via `crates/openlogi-hook`, the GUI remains responsive regardless of device communication latency or blocking system calls.

### Which crate handles platform-specific input capture?

Input capture is implemented in [`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs) using compile-time conditional compilation. The crate selects CGEventTap for macOS, evdev+uinput for Linux, and WH_MOUSE_LL for Windows during the build process, ensuring optimal native performance for each target platform.

### Can I interact with OpenLogi without running the desktop GUI?

Yes. The `crates/openlogi-cli` package provides the `openlogi` binary with subcommands including `list`, `assets`, and `diag`. These commands interact directly with the device discovery and asset systems without requiring the GPUI-based desktop frontend or the background Agent service to be running.