From 6e5c6edf538121ecf2b5a816143866819e322dea Mon Sep 17 00:00:00 2001 From: Nashim Aktar Mondal <101592496+Namthenikki@users.noreply.github.com> Date: Tue, 3 Nov 2026 14:27:30 +0530 Subject: [PATCH 1/2] fix(clipboard): enforce UTF-8 encoding for Windows clipboard output --- src/server/clipboard.test.ts | 6 +++++- src/server/clipboard.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/server/clipboard.test.ts b/src/server/clipboard.test.ts index 34547769..f1d88e33 100644 --- a/src/server/clipboard.test.ts +++ b/src/server/clipboard.test.ts @@ -91,7 +91,11 @@ describe("Clipboard helper", () => { expect(text).toBe("windows clipboard text") expect(spawn).toHaveBeenCalledWith( "powershell.exe", - expect.arrayContaining(["Get-Clipboard -Raw"]), + expect.arrayContaining([ + expect.stringMatching( + /\[Console\]::OutputEncoding\s*=\s*\[System\.Text\.Encoding\]::UTF8;\s*Get-Clipboard -Raw/, + ), + ]), expect.any(Object), ) }) diff --git a/src/server/clipboard.ts b/src/server/clipboard.ts index e033c53f..9975b7de 100644 --- a/src/server/clipboard.ts +++ b/src/server/clipboard.ts @@ -64,7 +64,7 @@ export async function getSystemClipboard(): Promise { const text = await spawnCaptureOutput("powershell.exe", [ "-NoProfile", "-Command", - "Get-Clipboard -Raw", + "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Clipboard -Raw", ]) return text.replace(/\r?\n$/, "") } From 52e672df5735ea6b6b4e601909728896d05ca808 Mon Sep 17 00:00:00 2001 From: Nashim Aktar Mondal <101592496+Namthenikki@users.noreply.github.com> Date: Fri, 6 Nov 2026 13:36:02 +0530 Subject: [PATCH 2/2] fix(clipboard): preserve UTF-8 output and handle split stdout chunks --- src/server/clipboard.test.ts | 15 ++++++++++++++- src/server/clipboard.ts | 11 +++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/server/clipboard.test.ts b/src/server/clipboard.test.ts index f1d88e33..1eac1e1e 100644 --- a/src/server/clipboard.test.ts +++ b/src/server/clipboard.test.ts @@ -96,10 +96,23 @@ describe("Clipboard helper", () => { /\[Console\]::OutputEncoding\s*=\s*\[System\.Text\.Encoding\]::UTF8;\s*Get-Clipboard -Raw/, ), ]), - expect.any(Object), + expect.objectContaining({ windowsHide: true }), ) }) + it("should keep a character split across stdout chunks", async () => { + vi.mocked(os.platform).mockReturnValue("win32") + const proc = createMockProcess({}) + vi.mocked(spawn).mockReturnValue(proc) + const rocket = Buffer.from("🚀") + + const pending = getSystemClipboard() + proc.stdout.emit("data", rocket.subarray(0, 2)) + proc.stdout.emit("data", rocket.subarray(2)) + + await expect(pending).resolves.toBe("🚀") + }) + it("should read clipboard on linux using fallback when first command fails", async () => { vi.mocked(os.platform).mockReturnValue("linux") const origEnv = process.env.XDG_SESSION_TYPE diff --git a/src/server/clipboard.ts b/src/server/clipboard.ts index 9975b7de..ed3e8b14 100644 --- a/src/server/clipboard.ts +++ b/src/server/clipboard.ts @@ -34,17 +34,20 @@ function spawnWithStdin( */ function spawnCaptureOutput(cmd: string, args: string[]): Promise { return new Promise((resolve, reject) => { - const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "ignore"] }) - let output = "" + const child = spawn(cmd, args, { + stdio: ["ignore", "pipe", "ignore"], + windowsHide: true, + }) + const chunks: Buffer[] = [] child.stdout.on("data", (chunk: Buffer) => { - output += chunk.toString("utf-8") + chunks.push(chunk) }) child.on("error", reject) child.on("close", (code) => { - if (code === 0) resolve(output) + if (code === 0) resolve(Buffer.concat(chunks).toString("utf-8")) else reject(new Error(`${cmd} exited with code ${code}`)) }) })