diff --git a/packages/pi-coding-agent/package.json b/packages/pi-coding-agent/package.json index 256113ec8..dbb0e3910 100644 --- a/packages/pi-coding-agent/package.json +++ b/packages/pi-coding-agent/package.json @@ -44,9 +44,6 @@ "undici": "^7.19.1", "yaml": "^2.8.2" }, - "optionalDependencies": { - "@mariozechner/clipboard": "^0.3.2" - }, "devDependencies": { "@types/diff": "^7.0.2", "@types/hosted-git-info": "^3.0.5", diff --git a/packages/pi-coding-agent/src/utils/clipboard-image.ts b/packages/pi-coding-agent/src/utils/clipboard-image.ts index 58cd5913f..bc3ebb9f3 100644 --- a/packages/pi-coding-agent/src/utils/clipboard-image.ts +++ b/packages/pi-coding-agent/src/utils/clipboard-image.ts @@ -1,6 +1,6 @@ import { spawnSync } from "child_process"; -import { clipboard } from "./clipboard-native.js"; +import { readImageFromClipboard as nativeReadImage } from "@gsd/native/clipboard"; import { loadPhoton } from "./photon.js"; export type ClipboardImage = { @@ -175,19 +175,20 @@ export async function readClipboardImage(options?: { let image: ClipboardImage | null = null; if (platform === "linux" && isWaylandSession(env)) { + // Wayland: use CLI tools (wl-paste/xclip) since native arboard + // may not have access to the Wayland compositor from a terminal. image = readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip(); } else { - if (!clipboard || !clipboard.hasImage()) { + // macOS, Windows, Linux X11: use native Rust clipboard (arboard) + try { + const nativeImage = await nativeReadImage(); + if (!nativeImage || nativeImage.data.length === 0) { + return null; + } + image = { bytes: nativeImage.data, mimeType: nativeImage.mimeType }; + } catch { return null; } - - const imageData = await clipboard.getImageBinary(); - if (!imageData || imageData.length === 0) { - return null; - } - - const bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData); - image = { bytes, mimeType: "image/png" }; } if (!image) { diff --git a/packages/pi-coding-agent/src/utils/clipboard-native.ts b/packages/pi-coding-agent/src/utils/clipboard-native.ts index e176d47c3..c4233ba77 100644 --- a/packages/pi-coding-agent/src/utils/clipboard-native.ts +++ b/packages/pi-coding-agent/src/utils/clipboard-native.ts @@ -1,21 +1,11 @@ -import { createRequire } from "module"; - -export type ClipboardModule = { - hasImage: () => boolean; - getImageBinary: () => Promise>; -}; - -const require = createRequire(import.meta.url); -let clipboard: ClipboardModule | null = null; - -const hasDisplay = process.platform !== "linux" || Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY); - -if (!process.env.TERMUX_VERSION && hasDisplay) { - try { - clipboard = require("@mariozechner/clipboard") as ClipboardModule; - } catch { - clipboard = null; - } -} - -export { clipboard }; +/** + * Re-export native clipboard utilities from @gsd/native. + * + * This module exists for backward compatibility. Prefer importing + * directly from "@gsd/native/clipboard" in new code. + */ +export { + copyToClipboard, + readTextFromClipboard, + readImageFromClipboard, +} from "@gsd/native/clipboard"; diff --git a/packages/pi-coding-agent/src/utils/clipboard.ts b/packages/pi-coding-agent/src/utils/clipboard.ts index 233b0eda0..4d1e1b6dd 100644 --- a/packages/pi-coding-agent/src/utils/clipboard.ts +++ b/packages/pi-coding-agent/src/utils/clipboard.ts @@ -1,61 +1,13 @@ -import { execSync, spawn } from "child_process"; -import { platform } from "os"; -import { isWaylandSession } from "./clipboard-image.js"; +import { copyToClipboard as nativeCopy } from "@gsd/native/clipboard"; export function copyToClipboard(text: string): void { // Always emit OSC 52 - works over SSH/mosh, harmless locally const encoded = Buffer.from(text).toString("base64"); process.stdout.write(`\x1b]52;c;${encoded}\x07`); - // Also try native tools (best effort for local sessions) - const p = platform(); - const options = { input: text, timeout: 5000 }; - + // Use native clipboard for local sessions (best effort) try { - if (p === "darwin") { - execSync("pbcopy", options); - } else if (p === "win32") { - execSync("clip", options); - } else { - // Linux. Try Termux, Wayland, or X11 clipboard tools. - if (process.env.TERMUX_VERSION) { - try { - execSync("termux-clipboard-set", options); - return; - } catch { - // Fall back to Wayland or X11 tools. - } - } - - const isWayland = isWaylandSession(); - if (isWayland) { - try { - // Verify wl-copy exists (spawn errors are async and won't be caught) - execSync("which wl-copy", { stdio: "ignore" }); - // wl-copy with execSync hangs due to fork behavior; use spawn instead - const proc = spawn("wl-copy", [], { stdio: ["pipe", "ignore", "ignore"] }); - proc.stdin.on("error", () => { - // Ignore EPIPE errors if wl-copy exits early - }); - proc.stdin.write(text); - proc.stdin.end(); - proc.unref(); - } catch { - // Fall back to xclip/xsel (works on XWayland) - try { - execSync("xclip -selection clipboard", options); - } catch { - execSync("xsel --clipboard --input", options); - } - } - } else { - try { - execSync("xclip -selection clipboard", options); - } catch { - execSync("xsel --clipboard --input", options); - } - } - } + nativeCopy(text); } catch { // Ignore - OSC 52 already emitted as fallback }