How the Actions Ring Overlay Renders at the Cursor Position Across Processes in OpenLogi

OpenLogi renders the Actions Ring in a dedicated openlogi-overlay process that positions a frameless GPUI window at the cursor coordinates using WindowBounds::Cursor, communicating with the main agent via tarpc IPC to ensure cross-process isolation and single-instance control.

OpenLogi uses a distributed multi-process architecture to separate input handling from user interface rendering. When a user invokes the Actions Ring, the system must calculate the exact cursor position and render an overlay without blocking the main application or agent processes. This article explains how the Actions Ring overlay renders at the cursor position across different processes, detailing the tarpc IPC channel, GPUI windowing, and role-based single-instance management implemented in the AprilNEA/OpenLogi repository.

Multi-Process Architecture for Overlay Isolation

The Actions Ring operates across three distinct processes to maintain security and responsiveness:

  • openlogi-agent – Runs in the background, manages HID++ device input, and triggers the overlay
  • openlogi-overlay – A pure GPUI client that handles all visual rendering in isolation
  • openlogi-desktop – The main graphical user interface

Because the overlay runs in its own process, it can draw a frameless, always-on-top window without interfering with the main GUI or the background agent. All state is passed through the tarpc IPC channel defined in crates/openlogi-ipc/src/ipc.rs, meaning the overlay never needs direct access to HID++ devices or the system’s input hook.

IPC Communication and Invocation Flow

When a user triggers the Actions Ring, the agent initiates a six-step rendering pipeline:

  1. Agent Dispatch – In crates/openlogi-agent/src/overlay.rs, the agent calls claim_the_role() to ensure only one overlay instance holds the "Actions Ring" role, then sends an ActionRingInvocation via OverlayCommand::Show through the IPC channel.

  2. IPC Reception – The openlogi-overlay process, via crates/openlogi-overlay/src/agent.rs, runs spawn_ipc() to listen for RPCs. It receives the ActionRingInvocation containing the list of slots, locale, and a unique session_id.

  3. Window Creation – crates/openlogi-overlay/src/main.rs creates a GPUI AppContext, claims the overlay role via claim_the_role(), and opens a new window for every received invocation using cx.open_window(options, …).

  4. Cursor Positioning – crates/openlogi-overlay/src/platform.rs builds WindowOptions with WindowBounds::Cursor to center the window on the current mouse position, clamping the geometry to the active display to prevent edge cutoff.

  5. UI Construction – crates/openlogi-overlay/src/ring.rs implements RingView::new(), which receives the invocation data and constructs the ring UI using slot.placement() to arrange slots radially around the cursor.

  6. Event Feedback – User interactions (hover, activation, cancellation) are sent back to the agent via the same IPC channel using OverlayCommand::Hover, OverlayCommand::Activate, and OverlayCommand::Cancel, allowing the agent to execute the bound shortcuts.

Cursor-Centered Window Positioning

The precise cursor positioning logic resides in crates/openlogi-overlay/src/platform.rs. The ring_window_options() function retrieves the current mouse coordinates and configures the GPUI window to appear centered on that point:

use gpui::{WindowOptions, WindowBounds};

fn ring_window_options(cx: &AppContext) -> WindowOptions {
    let cursor = cx.mouse_position();
    WindowOptions {
        bounds: WindowBounds::Cursor(cursor),
        kind: WindowKind::Frameless,
        // … other visual options …
        ..Default::default()
    }
}

This approach guarantees that the Actions Ring appears exactly where the user is looking, while the clamping logic ensures the window remains fully visible even if the cursor is near the screen edge.

Rendering the Ring UI with GPUI

Once the window is positioned, RingView in crates/openlogi-overlay/src/ring.rs handles the visual layout. The view reconstructs the ring statelessly from the ActionRingInvocation each time, calculating radial slot positions:

let (left, top) = slot.placement(WINDOW_SIZE, RADIUS, SLOT_SIZE);
div()
    .absolute()
    .left(px(left))
    .top(px(top))
    .size(px(SLOT_SIZE))
    .child(svg().path(icon_path).size(px(22.0)))

This stateless rendering ensures that each invocation starts fresh without persistent UI state between sessions, reducing memory overhead and preventing stale data issues.

Single-Instance Control and Role Management

To prevent duplicate overlays, OpenLogi implements a role-claiming mechanism. The claim_the_role() function—implemented in crates/openlogi-overlay/src/session.rs—guarantees that only one overlay process can own the "Actions Ring" role at a time. Both the agent (crates/openlogi-agent/src/overlay.rs) and the overlay process invoke this check before displaying the UI, ensuring system-wide single-instance semantics without requiring a global mutex.

Summary

  • Process Isolation – The Actions Ring runs in a dedicated openlogi-overlay binary separate from the agent and desktop GUI, enabling frameless, interference-free rendering.
  • IPC-Driven Rendering – All state passes through tarpc IPC via ActionRingInvocation and OverlayCommand enums defined in crates/openlogi-ipc/src/ipc.rs.
  • Cursor-Centered Positioning – ring_window_options() uses WindowBounds::Cursor and clamps the window to the current display edges in crates/openlogi-overlay/src/platform.rs.
  • Stateless Architecture – RingView reconstructs the UI from scratch for each invocation, ensuring no persistent state between interactions.
  • Single-Instance Guarantee – claim_the_role() in the session management code prevents multiple simultaneous overlay instances.

Frequently Asked Questions

Why does OpenLogi use a separate process for the Actions Ring overlay?

Running the overlay in its own process (openlogi-overlay) isolates the GPUI rendering engine from the background agent's HID++ device handling and the main desktop GUI. This separation prevents visual glitches, allows the overlay to create frameless, always-on-top windows without affecting other application windows, and ensures that crashes in the rendering layer do not destabilize the input monitoring system.

How does the overlay determine the exact cursor position for rendering?

The overlay queries the GPUI AppContext for the current mouse coordinates via cx.mouse_position() in crates/openlogi-overlay/src/platform.rs. It then passes these coordinates to WindowBounds::Cursor when constructing the WindowOptions, which instructs GPUI to center the new window at that exact screen location, clamped to the active display boundaries.

What prevents multiple Actions Ring overlays from appearing simultaneously?

Both the agent and the overlay process call claim_the_role() before displaying the interface. This function, implemented in crates/openlogi-overlay/src/session.rs, uses system-level role arbitration to ensure only one process can hold the "Actions Ring" role at any time. If an existing overlay is already active, subsequent attempts to claim the role fail, preventing duplicate windows.

How do the agent and overlay communicate during an interaction?

The components communicate exclusively over a tarpc IPC channel defined in crates/openlogi-ipc/src/ipc.rs. The agent sends ActionRingInvocation data via OverlayCommand::Show, and the overlay returns user interactions (hover, activate, cancel) through the same channel. This RPC-based approach eliminates shared memory concerns and allows the processes to run with different privilege levels.

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 →