Skip to content

perf(desktop-gpui): read and decode the screenshot clipboard image off the UI thread - #2756

Open
celeroncoder wants to merge 1 commit into
CapSoftware:mainfrom
celeroncoder:perf/gpui-clipboard-decode-off-main
Open

celeroncoder wants to merge 1 commit into
CapSoftware:mainfrom
celeroncoder:perf/gpui-clipboard-decode-off-main

Conversation

@celeroncoder

@celeroncoder celeroncoder commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

The Linux arm was built and tested locally. The macOS and Windows arms were written in the same shape and verified by reading, since I don't have access to macOS or Windows machines; CI has since built the GPUI desktop app on aarch64 and x86_64 macOS, Windows and Linux and run the macOS and Windows test jobs, all green, so this is out of draft.

After a screenshot, screenshot_finished copied the image to the clipboard on the UI thread: std::fs::read of the full-resolution PNG (tens of MB on a Retina display), then per platform the clipboard conversion, right when the tray, library and editor are also refreshing. On Linux that was a format sniff, bytes.to_vec(), a content hash and another clone inside ClipboardItem::new_image; on Windows a full RustImageData::from_bytes decode; on macOS NSData + NSImage + the pasteboard write.

Change

copy_image_to_clipboard(path, cx) now returns a Task: the file read and prepare_clipboard_image run on the background executor, and only write_clipboard_image runs on the UI thread. Each platform module exports the same three items (ClipboardImage, prepare_clipboard_image, write_clipboard_image):

  • Linux: prepare sniffs the format and builds the ClipboardItem from the owned bytes (no to_vec(), no new_image clone; the hash runs in the background). Write is try_write_to_clipboard.
  • Windows: prepare does the RustImageData::from_bytes decode in the background (RustImageData is {u32, u32, Option<DynamicImage>}, so it is Send). ClipboardContext::new and set_image stay on the UI thread.
  • macOS: only the file read moves; NSData, NSImage and the pasteboard write stay on the main thread, same body as before.

Error strings are unchanged. The same treatment is applied to the library "Copy" action, the settings "Copy" action and the editor's export-to-clipboard; the editor's success toast still fires only after the write succeeds. Nothing after screenshot_finished reads the clipboard, so the only observable change is that the clipboard is populated a few milliseconds after the editor opens instead of before.

Results

Linux, debug build (dependencies at opt-level 2), synthetic 5120×2880 PNG of 27.8 MB, medians of 5 runs, 2 vCPU aarch64 Linux box.

main @ 2a61427 This PR
UI-thread work before the clipboard write 48.0 ms (file read alone 8.7 ms) 0 ms
Background prepare — 14.4 ms

The try_write_to_clipboard call itself is unchanged and not included. On Windows, set_image still re-encodes to PNG and BMP on the UI thread; that is the largest remaining cost there and is out of scope here. macOS keeps its AppKit main-thread work by design.

Overlap

How this was made

This PR is agent-generated.

  • The finding comes from an audit of main by OpenCode scouts and Gemini 3.8 Flash, re-checked against the source by the orchestrating Claude session.
  • Claude Opus 5.5 wrote the change on current main, checked clipboard_rs for Send, and took the Linux measurement above.
  • Gemini 3.8 Flash (Antigravity) reviewed the diff independently: it confirmed the Send bounds from the clipboard_rs and gpui sources, the identical signatures across the three platform modules, that every task is detached and every toast fires after the write, re-ran cargo check, clippy and the platform:: tests on Linux, and approved. Neither agent had a macOS or Windows machine to build on.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 4/5

Fix clipboard request ordering before merging so users reliably paste the screenshot they copied last.

Findings

  1. P1 Older copies replace newer ones ▶
Fix with agent prompt
### Issue 1
apps/desktop-gpui/src/platform.rs:565-574
`copy_image_to_clipboard` now writes in the order background work finishes, not the order users click Copy. The library leaves its Copy buttons active and detaches every request. If a user copies a large screenshot and then a smaller one while the first is still preparing, the older request can finish last and replace the newer image. Track request order and skip stale writes, or queue the copies so an older request cannot overwrite a newer one.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Moves screenshot file reads and image preparation off the UI thread.

  • Screenshot copies prepare image data off the UI thread.

Reviews (1) · Last reviewed commit: "perf(desktop-gpui): read and decode the ..." · Reviewed by Greptile

@vercel

vercel Bot commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

@celeroncoder is attempting to deploy a commit to the Cap Software Inc Team on Vercel.

A member of the Team first needs to authorize it.

@celeroncoder

Copy link
Copy Markdown
Contributor Author

cc @richiemcilroy for review (GitHub doesn't let a fork request reviewers, so tagging instead).

@celeroncoder
celeroncoder marked this pull request as ready for review October 20, 2026 12:27
Comment on lines +565 to +574
cx.spawn(async move |cx| {
let image = cx
.background_executor()
.spawn(async move {
let bytes = std::fs::read(&path)
.map_err(|error| format!("Could not read the clipboard image: {error}"))?;
prepare_clipboard_image(bytes)
})
.await?;
cx.update(|cx| write_clipboard_image(image, cx))

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.

P1 Older copies replace newer ones

copy_image_to_clipboard now writes in the order background work finishes, not the order users click Copy. The library leaves its Copy buttons active and detaches every request. If a user copies a large screenshot and then a smaller one while the first is still preparing, the older request can finish last and replace the newer image. Track request order and skip stale writes, or queue the copies so an older request cannot overwrite a newer one.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/src/platform.rs
Line: 565-574

Comment:
**Older copies replace newer ones**

`copy_image_to_clipboard` now writes in the order background work finishes, not the order users click Copy. The library leaves its Copy buttons active and detaches every request. If a user copies a large screenshot and then a smaller one while the first is still preparing, the older request can finish last and replace the newer image. Track request order and skip stale writes, or queue the copies so an older request cannot overwrite a newer one.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

This branch has not been deployed

No deployments
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.

1 participant