mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
Merge pull request #7387 from earendil-works/issue/7248
fix(coding-agent): read clipboard text on Wayland
This commit is contained in:
commit
7724472eab
2 changed files with 71 additions and 3 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { execSync, spawn } from "child_process";
|
||||
import { type ExecFileSyncOptionsWithStringEncoding, execFileSync, execSync, spawn } from "child_process";
|
||||
import { platform } from "os";
|
||||
import { isWaylandSession } from "./clipboard-image.ts";
|
||||
import { clipboard } from "./clipboard-native.ts";
|
||||
|
|
@ -32,8 +32,32 @@ function emitOsc52(text: string): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
/** Read plain text from the system clipboard, if native clipboard access is available. */
|
||||
type ClipboardReadResult = { ok: true; text: string | null } | { ok: false };
|
||||
|
||||
const READ_CLIPBOARD_OPTIONS: ExecFileSyncOptionsWithStringEncoding = {
|
||||
encoding: "utf8",
|
||||
maxBuffer: 50 * 1024 * 1024,
|
||||
timeout: 5000,
|
||||
};
|
||||
|
||||
function readWaylandClipboardText(): ClipboardReadResult {
|
||||
try {
|
||||
const text = execFileSync("wl-paste", ["--no-newline", "--type", "text"], READ_CLIPBOARD_OPTIONS);
|
||||
return { ok: true, text: text || null };
|
||||
} catch {
|
||||
return { ok: false };
|
||||
}
|
||||
}
|
||||
|
||||
/** Read plain text from the system clipboard. */
|
||||
export async function readClipboardText(): Promise<string | null> {
|
||||
if (platform() === "linux" && isWaylandSession() && process.env.WAYLAND_DISPLAY) {
|
||||
const result = readWaylandClipboardText();
|
||||
if (result.ok) {
|
||||
return result.text;
|
||||
}
|
||||
}
|
||||
|
||||
if (!clipboard) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { execSync, spawn } from "child_process";
|
||||
import { execFileSync, execSync, spawn } from "child_process";
|
||||
import { platform } from "os";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { copyToClipboard, readClipboardText } from "../src/utils/clipboard.ts";
|
||||
|
|
@ -9,6 +9,7 @@ const mocks = vi.hoisted(() => {
|
|||
getText: vi.fn<() => Promise<string>>(),
|
||||
setText: vi.fn<(text: string) => Promise<void>>(),
|
||||
},
|
||||
execFileSync: vi.fn(),
|
||||
execSync: vi.fn(),
|
||||
spawn: vi.fn(),
|
||||
platform: vi.fn<() => NodeJS.Platform>(),
|
||||
|
|
@ -24,6 +25,7 @@ vi.mock("../src/utils/clipboard-native.js", () => {
|
|||
|
||||
vi.mock("child_process", () => {
|
||||
return {
|
||||
execFileSync: mocks.execFileSync,
|
||||
execSync: mocks.execSync,
|
||||
spawn: mocks.spawn,
|
||||
};
|
||||
|
|
@ -41,6 +43,7 @@ vi.mock("../src/utils/clipboard-image.js", () => {
|
|||
};
|
||||
});
|
||||
|
||||
const mockedExecFileSync = vi.mocked(execFileSync);
|
||||
const mockedExecSync = vi.mocked(execSync);
|
||||
const mockedSpawn = vi.mocked(spawn);
|
||||
const mockedPlatform = vi.mocked(platform);
|
||||
|
|
@ -62,6 +65,7 @@ beforeEach(() => {
|
|||
nativeResolved = false;
|
||||
mocks.clipboard.getText.mockReset();
|
||||
mocks.clipboard.setText.mockReset();
|
||||
mocks.execFileSync.mockReset();
|
||||
mocks.execSync.mockReset();
|
||||
mocks.spawn.mockReset();
|
||||
mocks.platform.mockReset();
|
||||
|
|
@ -96,6 +100,46 @@ describe("readClipboardText", () => {
|
|||
await expect(readClipboardText()).resolves.toBe("clipboard text");
|
||||
});
|
||||
|
||||
test("reads the Wayland clipboard before the stale native X11 clipboard", async () => {
|
||||
// Regression test for #7248.
|
||||
mockedPlatform.mockReturnValue("linux");
|
||||
mocks.isWaylandSession.mockReturnValue(true);
|
||||
vi.stubEnv("WAYLAND_DISPLAY", "wayland-0");
|
||||
mockedExecFileSync.mockReturnValue("Wayland text");
|
||||
mocks.clipboard.getText.mockResolvedValue("stale X11 text");
|
||||
|
||||
await expect(readClipboardText()).resolves.toBe("Wayland text");
|
||||
expect(mockedExecFileSync).toHaveBeenCalledWith("wl-paste", ["--no-newline", "--type", "text"], {
|
||||
encoding: "utf8",
|
||||
maxBuffer: 50 * 1024 * 1024,
|
||||
timeout: 5000,
|
||||
});
|
||||
expect(mocks.clipboard.getText).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("does not fall back to stale X11 text when the Wayland clipboard is empty", async () => {
|
||||
mockedPlatform.mockReturnValue("linux");
|
||||
mocks.isWaylandSession.mockReturnValue(true);
|
||||
vi.stubEnv("WAYLAND_DISPLAY", "wayland-0");
|
||||
mockedExecFileSync.mockReturnValue("");
|
||||
mocks.clipboard.getText.mockResolvedValue("stale X11 text");
|
||||
|
||||
await expect(readClipboardText()).resolves.toBeNull();
|
||||
expect(mocks.clipboard.getText).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("falls back to the native clipboard when wl-paste is unavailable", async () => {
|
||||
mockedPlatform.mockReturnValue("linux");
|
||||
mocks.isWaylandSession.mockReturnValue(true);
|
||||
vi.stubEnv("WAYLAND_DISPLAY", "wayland-0");
|
||||
mockedExecFileSync.mockImplementation(() => {
|
||||
throw new Error("wl-paste unavailable");
|
||||
});
|
||||
mocks.clipboard.getText.mockResolvedValue("X11 fallback text");
|
||||
|
||||
await expect(readClipboardText()).resolves.toBe("X11 fallback text");
|
||||
});
|
||||
|
||||
test("returns null for empty or unavailable clipboard text", async () => {
|
||||
await expect(readClipboardText()).resolves.toBeNull();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue