diff --git a/packages/coding-agent/src/utils/clipboard-image.ts b/packages/coding-agent/src/utils/clipboard-image.ts index 44f8fb108..e206703d3 100644 --- a/packages/coding-agent/src/utils/clipboard-image.ts +++ b/packages/coding-agent/src/utils/clipboard-image.ts @@ -277,7 +277,7 @@ export async function readClipboardImage(options?: { } if (!image && !wayland) { - image = await readClipboardImageViaNativeClipboard(); + image = (await readClipboardImageViaNativeClipboard()) ?? readClipboardImageViaXclip(); } } else { image = await readClipboardImageViaNativeClipboard(); diff --git a/packages/coding-agent/test/clipboard-image.test.ts b/packages/coding-agent/test/clipboard-image.test.ts index b7416a4c6..eb2ba613a 100644 --- a/packages/coding-agent/test/clipboard-image.test.ts +++ b/packages/coding-agent/test/clipboard-image.test.ts @@ -147,7 +147,9 @@ describe("readClipboardImage", () => { test("Non-Wayland: uses clipboard", async () => { mocks.spawnSync.mockImplementation(() => { - throw new Error("spawnSync should not be called for non-Wayland sessions"); + throw new Error( + "spawnSync should not be called for non-Wayland sessions when native clipboard returns an image", + ); }); mocks.clipboard.hasImage.mockReturnValue(true); @@ -160,15 +162,23 @@ describe("readClipboardImage", () => { expect(Array.from(result?.bytes ?? [])).toEqual([7]); }); - test("Non-Wayland: returns null when clipboard has no image", async () => { - mocks.spawnSync.mockImplementation(() => { - throw new Error("spawnSync should not be called for non-Wayland sessions"); + test("Non-Wayland: falls back to xclip when clipboard has no image", async () => { + mocks.spawnSync.mockImplementation((command, args, _options) => { + if (command === "xclip" && args.includes("TARGETS")) { + return spawnOk(Buffer.from("image/png\n", "utf-8")); + } + if (command === "xclip" && args.includes("image/png")) { + return spawnOk(Buffer.from([8, 9])); + } + throw new Error(`Unexpected spawnSync call: ${command} ${args.join(" ")}`); }); mocks.clipboard.hasImage.mockReturnValue(false); const { readClipboardImage } = await import("../src/utils/clipboard-image.ts"); const result = await readClipboardImage({ platform: "linux", env: {} }); - expect(result).toBeNull(); + expect(result).not.toBeNull(); + expect(result?.mimeType).toBe("image/png"); + expect(Array.from(result?.bytes ?? [])).toEqual([8, 9]); }); }); diff --git a/scripts/build-binaries.sh b/scripts/build-binaries.sh index 95c9bf82a..c261aaf20 100755 --- a/scripts/build-binaries.sh +++ b/scripts/build-binaries.sh @@ -159,26 +159,34 @@ for platform in "${PLATFORMS[@]}"; do case "$platform" in darwin-arm64) clipboard_native_package="clipboard-darwin-arm64" + clipboard_native_file="clipboard.darwin-arm64.node" ;; darwin-x64) clipboard_native_package="clipboard-darwin-x64" + clipboard_native_file="clipboard.darwin-x64.node" ;; linux-x64) clipboard_native_package="clipboard-linux-x64-gnu" + clipboard_native_file="clipboard.linux-x64-gnu.node" ;; linux-arm64) clipboard_native_package="clipboard-linux-arm64-gnu" + clipboard_native_file="clipboard.linux-arm64-gnu.node" ;; windows-x64) clipboard_native_package="clipboard-win32-x64-msvc" + clipboard_native_file="clipboard.win32-x64-msvc.node" ;; windows-arm64) clipboard_native_package="clipboard-win32-arm64-msvc" + clipboard_native_file="clipboard.win32-arm64-msvc.node" ;; esac mkdir -p "$OUTPUT_DIR/$platform/node_modules/@mariozechner" cp -r ../../node_modules/@mariozechner/clipboard "$OUTPUT_DIR/$platform/node_modules/@mariozechner/" cp -r ../../node_modules/@mariozechner/$clipboard_native_package "$OUTPUT_DIR/$platform/node_modules/@mariozechner/" + cp "../../node_modules/@mariozechner/$clipboard_native_package/$clipboard_native_file" \ + "$OUTPUT_DIR/$platform/node_modules/@mariozechner/clipboard/" # Copy terminal input native helpers next to compiled binaries. if [[ "$platform" == darwin-* ]]; then