Skip to content

fix(overlay): keep the Actions Ring above panels on Linux/Wayland - #1211

Open
4ni1ak wants to merge 2 commits into
AprilNEA:masterfrom
4ni1ak:fix/linux-ring-above-panels
Open

fix(overlay): keep the Actions Ring above panels on Linux/Wayland#1211
4ni1ak wants to merge 2 commits into
AprilNEA:masterfrom
4ni1ak:fix/linux-ring-above-panels

Conversation

@4ni1ak

@4ni1ak 4ni1ak commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Summary

On Linux under a Wayland compositor (reproduced live on KDE Plasma/KWin), the Actions Ring could open hidden behind a panel/dock, and its position was unreliable on a multi-monitor setup.

Fixes #1210

Root cause

  • Panel occlusion: the ring opened as a plain WindowKind::PopUp, which on Wayland is just another xdg_toplevel surface — nothing guarantees it draws above a panel/dock's own always-on-top zwlr_layer_shell_v1 overlay-layer surface.
  • Wrong/stale position: openlogi_hook::cursor_position() on Linux falls back to XWayland's X11 query_pointer under a Wayland session. That isn't a real Wayland pointer query and doesn't track one — confirmed live, it returned the exact same coordinate across multiple cursor moves and clicks on two different monitors.

Changes

crates/openlogi-overlay/src/ring.rs:

  • On Linux/Wayland, open_ring now opens an invisible full-screen Layer::Overlay host window first, then anchors the ring to it as a WindowKind::AnchoredPopup (center anchor + center gravity on the real cursor point). The overlay layer is guaranteed above everything else, including panels. Falls back to the previous plain WindowKind::PopUp at the old best-effort position guess on macOS, Windows, Linux/X11, or a Wayland compositor without zwlr_layer_shell_v1.
  • The host's real cursor position comes from the compositor's own first pointer-motion delivery to it (gpui::Window::mouse_position, fed by native platform pointer events) rather than the X11 query — accurate regardless of which monitor the cursor is on. Bounded at 250ms, falling back to the old best-effort guess if the compositor never delivers one, so a ring still opens rather than hanging.
  • Every dismissal path (slot click, cancel click, root click, click-away, the display-lifetime timeout) now closes the host window alongside the ring, so it never lingers as an invisible full-screen click-blocker.

crates/openlogi-overlay/src/session.rs:

  • dismiss_click_away (previously reachable only via the macOS-only native click-away monitor) now also closes the ring's host window when it fires.

Side effect worth calling out: clicking the invisible host — i.e. anywhere outside the ring — now dismisses it on Linux too. Linux never had click-away-to-dismiss before this; the existing mechanism (a native OS click monitor) is macOS-only.

Testing

Linux, x86_64, Rust 1.98.0:

cargo fmt --all -- --check
cargo clippy -p openlogi-overlay --all-targets -- -D warnings   # RUSTFLAGS=-D warnings
cargo test -p openlogi-overlay   # 17 passed

Runtime-verified live on this machine: KDE Plasma (KWin) Wayland session, dual monitor (primary + a secondary at a nonzero global X offset). Iterated through several rounds against the real compositor — confirmed the ring now stays above the panel and opens correctly centered on the cursor on both monitors, including the timeout fallback path (observed firing once when the compositor was slow to deliver the first pointer event).

Not tested: macOS, Windows, Linux/X11, or another Wayland compositor (GNOME/Mutter, Hyprland, Sway) — the fallback path is unchanged from the pre-existing behavior on all of those, but I don't have hardware/sessions for any of them to confirm.

A plain WindowKind::PopUp is just another toplevel to a Wayland
compositor — nothing stops it being drawn under a panel/dock's own
always-on-top surface, so the ring could open hidden behind one.
Anchor it as an AnchoredPopup off an invisible full-screen
Layer::Overlay host instead: that layer is guaranteed above everything
else, including panels.

The host also fixes cursor placement as a side effect. openlogi_hook's
Linux cursor_position() falls back to XWayland's X11 query_pointer
under a Wayland session — not a real Wayland pointer, and it doesn't
track one, so it reports a stale, display-ambiguous coordinate on a
Wayland-native desktop. The host surface gets the compositor's own
first pointer-motion delivery instead (with a bounded fallback to the
old X11-based guess if none arrives), which is accurate regardless of
which monitor the cursor is on.

Every dismissal path (slot/cancel/root click, click-away, the
display-lifetime timeout) now closes the host alongside the ring, and
clicking the host itself dismisses the ring — click-away-to-dismiss
Linux never had, since the existing native monitor is macOS-only.

Falls back to the previous plain-PopUp behavior on macOS, Windows,
Linux/X11, and Wayland compositors without zwlr_layer_shell_v1.

Fixes AprilNEA#1210
@4ni1ak
4ni1ak requested a review from AprilNEA as a code owner August 31, 2026 21:40
@greptile-apps

greptile-apps Bot commented Aug 31, 2026

Copy link
Copy Markdown

Greptile Summary

The PR places the Actions Ring above Wayland panels by anchoring it to a full-screen layer-shell host and obtains its position from compositor-delivered pointer motion.

  • Adds a bounded cursor-position wait with a best-effort fallback.
  • Closes the host during interaction, click-away, timeout, replacement, and popup-open failure paths.
  • Preserves the existing plain-popup behavior on unsupported platforms and compositors.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
crates/openlogi-overlay/src/main.rs Delegates ring creation to the asynchronous host-aware path and closes the optional host on display timeout.
crates/openlogi-overlay/src/ring.rs Implements the Wayland layer-shell host, compositor-derived cursor anchoring, corrected fallback conversion, and cleanup after popup creation failure.
crates/openlogi-overlay/src/session.rs Extends session-checked click-away dismissal to remove the ring’s associated Wayland host.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Ring invocation] --> B{Linux Wayland with layer shell?}
    B -->|No| C[Open plain popup]
    B -->|Yes| D[Open full-screen overlay host]
    D --> E{Pointer motion within 250 ms?}
    E -->|Yes| F[Use compositor cursor position]
    E -->|No| G[Convert fallback placement to host-local anchor]
    F --> H[Open anchored popup]
    G --> H
    H --> I{Dismissal or timeout}
    I --> J[Close ring and host]
    H -->|Popup open fails| K[Close host and return error]
Loading

Reviews (2): Last reviewed commit: "fix(overlay): cross-platform build + fal..." | Re-trigger Greptile

Comment thread crates/openlogi-overlay/src/ring.rs Outdated
Comment thread crates/openlogi-overlay/src/ring.rs Outdated
- Gate the oneshot import and silence the non-Linux stub's unused_async
  Linux-only — cargo clippy/test --workspace only ran the Linux cfg
  branch locally, so the non-Linux stub's unused import/async broke
  the macOS and Windows CI jobs.
- The 250ms no-pointer-motion fallback in linux_wayland_ring_host was
  reusing ring_placement()'s global-coordinate origin as a host-local
  anchor — exactly the coordinate-space bug the host exists to avoid,
  reintroduced on a secondary display. Convert it the same way the
  main PopUp fallback does.
- open_ring leaked the host window when the anchored popup itself
  failed to open: no RingView exists yet for any dismissal path to
  find, so the invisible full-screen host would sit there blocking
  clicks until the next ring invocation. Close it explicitly on that
  error path.

Both anchor/leak issues via Greptile review on AprilNEA#1211.
@4ni1ak

4ni1ak commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Fixed everything, pushed d0958712:

  • CI (macOS/Windows): my local gate only exercised the Linux cfg branch (cargo clippy -p openlogi-overlay on this machine), so the non-Linux stub's unused oneshot import and unused_async never showed up until CI's cargo clippy --workspace/cargo test --workspace compiled that branch too. Gated the import to Linux and documented+silenced the async lint on the stub (it's async only to match the call site's unconditional .await).
  • Greptile Issue 1 (popup-failure leaks host): confirmed — closing the host explicitly on that error path now, since no RingView exists yet for any dismissal path to find it.
  • Greptile Issue 2 (fallback uses wrong anchor): confirmed — the 250ms no-pointer-motion fallback was reusing ring_placement()'s global-coordinate origin as if it were already host-local, exactly the bug the host exists to avoid, reintroduced on a secondary display. Converts it the same way the plain-PopUp fallback path does now.

Re-ran the local gate (fmt/clippy/test) green on the final tree. Still no macOS/Windows toolchain here to verify those two CI lanes directly, but the fix is mechanical (cfg-gate the import, document the async-signature mismatch) — will watch CI.

@davidbudnick davidbudnick added type: bug Something is broken or behaves incorrectly area: gui Graphical user interface platform: linux Linux-specific issue labels Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: gui Graphical user interface platform: linux Linux-specific issue type: bug Something is broken or behaves incorrectly

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Actions Ring opens under panels/docks on Linux Wayland, and off-cursor on multi-monitor

2 participants