# How OpenLogi's Architecture Is Structured: A Three-Tier Native Application Design

> Discover OpenLogi's three-tier native architecture. Learn how separate Rust processes communicate via a robust IPC protocol for efficient operation.

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

---

**OpenLogi implements a three-tier native architecture where the GUI, Agent, and Overlay run as separate Rust processes that communicate via a versioned, append-only IPC protocol built on tarpc and bincode over local sockets.**

OpenLogi is an open-source Rust application for managing Logitech HID++ devices across macOS, Linux, and Windows. Understanding how OpenLogi's architecture is structured reveals a deliberate separation of concerns that isolates sensitive device I/O operations from UI rendering while maintaining strict forward compatibility through its wire protocol design. The codebase is organized as a Cargo workspace with each architectural tier residing in its own crate under the `crates/` directory.

## The Three-Tier Process Model

OpenLogi runs three distinct native processes that communicate via IPC, each packaged together but operating independently.

### GUI Tier: The Rendering Client

The **GUI tier** lives in `crates/openlogi-desktop` and `crates/openlogi-ui`. This is a pure IPC client that contains no device I/O logic. It renders the interface using GPUI and communicates with the Agent over a local socket. The entry point is [`crates/openlogi-desktop/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/main.rs).

The GUI handles:
- The ring UI for quick actions
- Settings and configuration panels
- Actions menu presentation

Because it contains no direct hardware access, the GUI cannot crash the device layer, ensuring stability.

### Agent Tier: The Hardware Controller

The **Agent tier** in `crates/openlogi-agent` serves as the background server that owns all device interactions. According to the source code in [`crates/openlogi-agent/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/main.rs), this process manages:
- The global input hook (via `openlogi-hook`)
- All HID++ device I/O
- DPI and SmartShift handling
- Device pairing and unpairing
- Per-application profile management

The `crates/openlogi-agent-core` crate provides the shared orchestration runtime that glues together the input hook, HID++ writes, DPI cycling, and Actions Ring session state.

### Overlay Tier: The Lightweight Accessor

The **Overlay tier** in `crates/openlogi-overlay` is a lightweight IPC client that shows a cursor-centred Actions Ring for quick access. Unlike a child process, the Overlay is a **sibling** to the GUI. Both link against `openlogi-ui`, ensuring UI dependencies propagate automatically to the Overlay. Its entry point is [`crates/openlogi-overlay/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/main.rs).

## Device Abstraction Stack

Between the Agent and the hardware lies a carefully layered device stack.

### HID++ Protocol Layer

The `crates/openlogi-hidpp` crate contains a vendored fork of the upstream HID++ protocol implementation, providing the enums and parsing logic required to communicate with Logitech devices. Sitting above this, `crates/openlogi-device` handles HID++-level device operations including enumeration, probing, writes, sessions, and pairing, abstracted over a generic `HidBackend` trait.

### Transport and Backend

The concrete host transport lives in `crates/openlogi-hid`, which implements the async communication layer. This crate handles:
- async-hid integration
- macOS input monitoring
- Probe caching mechanisms

The transport layer ensures that `openlogi-device` remains platform-agnostic by hiding OS-specific implementation details behind the `HidBackend` abstraction.

### Hardware Registry

`crates/openlogi-device-registry` maintains the hardware identity registry describing receiver protocols and driver metadata, while `crates/openlogi-assets` manages the device-render registry and cached fetches from OpenLogi asset mirrors.

## Inter-Process Communication Protocol

The GUI, Agent, and Overlay communicate via **tarpc** using **bincode** serialization over an `interprocess` local socket. The wire format is strictly **append-only** and versioned to ensure forward compatibility.

Key implementation details from [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) include:
- The first RPC method (`protocol_version`) is fixed at index 0 for takeover handshake
- Subsequent RPC methods append only to the end of the enum, never reordering or removing
- Changes to the wire format bump `PROTOCOL_VERSION`, checked strictly at connection time

This design allows old clients to connect to new agents safely, provided the protocol version handshake succeeds.

```rust
// GUI → Agent: request the current DPI setting
use openlogi_ipc::client::OpenLogiClient;
let mut client = OpenLogiClient::connect().await?;
let dpi = client.get_dpi().await?;      // RPC method defined in `openlogi-ipc`
println!("Current DPI: {}", dpi);

```

```rust
// Agent → Device: set a new DPI on a Logitech device
use openlogi_device::Device;
let device = Device::open_by_serial("XYZ123")?;
device.set_dpi(1200)?;                 // Calls into `openlogi-hid` transport

```

```rust
// Overlay (lightweight client) shows the Actions Ring
use openlogi_ui::ring::Ring;
let ring = Ring::new();
ring.show_at_cursor();                 // Pure UI; no device I/O

```

## System Integration and Platform Abstraction

Platform-specific functionality lives behind Cargo **cfg-gates** (`#[cfg(target_os = "...")]`) within each relevant crate. This architecture keeps core crates like `openlogi-core`, `openlogi-device`, and `openlogi-hid` completely OS-agnostic.

### Input Handling

The `crates/openlogi-hook` crate captures OS-specific input:
- **macOS**: CGEventTap
- **Linux**: evdev + uinput
- **Windows**: WH_MOUSE_LL (experimental)

Similarly, `crates/openlogi-inject` synthesizes input using CGEvent (macOS), uinput/MPRIS (Linux), and SendInput (Windows).

### Permission Management

`crates/openlogi-permissions` reads privacy-permission status (macOS TCC, Linux device-file probes) without prompting, allowing the Agent to detect accessibility permissions before attempting hook installation.

## Build Automation

The `xtask` crate in [`xtask/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/xtask/src/main.rs) provides Cargo-xtask utilities for packaging, bundling, and release manifest generation, orchestrating the complex multi-process build pipeline.

## Summary

- **Three-tier separation**: GUI, Agent, and Overlay run as independent processes with the Agent owning all hardware access
- **Versioned IPC**: tarpc/bincode protocol with append-only method enums and strict `PROTOCOL_VERSION` checking ensures forward compatibility
- **Platform abstraction**: OS-specific code isolated via `#[cfg]` gates in hook, inject, and transport crates while core libraries remain agnostic
- **Crate organization**: 15+ specialized crates separate concerns between UI (`openlogi-ui`), device logic (`openlogi-device`), transport (`openlogi-hid`), and data types (`openlogi-core`)
- **Entry points**: [`crates/openlogi-agent/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/main.rs), [`crates/openlogi-desktop/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/main.rs), and [`crates/openlogi-overlay/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/main.rs) launch the respective processes

## Frequently Asked Questions

### What is the three-tier architecture in OpenLogi?

OpenLogi's three-tier architecture consists of the **GUI** (rendering client), **Agent** (background hardware controller), and **Overlay** (lightweight quick-access UI). Each runs as a separate native process with the Agent owning all device I/O, while the GUI and Overlay act as pure IPC clients. This separation prevents UI crashes from affecting device operations and allows the Agent to run with different permissions than the interface.

### How does OpenLogi maintain forward compatibility during IPC communication?

The IPC protocol uses an **append-only** enum design where new RPC methods are added only at the end, and existing method indices never change. The first method (`protocol_version`) remains fixed at index 0 for handshake purposes. Any breaking change to the wire format increments `PROTOCOL_VERSION`, which is strictly checked at connection time in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs). This ensures old clients can safely connect to newer agent versions if the protocol version matches.

### Where does platform-specific code live in OpenLogi's codebase?

Platform-specific implementations reside in dedicated crates using Rust's `#[cfg(target_os = "...")]` gates. The `openlogi-hook` crate handles input capture differently for macOS (CGEventTap), Linux (evdev), and Windows (WH_MOUSE_LL), while `openlogi-inject` manages input synthesis. Core crates like `openlogi-core` and `openlogi-device` contain no platform-specific code, relying instead on the transport abstractions provided by `openlogi-hid`.

### Why is the Agent separated from the GUI in OpenLogi's architecture?

The Agent runs as a background process to maintain **input hook ownership** and **device I/O privileges** independently of the UI lifecycle. Since input hooks and HID++ device access require elevated or specific permissions (particularly on macOS with TCC), isolating this functionality prevents GUI crashes or restarts from dropping hardware connections. Additionally, this allows the Overlay to function as a lightweight sibling process rather than a child window, both communicating with the persistent Agent.