mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 14:01:06 +00:00
fix(coding-agent): convert clipboard BMP images to PNG on paste
WSL2/WSLg often provides clipboard images as image/bmp only. Previously this resulted in invalid PNG files being written. Now readClipboardImage() converts unsupported formats to PNG via Photon before returning. Closes #1109 Based on #1112 by @lightningRalf
This commit is contained in:
parent
6f2d066342
commit
38ed9e86f8
3 changed files with 148 additions and 10 deletions
|
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* Test for BMP to PNG conversion in clipboard image handling.
|
||||
* Separate from clipboard-image.test.ts due to different mocking requirements.
|
||||
*
|
||||
* This tests the fix for WSL2/WSLg where clipboard often provides image/bmp
|
||||
* instead of image/png.
|
||||
*/
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
function createTinyBmp1x1Red24bpp(): Uint8Array {
|
||||
// Minimal 1x1 24bpp BMP (BGR + row padding to 4 bytes)
|
||||
// File size = 14 (BMP header) + 40 (DIB header) + 4 (pixel row) = 58
|
||||
const buffer = Buffer.alloc(58);
|
||||
|
||||
// BITMAPFILEHEADER
|
||||
buffer.write("BM", 0, "ascii");
|
||||
buffer.writeUInt32LE(buffer.length, 2); // file size
|
||||
buffer.writeUInt16LE(0, 6); // reserved1
|
||||
buffer.writeUInt16LE(0, 8); // reserved2
|
||||
buffer.writeUInt32LE(54, 10); // pixel data offset
|
||||
|
||||
// BITMAPINFOHEADER
|
||||
buffer.writeUInt32LE(40, 14); // DIB header size
|
||||
buffer.writeInt32LE(1, 18); // width
|
||||
buffer.writeInt32LE(1, 22); // height (positive = bottom-up)
|
||||
buffer.writeUInt16LE(1, 26); // planes
|
||||
buffer.writeUInt16LE(24, 28); // bits per pixel
|
||||
buffer.writeUInt32LE(0, 30); // compression (BI_RGB)
|
||||
buffer.writeUInt32LE(4, 34); // image size (incl. padding)
|
||||
buffer.writeInt32LE(0, 38); // x pixels per meter
|
||||
buffer.writeInt32LE(0, 42); // y pixels per meter
|
||||
buffer.writeUInt32LE(0, 46); // colors used
|
||||
buffer.writeUInt32LE(0, 50); // important colors
|
||||
|
||||
// Pixel data (B, G, R) + 1 byte padding
|
||||
buffer[54] = 0x00; // B
|
||||
buffer[55] = 0x00; // G
|
||||
buffer[56] = 0xff; // R
|
||||
buffer[57] = 0x00; // padding
|
||||
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
// Mock wl-paste to return BMP
|
||||
vi.mock("child_process", async () => {
|
||||
const actual = await vi.importActual<typeof import("child_process")>("child_process");
|
||||
return {
|
||||
...actual,
|
||||
spawnSync: vi.fn((command: string, args: string[]) => {
|
||||
if (command === "wl-paste" && args.includes("--list-types")) {
|
||||
return { status: 0, stdout: Buffer.from("image/bmp\n"), error: null };
|
||||
}
|
||||
if (command === "wl-paste" && args.includes("image/bmp")) {
|
||||
return { status: 0, stdout: Buffer.from(createTinyBmp1x1Red24bpp()), error: null };
|
||||
}
|
||||
return { status: 1, stdout: Buffer.alloc(0), error: null };
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock the native clipboard (not used in Wayland path, but needs to be mocked)
|
||||
vi.mock("@mariozechner/clipboard", () => ({
|
||||
default: {
|
||||
hasImage: vi.fn(() => false),
|
||||
getImageBinary: vi.fn(() => Promise.resolve(null)),
|
||||
},
|
||||
}));
|
||||
|
||||
describe("readClipboardImage BMP conversion", () => {
|
||||
test("converts BMP to PNG on Wayland/WSLg", async () => {
|
||||
const { readClipboardImage } = await import("../src/utils/clipboard-image.js");
|
||||
|
||||
// Simulate Wayland session (WSLg)
|
||||
const image = await readClipboardImage({
|
||||
env: { WAYLAND_DISPLAY: "wayland-0" },
|
||||
platform: "linux",
|
||||
});
|
||||
|
||||
expect(image).not.toBeNull();
|
||||
expect(image!.mimeType).toBe("image/png");
|
||||
|
||||
// Verify PNG magic bytes
|
||||
expect(image!.bytes[0]).toBe(0x89);
|
||||
expect(image!.bytes[1]).toBe(0x50); // P
|
||||
expect(image!.bytes[2]).toBe(0x4e); // N
|
||||
expect(image!.bytes[3]).toBe(0x47); // G
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue