Skip to content

fix(cua-driver)(macos): stop idle overlay frame ticks - #1865

Merged
f-trycua merged 2 commits into
trycua:mainfrom
redwine99:fix/macos-overlay-idle-cpu
Jun 18, 2026
Merged

fix(cua-driver)(macos): stop idle overlay frame ticks#1865
f-trycua merged 2 commits into
trycua:mainfrom
redwine99:fix/macos-overlay-idle-cpu

Conversation

@redwine99

@redwine99 redwine99 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Stop the macOS agent-cursor overlay render loop from repainting at frame cadence when the overlay is quiescent
  • Block on the command channel while no animation, fade, click pulse, or focus rect can change pixels
  • Add focused tests for the sentinel idle state and active/fading cursor state

Fixes #1808

Root cause

The macOS overlay render thread used a 60fps loop even when cua-driver serve had only the off-screen default/sentinel cursor and no active cursor work. That kept allocating/compositing a fullscreen pixmap while the daemon was otherwise idle.

Verification

Following CONTRIBUTING.md, I ran formatting and validation before preparing this PR:

cd libs/cua-driver/rust
cargo fmt
cargo build -p cua-driver
DYLD_LIBRARY_PATH=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx \
  cargo test -p platform-macos --lib

Result:

test result: ok. 76 passed; 0 failed; 0 ignored

I also ran the repository pre-commit configuration against the changed Rust file. The configured hooks skipped because they currently target TS/JS/JSON/Markdown/YAML or Python files, not Rust.

Idle CPU check

Using a local sampling helper against cua-driver serve --no-permissions-gate:

Before this fix:

avg_cpu=41.32
values=41.8,40.2,40.2,42.0,42.4

After this fix:

avg_cpu=0.00
values=0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0

Notes

The diff includes some nearby Rust formatting/reflow from cargo fmt in addition to the render-loop logic change.

Summary by CodeRabbit

  • Bug Fixes

    • Reduced CPU usage by preventing unnecessary cursor overlay render ticks when nothing can change.
    • Improved overlay stability by updating cursor animations/fades and compositing pixmaps only when activity requires it.
    • Render thread now efficiently blocks during idle quiescence instead of spinning.
    • Main-thread behavior now consistently pauses when overlay is disabled or graphics access is unavailable.
  • Chores

    • Refined internal rendering-state logic to better determine when frame work is needed.
    • Added/updated unit tests for idle (sentinel) vs active/fading tick behavior.

@vercel

vercel Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

@redwine99 is attempting to deploy a commit to the Cua Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1b430f4d-75bd-47c6-8a5a-66bb1679d41c

📥 Commits

Reviewing files that changed from the base of the PR and between 8b068ff and d347430.

📒 Files selected for processing (1)
  • libs/cua-driver/rust/crates/platform-macos/src/cursor/overlay.rs
💤 Files with no reviewable changes (1)
  • libs/cua-driver/rust/crates/platform-macos/src/cursor/overlay.rs

📝 Walkthrough

Walkthrough

This PR optimizes the macOS overlay render loop to eliminate ~50% idle CPU usage by blocking when no cursor animation or movement is active, only compositing pixmaps when commands arrive or frame ticks could visibly change the cursor, and conditionally ticking/repinning only when necessary.

Changes

Render Loop Idle Blocking & Conditional Ticking

Layer / File(s) Summary
Frame tick requirement detection
libs/cua-driver/rust/crates/platform-macos/src/cursor/overlay.rs
Adds RenderState::needs_frame_tick() and render_map_needs_frame_tick() to compute whether the renderer must wake at frame cadence based on active animation, spring, click, focus-rect, and visibility/idle-alpha state.
Render loop idle-blocking & conditional ticking
libs/cua-driver/rust/crates/platform-macos/src/cursor/overlay.rs
Updates render_loop to block on rx.recv() when quiescent, drain messages with try_recv() when active, conditionally tick cursors only when frame-tick-needed, and composite pixmaps only when commands arrive or ticks could change pixels.

Initialization, Safety & Supporting Changes

Layer / File(s) Summary
Main thread initialization & safety parking
libs/cua-driver/rust/crates/platform-macos/src/cursor/overlay.rs
run_on_main_thread now uses explicit loop { park(); } blocks when initialization fails (receiver missing, overlay disabled, no graphic-session access). run_appkit adds explicit null-check return for allocated NSWindow, and render-thread spawn captures win_ptr via separate binding.
Guard and control-flow formatting in helpers
libs/cua-driver/rust/crates/platform-macos/src/cursor/overlay.rs
seed_start_if_sentinel matches render-map guard with explicit multi-line else return, and animate_cursor_to reformats OverlayCommand::MoveTo send without changing payload.
FFI declarations & pixmap handling
libs/cua-driver/rust/crates/platform-macos/src/cursor/overlay.rs
dispatch_async_f and CGImageRelease extern declarations are reformatted; pixmap_to_cgimage guard and null-image handling use explicit early returns and conditional unwrapping.

Test Expansion & New Quiescence Validation

Layer / File(s) Summary
Assertion clarity & quiescence tests
libs/cua-driver/rust/crates/platform-macos/src/cursor/overlay.rs
Test helpers/assertions are expanded into multi-line readable forms. New tests validate render_map_needs_frame_tick() returns false for sentinel cursor and true for active/fading cursors, confirming quiescence behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • trycua/cua#1781: Touches run_on_main_thread() parking behavior and graphic-session probing; overlaps with main-thread parking logic in this PR.
  • trycua/cua#1790: Initializes the macOS cursor overlay (overlay::init()/run_on_main_thread()), which this PR directly optimizes via render-loop wake/tick/compositing changes.
  • trycua/cua#1787: Adjusts sentinel cursor seeding and interacts with render-loop/tick behavior around sentinel/animated cursors.

Poem

🐰 I nudged the loop to catch some Zs,
no more frantic CPU trapeze.
When cursors rest, I hush the race —
wake only when pixels chase. 🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and concisely summarizes the main change: preventing the macOS overlay render loop from continuously repainting when idle.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #1808: blocking the render loop during quiescence, preventing frame-cadence repainting when idle, and maintaining normal behavior during overlay activity.
Out of Scope Changes check ✅ Passed All changes are directly related to addressing the idle CPU usage issue; the formatting changes are acknowledged as incidental from cargo fmt and do not represent scope creep.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/cua-driver/rust/crates/platform-macos/src/cursor/overlay.rs`:
- Around line 585-599: The frame delta dt is being computed after a blocking
rx.recv() so the first frame after waking includes the entire idle duration
(clamped to 50ms) and causes wake-up animations to jump; modify the logic in the
loop around first_msg/frame_tick_needed so that when we woke due to a received
message (i.e., first_msg.is_some() or had_msg is true after rx.recv()) you reset
last_tick or set dt = 0.0 before using it for ticking/animation updates
(affecting the code paths that use dt in the later tick-on-had_msg section),
ensuring the first post-idle frame starts from the initial animation state
rather than partway through.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7dae37a7-1958-4488-8187-f11fd4568e37

📥 Commits

Reviewing files that changed from the base of the PR and between c08f544 and e2f181e.

📒 Files selected for processing (1)
  • libs/cua-driver/rust/crates/platform-macos/src/cursor/overlay.rs

Comment thread libs/cua-driver/rust/crates/platform-macos/src/cursor/overlay.rs
@redwine99

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@redwine99

Copy link
Copy Markdown
Contributor Author

I installed the patched cua-driver binary into the existing /Applications/CuaDriver.app bundle on macOS and verified the fix against the real app-bundle daemon path:

/Applications/CuaDriver.app/Contents/MacOS/cua-driver serve

The installed binary matched the patched release build by SHA-256, and idle CPU remained near zero:

avg_cpu=0.01 samples=8 values=0.0,0.0,0.0,0.0,0.0,0.1,0.0,0.0

I also verified the on-demand daemon path used by the MCP server. Hermes was configured to run:

mcp_servers:
cua-driver:
command: /Users/test/.local/bin/cua-driver
args:
- mcp

where /Users/test/.local/bin/cua-driver resolves to the app-bundle binary.

One macOS-specific note from testing a locally replaced app-bundle binary: after replacing and ad-hoc re-signing the bundle, Screen Recording permission had to be reset/re-added for /Applications/CuaDriver.app, and all existing cua-driver mcp / cua-driver serve processes had to be stopped so TCC would apply the new code identity. After that, Finder screenshot capture worked again, confirming the patched daemon still functions end-to-end.

Let the agent-cursor overlay render loop block on incoming commands
when no cursor animation, fade, click pulse, or focus rect can change
pixels. This keeps an idle `cua-driver serve` daemon from compositing a
fullscreen pixmap at roughly 60fps while preserving frame ticks during
active cursor motion.

Add focused tests for the quiescent sentinel-cursor state and for active
cursor state that should continue requesting frame ticks. The committed
file also includes the Rust formatting changes from running `cargo fmt`,
so nearby existing code may be reflowed in addition to the logic change.

Following CONTRIBUTING.md, ran formatting and validation before preparing
this PR: `cargo fmt`, `cargo build -p cua-driver`, and
`cargo test -p platform-macos --lib`. I also ran the repository
pre-commit configuration against the changed Rust file; all configured
hooks skipped because they target TS/JS/JSON/Markdown/YAML or Python
files, not Rust.
Avoid charging the render loop's blocking recv() idle duration to the
first animation tick after a command arrives. This keeps wake-up frames
at the initial animation state instead of advancing by the clamped idle
delta.

Validated with `cargo build -p cua-driver`,
`cargo test -p platform-macos --lib`, and the local idle CPU sampling
helper.
@redwine99
redwine99 force-pushed the fix/macos-overlay-idle-cpu branch from 8b068ff to d347430 Compare June 16, 2026 07:10
@redwine99

Copy link
Copy Markdown
Contributor Author

Rebased this branch onto current main after the cua-driver-rs-v0.5.4 release so the patch applies cleanly on top of the latest driver code.

Local validation on the rebased branch:

  • Built release binary with cargo build --release -p cua-driver
  • Verified the binary reports cua-driver 0.5.4
  • Ran the patched daemon directly on a temporary socket and sampled idle CPU:
avg_cpu=0.30 samples=8 values=0.0,0.0,0.0,0.0,2.4,0.0,0.0,0.0

I also checked formatting for the changed macOS overlay file only:

rustfmt --edition 2021 --check crates/platform-macos/src/cursor/overlay.rs

Note: a full cargo fmt --all --check currently reports formatting diffs in files outside this PR's touched macOS overlay file on current main, so I did not include those unrelated formatter changes in this branch.

@redwine99

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@redwine99

redwine99 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

@f-trycua Could you take a look when you have a chance? The PR is currently mergeable.

@f-trycua f-trycua left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed: tightly-scoped macOS overlay idle-CPU fix — stops idle frame ticks when nothing is animating. CodeRabbit green, mergeable, no conflicts. LGTM.

@f-trycua
f-trycua merged commit 31dda4b into trycua:main Jun 18, 2026
1 check passed
f-trycua added a commit that referenced this pull request Jun 18, 2026
…#1808) (#1933)

* fix(cua-driver-rs)(windows): stop idle overlay CPU + orphan mcp child (#1808)

The Windows agent-cursor overlay render timer ran at ~125 Hz unconditionally:
every WM_TIMER tick allocated a full virtual-screen tiny-skia pixmap, swizzled
it RGBA->BGRA pixel-by-pixel, and blitted it via UpdateLayeredWindow — even
when no cursor was animating and the pointer was static. An idle `cua-driver
mcp` therefore pinned 60-85% of a CPU core (issue #1808), and long-lived
instances accumulated CPU-hours.

Part A (idle CPU): mirror the macOS fix (#1865). Add a `needs_frame_tick`
predicate (in-flight path / spring / click pulse / unfinished idle-fade) and
gate the composite+blit+z-order behind it. The render timer is now re-armed
between an ACTIVE cadence (~125 Hz, smooth animation) and a slow IDLE heartbeat
(250 ms) once every cursor goes quiescent. `send_command` / `remove_cursor`
call `wake_overlay()` to flip back to ACTIVE within ~8 ms via a cross-thread
SetTimer, so the first move after idle is not delayed. A final settle frame is
still emitted as animations finish, so the layered window is left in its
resting/cleared state before the loop parks. No full-screen pixmap allocation,
no RGBA->BGRA copy, no UpdateLayeredWindow while idle.

Part B (orphan on disconnect): the overlay runs on a detached STA thread with
its own Win32 message loop, so returning from `async_main` after the stdio MCP
server loop ended (stdin EOF) was not guaranteed to tear it down promptly. The
in-process Windows/Linux `mcp` path now `std::process::exit`es once
`server::run` returns, mirroring the macOS arm, so the overlay thread dies with
the process the moment the client disconnects.

Adds headless unit tests for the quiescent-sentinel state, the active-animation
state, and the click-pulse-then-quiescent transition. Verified `platform-windows`
cross-compiles cleanly for x86_64-pc-windows-msvc and all platform-windows lib
tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(cua-driver-rs): keep Cargo.lock matching main (avoid nix cargoHash churn)

The overlay fix needs no new dependencies; an incidental cargo build had
re-synced the workspace member versions (0.5.3 -> 0.5.6) in Cargo.lock,
which fetchCargoVendor hashes, breaking the Nix cargoHash and turning every
Linux nix job red. Restore Cargo.lock to main's committed state so the hash
stays valid. (The Cargo.toml/Cargo.lock version drift on main is a separate
pre-existing issue, not this PR's concern.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

~50% CPU usage when idle

2 participants