fix: clipboard image paste on Linux (WebKitGTK)#12909
Conversation
WebKitGTK does not expose clipboard images via DataTransfer API (WebKit Bug #218519), so the browser path never finds image items. Previously, the native clipboard fallback (readClipboardImage) was only attempted when no plain text existed in the clipboard. This caused image paste to fail when the clipboard contained both image and text data (common with screenshot tools like Spectacle that include file paths, or when copying images from browsers). Changes: - Move native clipboard read before plain text check so images always take priority over text on desktop (consistent with how Chromium handles clipboard images via DataTransfer) - Add try-catch around readClipboardImage call to prevent silent crashes from propagating - Add console.warn logging for each failure point in the RGBA-to-PNG conversion pipeline to aid debugging on affected systems
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
|
The following comment was made by an LLM, it may be inaccurate: No duplicate PRs found |
There was a problem hiding this comment.
Pull request overview
Fixes Linux desktop (WebKitGTK) clipboard image paste by prioritizing a native clipboard image read path before falling back to plain text, and adds additional logging around the desktop RGBA→PNG conversion path to aid debugging.
Changes:
- Update paste handling to always attempt
readClipboardImage()on desktop before inserting text from the clipboard. - Add warning logs and additional error handling around the desktop clipboard image read + canvas conversion pipeline.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
packages/desktop/src/index.tsx |
Adds warning logs and a try/catch around canvas conversion when building a PNG File from native clipboard RGBA bytes. |
packages/app/src/components/prompt-input/attachments.ts |
Changes paste logic to attempt native clipboard image reads first (even when text is present), with fallback to plain text. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const image = await readImage().catch((e) => { | ||
| console.warn("[clipboard] readImage failed:", e) | ||
| return null | ||
| }) |
There was a problem hiding this comment.
readImage() failures were previously swallowed (returned null) and are now always console.warned. Since readClipboardImage is now attempted even when pasting plain text, this risks producing warnings in the common/expected “clipboard has no image” case and can spam logs. Consider filtering/suppressing known expected errors (e.g., “no image available”), gating these warnings behind import.meta.env.DEV, or downgrading to a debug-level logger for expected failures.
| const imageData = ctx.createImageData(size.width, size.height) | ||
| imageData.data.set(bytes) | ||
| ctx.putImageData(imageData, 0, 0) |
There was a problem hiding this comment.
Before calling imageData.data.set(bytes), consider validating that bytes.length === size.width * size.height * 4 and returning null (with a log) when it doesn’t match. Relying on a thrown RangeError for control flow makes this path harder to reason about and produces less actionable diagnostics (you lose the expected vs actual lengths).
| // Desktop: try native clipboard for images before falling back to text. | ||
| // WebKitGTK does not expose clipboard images via DataTransfer (WebKit Bug #218519), | ||
| // so the browser path above will miss them. Always attempt the native read here | ||
| // regardless of whether text is also present in the clipboard. | ||
| if (input.readClipboardImage) { | ||
| try { | ||
| const file = await input.readClipboardImage() | ||
| if (file) { | ||
| await addImageAttachment(file) | ||
| return | ||
| } |
There was a problem hiding this comment.
This change introduces new paste-precedence behavior (always attempt native clipboard image read before falling back to text, even when text is present). There are bun:test unit tests in this directory, but none covering handlePaste; please add tests for (1) native image success when clipboard also has text, and (2) native read failure falling back to plain text.
GenericTool was ignoring the output prop entirely, so any tool not in the registry (MCP tools, background_output, custom tools) would show only the tool name with no expandable content. Update GenericTool to: - Accept output, defaultOpen, forceOpen, and locked props - Pass output as collapsible children to BasicTool - Render output in a scrollable pre-formatted text block - Use the same data-component="tool-output" as registered tools This makes the expand arrow appear when output exists, and clicking it reveals the tool's output content.
- Fix edit tool Show condition: filediff.path → filediff.file (matches FileDiff schema which uses 'file' not 'path') - Add ErrorBoundary around Diff component in edit tool with text fallback when @pierre/diffs fails to render (shadow DOM/worker issues) - Add try-catch in Diff component's render effect with console.warn and basic text fallback on failure - Fix task tool empty content: only render tool list wrapper when childToolParts has items, show props.output as fallback text when child session data is unavailable - Import ErrorBoundary from solid-js
Summary
Details
Clipboard paste (Linux/WebKitGTK)
WebKitGTK Bug #218519:
clipboardData.itemsreturns empty for images. The nativereadClipboardImage()fallback was gated behind!plainText, preventing it from running when clipboard also contained text (common with screenshot tools). Moved native clipboard attempt before plainText check.Edit tool diff
<Show when={props.metadata.filediff?.path}>referenced nonexistentpathfield — FileDiff schema usesfile. Fixed tofilediff?.file.ErrorBoundaryaround the Dynamic diff component with a text-based fallback when the shadow DOM / web worker based diff rendering fails.createEffectrender loop soFileDiff.render()failures don't silently swallow errors.Task agent empty state
Task tool always rendered wrapper divs even when
childToolParts()was empty (child session data not in store). Now conditionally renders: shows tool list when parts exist, falls back toprops.outputtext when empty.GenericTool output
GenericTool(fallback for unregistered tools) was ignoring theoutputprop entirely. Now passes throughoutput,hideDetails,defaultOpen,forceOpen,lockedand renders output in a collapsible pre block.Test plan