# Actions Ring Overlay Architecture in OpenLogi: How the IPC-Based UI Works

> Explore the Actions Ring overlay architecture in OpenLogi. Discover how this IPC-based UI handles GPUI rendering and input for a seamless user experience.

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

---

**The Actions Ring overlay is a cursor-centered, eight-slot UI that runs as a separate binary (`openlogi-overlay`) and communicates with the OpenLogi agent via tarpc IPC, handling all GPUI rendering and input forwarding while the agent manages HID++ communication and action execution.**

The Actions Ring overlay architecture enables rapid contextual action selection in the OpenLogi repository through a strict process separation model. This design delegates all visual presentation to a dedicated overlay process while keeping hardware interaction, session validation, and business logic within the main agent. Understanding this architecture reveals how modern Rust applications can achieve responsive, overlay-based UIs without blocking the main application thread.

## Architecture Overview

The overlay operates as a pure IPC client that never directly accesses hardware. According to the OpenLogi source code, the agent maintains exclusive control over HID++ communication, haptic feedback, and action execution, while the overlay binary only renders the interface and forwards user inputs. This separation ensures that UI complexity never interferes with low-l latency hardware handling.

### Process Separation and IPC Design

The agent ([`crates/openlogi-agent/src/overlay.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/overlay.rs)) spawns the overlay via `crate::agent::spawn_ipc`. The overlay immediately registers itself as the sole holder of the Actions Ring role and opens an asynchronous `mpsc` channel structured as `Ipc { invocations, commands }`. This channel receives `ActionRingInvocation` payloads from the agent and sends `OverlayCommand` responses back, creating a bidirectional communication flow over tarpc.

### The Eight-Slot Ring Structure

The UI presents exactly eight action slots arranged circularly around the cursor position. Each slot renders using assets from `openlogi_ui::action_icons::ActionIcons` defined in [`crates/openlogi-ui/src/action_icons.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ui/src/action_icons.rs). The ring appears as a borderless, transparent GPUI window created with `WindowKind::PopUp` options, ensuring it floats above other applications without stealing focus.

## Data Flow and Lifecycle

### 1. Agent Invocation

When the user triggers "Show Actions Ring", the agent constructs an `ActionRingInvocation` payload in [`crates/openlogi-agent/src/overlay.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/overlay.rs). This structure, defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) (lines 385-412), contains the eight slots to display and their associated metadata. The agent populates the slots and transmits the invocation over the IPC channel to the waiting overlay process.

### 2. Overlay Initialization

The overlay binary calls `claim_the_role()` during startup to establish a single-instance lock using a lock file. This guarantees that only one Actions Ring instance runs system-wide, preventing conflicting UI states. Once initialized, the overlay opens the `mpsc` channel and enters its main event loop in [`crates/openlogi-overlay/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/main.rs).

### 3. Window Placement and Rendering

Upon receiving the `ActionRingInvocation`, the overlay executes a precise placement sequence:

- **Locale activation**: Calls `openlogi_core::locale::activate` to ensure proper localization
- **Cursor capture**: Uses `RingPlacement::capture` from [`crates/openlogi-overlay/src/platform.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/platform.rs) to obtain the current cursor position
- **Edge clamping**: Applies `clamp_window_origin` in [`ring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ring.rs) to adjust the window origin when near screen edges, ensuring all eight slots remain fully visible
- **Window creation**: Opens the GPUI window using `ring_window_options()` configured with `WindowKind::PopUp` for a borderless, non-focusable appearance

### 4. User Interaction Handling

Inside the window, `RingView` (from [`crates/openlogi-overlay/src/ring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/ring.rs)) instantiates interactive elements for each slot. Every slot monitors hover states and sends `OverlayCommand::Hover` to the agent when the user moves the cursor over it. On click, the slot dispatches `OverlayCommand::Activate` containing the specific slot index and session ID, then immediately calls `window.remove_window()` to close the UI.

### 5. Command Forwarding and Execution

The overlay forwards all `OverlayCommand` variants through the `commands` sender back to the agent. The agent validates the session ID against the current session state, executes the bound action through its internal handler, and manages any resulting state transitions. The wire format defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) includes version metadata to ensure compatibility between agent and overlay binaries.

### 6. Automatic Dismissal

A background task spawned in [`crates/openlogi-overlay/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/main.rs) waits for `DISPLAY_LIFETIME`, a constant defined in `openlogi_core::action_ring::DISPLAY_LIFETIME`. Upon expiration, the task automatically sends `OverlayCommand::Cancel` to the agent and terminates the window, preventing orphaned UI elements from remaining on screen indefinitely.

### 7. Click-Away Detection

The `ClickAwaySession` struct in [`crates/openlogi-overlay/src/session.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/session.rs) implements a global click monitor. When it detects a mouse click outside the ring's geometric bounds, it immediately dispatches a `Cancel` command to the agent. This ensures intuitive dismissal behavior when users click elsewhere on their desktop.

## Key Implementation Details

**Single-Instance Enforcement**: The `claim_the_role()` function uses a filesystem-level lock to prevent multiple overlay instances. This mechanism is critical for maintaining consistent session state and preventing resource contention.

**Edge-Aware Placement**: The `clamp_window_origin` logic in [`ring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ring.rs) dynamically calculates window positioning based on screen dimensions. When the cursor is positioned near monitor edges, the algorithm adjusts the ring's origin point to ensure all eight slots remain fully visible without extending off-screen.

**Session Validation**: `ClickAwaySession` maintains a strict session ID check, verifying that only the window belonging to the current invocation can send commands. This prevents stale interactions from lingering overlay processes that may not have terminated cleanly.

**IPC Protocol Versioning**: The tarpc-based communication layer in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) defines both `ActionRingInvocation` and `OverlayCommand` with explicit version fields. This allows the agent to detect overlay version mismatches and handle backward compatibility during application updates.

## Code Examples

### Triggering the Actions Ring from the Agent

```rust
use openlogi_ipc::{ActionRingInvocation, ActionRingSlot};

fn show_actions_ring(runtime: &Runtime) {
    // Build the payload that the overlay will render
    let mut invocation = ActionRingInvocation::new();
    // Fill slots with actions (here we just use defaults)
    for slot in ActionRingSlot::ALL {
        invocation.slots.insert(slot, default_presentation(slot));
    }
    // Send it over the IPC channel
    runtime.ipc.send_invocation(invocation);
}

```

This code demonstrates constructing the invocation payload and transmitting it via the runtime's IPC channel. The `ActionRingInvocation` type definition resides in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) at lines 385-412.

### Rendering Interactive Slots in RingView

```rust
div()
    .id(("ring-slot", slot.index()))
    .absolute()
    .left(px(left))
    .top(px(top))
    .size(px(SLOT_SIZE))
    .bg(if selected { color::accent_at_lightness(SELECTED_FILL_L) } else { SLOT_RESTING })
    .on_hover(cx.listener(move |this, hovered, _, cx| {
        if *hovered {
            this.hovered = Some(slot);
            let _ = this.commands.send(OverlayCommand::Hover { session_id, slot });
        } else {
            this.hovered = None;
        }
        cx.notify();
    }))
    .on_click(move |_, window, cx| {
        let _ = activate.send(OverlayCommand::Activate { session_id, slot });
        window.remove_window();
    })
    .into_any_element()

```

This excerpt from [`crates/openlogi-overlay/src/ring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/ring.rs) shows how each slot handles hover states by sending `OverlayCommand::Hover` and triggers actions via `OverlayCommand::Activate` before removing the window. The visual feedback uses `accent_at_lightness` for selection states.

### Automatic Dismissal Implementation

```rust
cx.spawn(async move |cx| {
    cx.background_executor().timer(DISPLAY_LIFETIME).await;
    let _ = timeout_commands.send(OverlayCommand::Cancel { session_id });
}).detach();

```

Found in [`crates/openlogi-overlay/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/main.rs) at lines 101-113, this spawn operation creates a background task that automatically cancels the overlay after the configured display lifetime expires, ensuring the UI does not persist indefinitely.

## Summary

- The **Actions Ring overlay architecture** separates UI rendering from action execution through a dedicated `openlogi-overlay` binary that communicates via tarpc IPC with the main agent.
- The agent in [`crates/openlogi-agent/src/overlay.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/overlay.rs) controls invocation and execution, while the overlay handles GPUI rendering, cursor tracking, and input capture defined in [`crates/openlogi-overlay/src/ring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/ring.rs).
- Single-instance enforcement via `claim_the_role()` and filesystem locks prevents conflicting UI instances.
- Automatic dismissal uses `DISPLAY_LIFETIME` timeouts and `ClickAwaySession` monitoring to guarantee clean UI cleanup without user intervention.
- All inter-process communication uses versioned protocols defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) to maintain compatibility across application updates.

## Frequently Asked Questions

### What is the Actions Ring overlay in OpenLogi?

The Actions Ring overlay is a cursor-centered, eight-slot circular interface that appears when triggered by the OpenLogi agent. It runs as a separate binary (`openlogi-overlay`) that renders the UI using GPUI and forwards user interactions back to the agent via IPC, keeping hardware access and action execution isolated in the main process to ensure system stability.

### How does the overlay ensure only one instance runs at a time?

The overlay calls `claim_the_role()` during initialization in [`crates/openlogi-overlay/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/main.rs), which acquires a filesystem lock. This mechanism guarantees that only one overlay process can hold the Actions Ring role at any given time, preventing duplicate windows and conflicting session states that could confuse the agent's action handler.

### What happens when the user clicks outside the Actions Ring?

A `ClickAwaySession` struct defined in [`crates/openlogi-overlay/src/session.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/session.rs) monitors global mouse events. When it detects a click outside the ring's geometric bounds, it immediately sends an `OverlayCommand::Cancel` to the agent and terminates the window, ensuring the overlay dismisses intuitively when the user clicks elsewhere on screen.

### How long does the Actions Ring remain visible?

The overlay automatically dismisses after the `DISPLAY_LIFETIME` constant defined in `openlogi_core::action_ring`, typically spanning several seconds. A background task spawned in [`main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/main.rs) handles this timeout by sending a `Cancel` command, while user interaction can also trigger earlier dismissal via the `ClickAwaySession` or explicit slot selection.