# How the Actions Ring Overlay Works in OpenLogi: Architecture and Implementation

> Discover the Actions Ring overlay in OpenLogi. Learn how this IPC client binary renders a radial menu using GPUI and processes user interactions for seamless execution.

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

---

**The Actions Ring overlay is a cursor-centered, eight-slot radial menu that runs as a separate IPC client binary, rendering via GPUI and forwarding user interactions back to the OpenLogi agent for execution.**

OpenLogi is an open-source input device management framework that separates UI rendering from hardware control. The Actions Ring overlay demonstrates this architecture by isolating the graphical interface in a dedicated process while keeping HID++ processing, session validation, and haptic feedback within the agent.

## Architecture Overview

The Actions Ring overlay follows a **split-process design** that maximizes stability and security. The **agent process** (`openlogi-agent`) handles all hardware communication, while the **overlay process** (`openlogi-overlay`) manages only the visual interface.

When a user triggers the ring, the agent creates an `ActionRingInvocation` payload and transmits it via tarpc IPC. The overlay binary receives this data, claims exclusive display rights through a file-based lock, and renders the interface using the GPUI framework. This separation ensures that graphic crashes never affect hardware drivers.

## IPC Protocol and Data Flow

Communication between the agent and overlay relies on strongly-typed messages defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs). The protocol uses two primary structures:

- **`ActionRingInvocation`** – Contains the eight action slots, locale settings, and session metadata sent from agent to overlay
- **`OverlayCommand`** – Carries user interactions (Hover, Activate, Cancel) from overlay back to agent

The data flow proceeds through these stages:

1. **Agent initiation** – [`crates/openlogi-agent/src/overlay.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/overlay.rs) constructs the invocation and sends it through the tarpc channel
2. **Overlay receipt** – [`crates/openlogi-overlay/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/main.rs) receives the payload via an async `mpsc` channel
3. **Command return** – User interactions transmit back to the agent via the `commands` sender for execution

## Rendering the Ring UI

The visual implementation centers on `RingView` in [`crates/openlogi-overlay/src/ring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/ring.rs). This GPUI component renders a transparent, borderless pop-up window positioned at the cursor location.

### Window Placement Logic

The `RingPlacement::capture` function (defined in [`crates/openlogi-overlay/src/platform.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/platform.rs)) obtains the current cursor coordinates. The `clamp_window_origin` utility then adjusts these coordinates to ensure the eight-slot ring remains fully visible even near screen edges.

The `ring_window_options()` function configures the GPUI window with specific properties:

- `WindowKind::PopUp` for borderless appearance
- Non-focusable to prevent stealing input from active applications
- Transparent background allowing the desktop to show through the ring's center

### Slot Rendering

Each of the eight slots renders as an interactive element with hover states and icon assets from [`crates/openlogi-ui/src/action_icons.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ui/src/action_icons.rs). The `slot_element` method constructs GPUI div elements with absolute positioning calculated from the ring's center point.

```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()

```

*See the full implementation in* [[`crates/openlogi-overlay/src/ring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/ring.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-overlay/src/ring.rs).

## User Interaction Handling

The overlay captures two primary interaction types and translates them into IPC commands:

- **Hover events** – Send `OverlayCommand::Hover` to highlight slots and trigger preliminary haptic feedback in the agent
- **Activation clicks** – Send `OverlayCommand::Activate` to execute the bound action, then immediately close the window

The `ClickAwaySession` struct in [`crates/openlogi-overlay/src/session.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/session.rs) monitors for clicks outside the ring boundaries. When detected, it sends `OverlayCommand::Cancel`, ensuring the overlay dismisses intuitively when users click elsewhere on the desktop.

## Session Lifecycle and Dismissal

The Actions Ring implements automatic cleanup through multiple mechanisms:

**Single-Instance Enforcement** – The `claim_the_role()` function in [`main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/main.rs) uses a file lock to prevent multiple overlay instances, ensuring only one ring displays at a time.

**Timeout Dismissal** – The `DISPLAY_LIFETIME` constant from [`crates/openlogi-core/src/action_ring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/action_ring.rs) defines the maximum display duration. A background task spawned in [`main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/main.rs) waits for this duration before sending a forced cancel:

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

```

**Session Validation** – Every command includes a `session_id` validated by the agent, preventing stale interactions from previous ring invocations from affecting current operations.

## Triggering the Actions Ring from Agent Code

To programmatically display the ring, the agent constructs an `ActionRingInvocation` and transmits it through the runtime's IPC channel:

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

fn show_actions_ring(runtime: &Runtime) {
    let mut invocation = ActionRingInvocation::new();
    for slot in ActionRingSlot::ALL {
        invocation.slots.insert(slot, default_presentation(slot));
    }
    runtime.ipc.send_invocation(invocation);
}

```

The `ActionRingInvocation` type definition lives in [[`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/src/ipc.rs), which also documents version numbers for protocol compatibility between agent and overlay binaries.

## Key Implementation Files

| File Path | Responsibility |
|-----------|----------------|
| [`crates/openlogi-overlay/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/main.rs) | Entry point, role claiming (`claim_the_role()`), IPC reception, window lifecycle |
| [`crates/openlogi-overlay/src/ring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/ring.rs) | GPUI view rendering, slot positioning (`clamp_window_origin`), hover/click handling |
| [`crates/openlogi-overlay/src/session.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/session.rs) | Click-away detection (`ClickAwaySession`) and session validation |
| [`crates/openlogi-agent/src/overlay.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/overlay.rs) | Agent-side overlay supervision and binary spawning |
| [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) | Protocol definitions (`ActionRingInvocation`, `OverlayCommand`) |
| [`crates/openlogi-core/src/action_ring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/action_ring.rs) | Shared constants including `DISPLAY_LIFETIME` |
| [`crates/openlogi-ui/src/action_icons.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ui/src/action_icons.rs) | Visual assets for the eight ring slots |
| [`crates/openlogi-overlay/src/platform.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/platform.rs) | Platform-specific cursor capture (`RingPlacement::capture`) |

## Summary

- The **Actions Ring overlay** operates as a dedicated binary (`openlogi-overlay`) communicating via tarpc IPC with the agent process.
- Rendering uses **GPUI** with borderless, transparent pop-up windows positioned via `RingPlacement::capture` and `clamp_window_origin`.
- User interactions transmit back to the agent through **`OverlayCommand`** messages (Hover, Activate, Cancel).
- **Automatic dismissal** occurs via `DISPLAY_LIFETIME` timeout or `ClickAwaySession` monitoring.
- **Single-instance enforcement** prevents multiple rings via `claim_the_role()` file locking.
- All IPC protocol definitions reside in [`openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-ipc/src/ipc.rs) with documented versioning for backward compatibility.

## Frequently Asked Questions

### How does the Actions Ring overlay prevent multiple instances from opening simultaneously?

The overlay uses the `claim_the_role()` function in [`main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/main.rs), which acquires a file-based lock when starting. If another overlay process is already running, the lock acquisition fails and the new instance exits immediately. This ensures only one Actions Ring displays regardless of how many trigger events occur.

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

The `ClickAwaySession` struct in [`session.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/session.rs) monitors global click events. When it detects a click outside the ring's geometry, it sends `OverlayCommand::Cancel` to the agent and triggers window closure. This provides intuitive dismissal behavior without requiring explicit cancel button interaction.

### Why is the Actions Ring implemented as a separate binary rather than a thread in the agent?

The split-process architecture isolates the GPUI rendering stack from the hardware-critical agent process. If the graphics subsystem crashes or hangs, the agent continues managing input devices and can restart the overlay. This separation also allows different privilege levels: the overlay needs only display access while the agent requires hardware access.

### Where is the display duration timeout configured?

The constant `DISPLAY_LIFETIME` is defined in [`crates/openlogi-core/src/action_ring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/action_ring.rs) and imported by the overlay. The main event loop in [`crates/openlogi-overlay/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/main.rs) spawns an async task that waits for this duration using the background executor, then sends a `Cancel` command to close the window automatically if the user hasn't interacted with the ring.