How OpenLogi's Architecture Is Structured: A Three-Tier Native Application Design
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.
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, 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.
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 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.
// 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);
// 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
// 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 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_VERSIONchecking 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,crates/openlogi-desktop/src/main.rs, andcrates/openlogi-overlay/src/main.rslaunch 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. 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →