Skip to content

fix(cua-driver/linux): stop idle X11 overlay frame ticks - #2258

Closed
KeroZelvin wants to merge 6 commits into
trycua:mainfrom
KeroZelvin:fix/linux-x11-idle-overlay
Closed

fix(cua-driver/linux): stop idle X11 overlay frame ticks#2258
KeroZelvin wants to merge 6 commits into
trycua:mainfrom
KeroZelvin:fix/linux-x11-idle-overlay

Conversation

@KeroZelvin

@KeroZelvin KeroZelvin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • make the Linux/X11 cursor overlay event-driven when no cursor pixels can change
  • retain a cheap 80 ms z-order heartbeat for a visible resting cursor without rebuilding or uploading the full-screen pixmap
  • park through the default opaque idle-hide delay, then wake at the exact fade deadline and resume frame cadence only for the 180 ms fade
  • preserve each cursor's idle clock when unrelated cursor commands wake the shared loop
  • map the initial full-screen X11 window with an empty bounding shape so it cannot appear black before the first command
  • preserve final-frame-before-arrival ordering and add focused scheduler/quiescence regression coverage

Fixes #2204.

Context

The X11 overlay loop currently allocates and paints a full-screen tiny_skia::Pixmap, converts RGBA to BGRA, updates the X Shape region, and calls XPutImage every 16 ms even when the default cursor is still at its off-screen sentinel or all cursors have settled. It also keeps painting unchanged pixels throughout the default 20-second opaque idle-hide delay after an action.

That is the Linux equivalent of the idle render-loop problem addressed for macOS in #1865 and Windows in #1933. It leaves cua-overlay-x11 consuming a substantial fraction of one core while an MCP session is idle.

Implementation

  • Add needs_frame_tick state detection for active animation, spring settle, click pulse, and the actual 180 ms idle fade.
  • Block on the overlay command channel when no cursor needs another frame.
  • Advance pre-existing quiescent cursors by the full parked interval before applying a command, while starting newly commanded animations at dt = 0, including commands drained immediately after a maintenance timeout.
  • Drain queued command bursts before ticking and rendering.
  • Render commands, active animation/fade frames, and the final settle/clear frame only.
  • Fire arrival notifications after the destination frame has been painted, preserving the prior ordering contract.
  • Keep X11 z-order maintenance active for visible resting cursors through an 80 ms bounded receive, but do not repaint on maintenance-only wakeups.
  • Use absolute Instant deadlines for z-order maintenance and idle fade. The remaining timeout is calculated immediately before blocking, so synchronous X11 maintenance cannot extend the deadline.
  • Advance the full elapsed interval on maintenance wakes rather than applying the 50 ms active-animation safety cap.
  • Keep scheduler-only helpers and time imports Linux-gated so the crate's non-Linux stubs continue to compile.
  • Set both SK::INPUT and SK::BOUNDING to empty before mapping the initial overlay window, preventing an opaque black full-screen window on bare/non-composited X servers.
  • Exit cleanly when all command senders disconnect.

Regression coverage

Added focused Linux tests for:

  • sentinel/default cursor quiescence
  • active cursor frame requirements
  • completed move parking during the opaque idle-hide delay
  • deadline-aligned transition into idle fade and final fade quiescence
  • completed click-pulse quiescence
  • visible resting cursor z-order-only scheduling
  • active-frame command preservation
  • idle command wakeup
  • maintenance timeout wakeup
  • absolute deadline anchoring and expired-deadline handling
  • full elapsed-time accounting on maintenance wakes
  • multi-cursor idle-deadline progress during unrelated command traffic
  • timeout-boundary command drains starting MoveTo and ClickPulse at dt = 0
  • active-frame replacement ordering without stale arrival completion
  • disconnected sender handling in blocking and timeout modes
  • explicit SetEnabled(false) final-clear rendering followed by frame- and z-order-scheduler parking

Validation boundary

The exact patched build passed the scheduler suite and isolated Xvfb startup, idle, action, and settle checks. A later live 1920×2160 X11 Hermes session became unusable after an unreturned high-level Chrome hotkey request and required an orderly reboot. No kernel lockup, OOM, GPU fault, or coredump evidence survived, and the hotkey is not itself an overlay animation command, so I cannot attribute that incident to this patch. I am recording it as a validation boundary rather than a regression claim: this PR proves quiescent overlay parking, not full live desktop/input-stack safety. --no-overlay remains the safest operational fallback pending broader watchdog-backed live validation.

Follow-ups / out of scope

  • Active renderer optimization: active animation still allocates, scans, shapes, and uploads a full-root BGRA frame at roughly 60 Hz. On a 1920×2160 root that is 15.82 MiB per frame, or about 0.93 GiB/s of raw pixel data before allocation, shape, GC, and compositor overhead. Dirty-region or cursor-sized uploads, GC reuse, shape simplification, and lazy X11 overlay startup should be handled separately rather than expanding this scheduler-focused fix.
  • X11 telemetry and fail-safe behavior: the current paint path discards shape_rectangles, put_image, free_gc, and flush failures. Rate-limited diagnostics, sampled asynchronous error checks, and a safe clear/unmap/park policy after repeated failures belong in a focused follow-up.
  • One-shot CLI/CDP/AT-SPI/background-input errors are separate from this overlay loop and should not be conflated with the idle-render fix.

Validation

The exact PR diff was replayed, independently reviewed, and full-workspace tested on current upstream/main at 763a6ea21b86ad5f8a70ab2581edee1dd7370efa. The published head is 12fc510c7 and remains mergeable. Current upstream main has since advanced to 95c68886; a fresh merge-tree check is clean, and upstream has not changed platform-linux/src/overlay.rs since this PR's base. Issue #2204 remains open, current main still contains the unconditional 16 ms X11 paint loop, and open-PR searches found no competing Linux idle-overlay repair. The latest upstream commit only changes browser telemetry files; it does not touch the Linux overlay.

  • cargo test --workspace on the patch replayed onto current main — passed
  • cargo test --workspace --no-run on the patch replayed onto current main — passed
  • cargo test --workspace -- --test-threads=1 on the exact published branch — passed
  • cargo test -p platform-linux --lib — 113 passed
  • cargo test -p platform-linux overlay --lib — 19 scheduler/overlay tests passed
  • cargo check -p platform-linux --target x86_64-pc-windows-gnu — passed; the previous head failed with unresolved Duration references
  • cargo test -p cursor-overlay — 12 passed; doc tests passed
  • cargo build -p cua-driver — passed
  • rustfmt --edition 2021 crates/platform-linux/src/overlay.rs --check — passed
  • git diff --check — passed

The repository-wide cargo fmt --all --check and strict Clippy gates are not green on current upstream main because of pre-existing formatting and lint debt outside this diff. The changed Linux overlay file passes its focused formatter check, and strict Clippy reported no diagnostic in the newly added scheduler/deadline code.

Isolated Xvfb verification used bare 1920×2160 debug builds and sampled the cua-overlay-x11 thread from /proc/<pid>/task:

State Overlay thread CPU CPU ticks over sample
Unpatched upstream/main (b088628a; overlay identical at current 763a6ea2), initial MCP idle 97.0% 194 over 2 s
Patched, initial MCP idle 0.0% 0 over 2 s
Patched, active/settling cursor 96.99% 97 over 1 s
Patched, opaque portion of default idle-hide delay after an action 0.0% 0 over 2 s

Before the first command, xwininfo -shape reported Window shape extents: 0x0+0+0, proving the mapped startup overlay has an empty bounding region. The MCP initialize and move_cursor calls succeeded, the patched driver remained alive, and all Xvfb/driver processes were cleaned up afterward.

@KeroZelvin
KeroZelvin force-pushed the fix/linux-x11-idle-overlay branch from 55b7bee to b82e215 Compare July 17, 2026 23:08
@KeroZelvin
KeroZelvin marked this pull request as ready for review July 17, 2026 23:17

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b82e215408

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread libs/cua-driver/rust/crates/platform-linux/src/overlay.rs Outdated
@injaneity

Copy link
Copy Markdown
Collaborator

closing as superseded by #2331

@injaneity injaneity closed this Jul 20, 2026
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.

cua-driver 0.7.1 Linux/X11: cua-overlay-x11 thread burns ~50% CPU at idle (same as #1808, Linux not fixed by #1865/#1933)

3 participants