Skip to content

fix(cua-driver): Linux agent cursor never painted under reparenting WMs — z-order BadMatch killed the overlay thread - #2957

Merged
f-trycua merged 1 commit into
mainfrom
fix/overlay-zorder-reparenting-wm
Aug 7, 2026
Merged

fix(cua-driver): Linux agent cursor never painted under reparenting WMs — z-order BadMatch killed the overlay thread#2957
f-trycua merged 1 commit into
mainfrom
fix/overlay-zorder-reparenting-wm

Conversation

@f-trycua

@f-trycua f-trycua commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Fixes #2955.

Root cause

The first element click in a session sends PinAbove(<target window>), and the z-order enforcer restacks the overlay with ConfigureWindow(sibling=<client xid>, StackMode=Above). Under a reparenting WM (xfwm4, Mutter, KWin, …) the client window is a child of a WM frame window, not of the root — and X11 requires a restack sibling to share the configured window's parent, so the server answers BadMatch.

classify_x11_overlay_event treated any non-BadWindow error as fatal: the overlay thread broke its loop, RENDER was cleared, and every later cursor command was rejected by the dead channel — silently. Net effect on such desktops (which includes the xorgxrdp/XFCE box from #2955, but is not xrdp-specific):

  • the agent cursor never painted a single frame (window mapped, bounding shape stayed 0x0 forever);
  • set_agent_cursor_enabled returned {"enabled": true} while get_agent_cursor_state read enabled: false (the fallback when RENDER is None).

Instrumented trace of the death, seconds after the first click:

overlay: command applied  cmd=ClickPulse { x: 696.0, y: 591.0 }  visible=true  pos=(696,591)
overlay: paint pass  tiles=1
WARN X11 overlay event drain failed; disabling overlay: X11Error { error_kind: Match, error_code: 8,
     bad_value: 71303171 (= the pinned window), major_opcode: 12 (ConfigureWindow) }
WARN overlay: X11 channel rejected command (no sender or queue full)

Fix

  • reassert() resolves the pin target to its top-level root-child ancestor (query_tree walk) before using it as the restack sibling — the WM frame is the window that actually occupies a stacking slot, so the restack now lands where PinAbove intended. The walk doubles as the liveness probe that get_window_attributes provided before. On non-reparenting setups the walk terminates on the first iteration and behavior is unchanged.
  • recoverable_x11_z_order_error also accepts BadMatch (a reparent can still race between the ancestor walk and the restack). Errors naming the overlay window itself remain fatal, matching the existing BadWindow rule.
  • send_command_for now logs a warning when the X11 channel rejects a command — previously a dead render thread was completely silent, which is what made this so hard to see.

Testing

  • cargo test -p platform-linux --lib — 263 passed. The old non_badwindow_configure_error_remains_fatal test asserted the buggy behavior; it is replaced by z_order_sibling_badmatch_is_recoverable plus a new overlay_badmatch_remains_fatal guard for the overlay-window case.
  • Live on the previously-broken desktop (Ubuntu 24.04, xorgxrdp, xfwm4, no compositor): element clicks on Galculator now animate the cursor — bounding shape observed going 0x0 → 38x39 (arrow) → 98x90 (arrow + click pulse + session badge) at the clicked button, thread survives, and a desktop screenshot shows the rendered cursor with its session badge. get_agent_cursor_state now agrees with set_agent_cursor_enabled.

🤖 Generated with Claude Code

@f-trycua
f-trycua force-pushed the fix/overlay-zorder-reparenting-wm branch from b5c013b to b08ce82 Compare August 6, 2026 20:36
…e Linux agent cursor can paint

The first element click on a session pins the overlay above the target
window and restacks with ConfigureWindow(sibling=<client xid>, Above).
Under a reparenting WM (xfwm4, Mutter, KWin) the client window lives
inside a WM frame, so it does not share the root-child overlay's parent
and the server answers BadMatch. classify_x11_overlay_event treated
that as fatal: the overlay thread exited, RENDER was cleared, and every
later cursor command was silently rejected — the agent cursor never
painted a single frame on such desktops and set_agent_cursor_enabled
appeared to succeed while get_agent_cursor_state read enabled:false.

Three changes:
- reassert() resolves the pin target to its top-level root-child
  ancestor via query_tree before using it as the restack sibling — the
  frame is the window that actually occupies a stacking slot, and the
  walk doubles as the liveness probe.
- recoverable_x11_z_order_error also accepts BadMatch (a reparent can
  still race the restack); only errors naming the overlay window itself
  stay fatal.
- send_command_for logs a warning when the X11 channel rejects a
  command, so a dead render thread is diagnosable instead of silent.

Fixes #2955

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@f-trycua
f-trycua force-pushed the fix/overlay-zorder-reparenting-wm branch from b08ce82 to 6be766e Compare August 7, 2026 08:57
@f-trycua
f-trycua merged commit 9522358 into main Aug 7, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Linux agent cursor overlay never paints on xorgxrdp (no compositor): shape stays 0x0 while set_agent_cursor_enabled reports success

1 participant