How OpenLogi's Three-Process Architecture Separates GUI, Agent, and Overlay
OpenLogi implements a strict separation of concerns across three distinct binaries—openlogi-desktop (GUI), openlogi-agent (device controller), and openlogi-overlay (cursor ring)—that communicate exclusively through a tarpc-based IPC contract defined in crates/openlogi-ipc, ensuring presentation layers never perform direct HID I/O.
The OpenLogi repository organizes its functionality into separate OS processes to isolate device I/O operations from user interface rendering. This architecture prevents UI crashes from affecting device state and allows the overlay to function independently of the settings application.
The Three Core Processes
OpenLogi ships three separate binaries that run side-by-side, each with a distinct role and crate location:
GUI Process (openlogi-desktop)
Located in crates/openlogi-desktop, this process renders the settings application using GPUI and gpui-component. It never communicates directly with devices. Instead, it acts as a pure IPC client that sends configuration requests to the agent. According to the contract documented in crates/openlogi-desktop/AGENTS.md, the GUI code intentionally excludes platform-specific HID crates to enforce this boundary.
Agent Process (openlogi-agent)
Located in crates/openlogi-agent, this background server owns the input hook and performs all HID++ I/O operations. It maintains the canonical device state and exposes a tarpc local-socket transport via openlogi_ipc::server::Server. All device-specific logic lives in crates/openlogi-device and crates/openlogi-hid, completely isolated from the GUI and overlay crates.
Overlay Process (openlogi-overlay)
Located in crates/openlogi-overlay, this binary renders the cursor-centered "Actions Ring" interface. Like the GUI, it is a pure IPC client that never performs device I/O itself. It receives ring snapshots from the agent via OverlayCommand messages and renders them using GPUI windows.
IPC Communication Between GUI and Agent
The GUI communicates with the agent through the shared IPC contract defined in crates/openlogi-ipc and documented in AGENTS.md (repo root). When the GUI boots, it creates an IPC client using openlogi_ipc::client::Client to issue requests such as retrieving current device configurations or applying DPI changes.
In crates/openlogi-desktop/src/runtime.rs, the GUI coordinates this client and updates the AppState only after receiving data from the agent. Because the GUI imports no HID crates, it cannot bypass the agent to touch hardware directly. The agent listens on a local socket and performs the actual HID++ reads/writes, keeping all platform-specific code within crates/openlogi-hid.
Overlay as a Pure IPC Client
The overlay binary launches as a sibling process to the GUI (not a child process) and also instantiates openlogi_ipc::client::Client as shown in crates/openlogi-overlay/src/main.rs (lines 31-34). The agent pushes ring snapshots to the overlay via the IPC protocol using OverlayCommand structures. The overlay only renders these snapshots; any user interaction (hover, click, cancel) is sent back to the agent for execution.
This design means crates/openlogi-overlay does not depend on crates/openlogi-desktop, keeping the two presentation processes independent while they share common UI assets.
Shared UI Assets in openlogi-ui
Both the GUI and overlay import the openlogi-ui crate for common presentation elements. Located in crates/openlogi-ui, this crate contains shared assets including icons, ring geometry definitions, and locale catalogs. As documented in crates/openlogi-ui/src/lib.rs and crates/openlogi-ui/Cargo.toml, adding a new icon or UI component to this crate automatically makes it available to both binaries without creating a dependency between the desktop and overlay processes.
Process Lifetime and Role Claiming
When the overlay starts, it claims a unique role by acquiring overlay.lock to ensure only one overlay instance binds to a particular agent run. This mechanism, implemented in crates/openlogi-overlay/src/session.rs (lines 128-144), releases the lock when the overlay process drops, allowing a new instance to claim the role. The GUI does not claim this role; it connects to the agent as a standard IPC client without exclusive binding.
Practical Implementation Example
The following code illustrates how both the GUI and overlay instantiate IPC clients to communicate with the agent:
// ---------- GUI side (openlogi-desktop) ----------
use openlogi_ipc::client::Client;
// Create the IPC client once at start-up
let client = Client::new()?;
// Ask the agent for the current device state
let device_state = client.get_device_state(device_id).await?;
// Render UI based on `device_state`
// ...
// ---------- Overlay side (openlogi-overlay) ----------
use openlogi_ipc::client::Client;
// The overlay also creates a client
let client = Client::new()?;
// Subscribe to ring updates from the agent
let mut ring_stream = client.subscribe_ring_updates()?;
// Render the ring whenever a new snapshot arrives
while let Some(snapshot) = ring_stream.next().await {
render_ring(snapshot);
}
In crates/openlogi-overlay/src/main.rs (lines 35-80), this client spawns the overlay window and handles incoming OverlayCommand messages, while crates/openlogi-desktop/AGENTS.md (lines 40-46) documents how the GUI's runtime coordinates state updates exclusively through this IPC channel.
Summary
- Three separate binaries:
openlogi-desktop(GUI),openlogi-agent(device control), andopenlogi-overlay(cursor ring) run as independent OS processes. - tarpc IPC contract: Defined in
crates/openlogi-ipc, this contract enables communication via local sockets usingopenlogi_ipc::client::Clientandopenlogi_ipc::server::Server. - Pure client architecture: Both GUI and overlay are pure IPC clients that never import HID crates or perform device I/O.
- Single source of truth: The agent maintains canonical device state and handles all HID++ operations.
- Shared UI layer: The
openlogi-uicrate provides common assets to both presentation processes without creating inter-process dependencies. - Role claiming: The overlay uses
overlay.lockinsession.rsto ensure exclusive binding to the agent instance.
Frequently Asked Questions
Why does OpenLogi use three separate processes instead of a single application?
Using three processes isolates device I/O from UI rendering to prevent crashes in the interface from affecting hardware control. The agent runs as a background service that owns all HID++ operations, while the GUI and overlay can restart independently without losing device state or requiring hardware re-initialization.
How does the overlay process claim exclusive access to the agent?
The overlay claims a role by acquiring overlay.lock when starting, as implemented in crates/openlogi-overlay/src/session.rs (lines 128-144). This lock ensures only one overlay instance communicates with the agent at a time. Dropping the lock (either through normal shutdown or crash) releases the role for subsequent overlay instances.
What prevents the GUI from directly accessing HID devices?
The GUI crate explicitly excludes HID dependencies. According to crates/openlogi-desktop/AGENTS.md (lines 16-22), the GUI never imports openlogi-hid or platform-specific HID crates. All hardware access flows through the openlogi_ipc::client::Client to the agent, enforcing the architectural boundary at the compiler level.
How do the GUI and overlay share UI components while remaining separate processes?
Both binaries depend on the openlogi-ui crate, which contains shared assets like icons, ring geometry, and locale catalogs. Because this is a library dependency (not a process dependency), changes to crates/openlogi-ui/src/lib.rs automatically propagate to both the desktop settings app and the overlay ring without requiring the processes to communicate directly or share memory.
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 →