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

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. 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 constructs the invocation and sends it through the tarpc channel
  2. Overlay receipt – 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. 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) 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. The slot_element method constructs GPUI div elements with absolute positioning calculated from the ring's center point.

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/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 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 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 defines the maximum display duration. A background task spawned in main.rs waits for this duration before sending a forced cancel:

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:

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/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 Entry point, role claiming (claim_the_role()), IPC reception, window lifecycle
crates/openlogi-overlay/src/ring.rs GPUI view rendering, slot positioning (clamp_window_origin), hover/click handling
crates/openlogi-overlay/src/session.rs Click-away detection (ClickAwaySession) and session validation
crates/openlogi-agent/src/overlay.rs Agent-side overlay supervision and binary spawning
crates/openlogi-ipc/src/ipc.rs Protocol definitions (ActionRingInvocation, OverlayCommand)
crates/openlogi-core/src/action_ring.rs Shared constants including DISPLAY_LIFETIME
crates/openlogi-ui/src/action_icons.rs Visual assets for the eight ring slots
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 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, 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 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 and imported by the overlay. The main event loop in 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →