diff --git a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts index 398aff5af27..cc28a8b6909 100644 --- a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts +++ b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts @@ -4,6 +4,7 @@ import clipboardy from "clipboardy" import { lazy } from "../../../../util/lazy.js" import { tmpdir } from "os" import path from "path" +import { Config } from "../../../../config/config.js" export namespace Clipboard { export interface Content { @@ -73,15 +74,29 @@ export namespace Clipboard { if (process.env["WAYLAND_DISPLAY"] && Bun.which("wl-copy")) { console.log("clipboard: using wl-copy") return async (text: string) => { + const config = await Config.get().catch(() => ({})) + const enablePrimary = (config as Config.Info).clipboard?.linux?.enablePrimaryCopy ?? false const proc = Bun.spawn(["wl-copy"], { stdin: "pipe", stdout: "ignore", stderr: "ignore" }) proc.stdin.write(text) proc.stdin.end() await proc.exited.catch(() => {}) + if (enablePrimary) { + const procPrimary = Bun.spawn(["wl-copy", "--primary"], { + stdin: "pipe", + stdout: "ignore", + stderr: "ignore", + }) + procPrimary.stdin.write(text) + procPrimary.stdin.end() + await procPrimary.exited.catch(() => {}) + } } } if (Bun.which("xclip")) { console.log("clipboard: using xclip") return async (text: string) => { + const config = await Config.get().catch(() => ({})) + const enablePrimary = (config as Config.Info).clipboard?.linux?.enablePrimaryCopy ?? false const proc = Bun.spawn(["xclip", "-selection", "clipboard"], { stdin: "pipe", stdout: "ignore", @@ -90,11 +105,23 @@ export namespace Clipboard { proc.stdin.write(text) proc.stdin.end() await proc.exited.catch(() => {}) + if (enablePrimary) { + const procPrimary = Bun.spawn(["xclip", "-selection", "primary"], { + stdin: "pipe", + stdout: "ignore", + stderr: "ignore", + }) + procPrimary.stdin.write(text) + procPrimary.stdin.end() + await procPrimary.exited.catch(() => {}) + } } } if (Bun.which("xsel")) { console.log("clipboard: using xsel") return async (text: string) => { + const config = await Config.get().catch(() => ({})) + const enablePrimary = (config as Config.Info).clipboard?.linux?.enablePrimaryCopy ?? false const proc = Bun.spawn(["xsel", "--clipboard", "--input"], { stdin: "pipe", stdout: "ignore", @@ -103,6 +130,16 @@ export namespace Clipboard { proc.stdin.write(text) proc.stdin.end() await proc.exited.catch(() => {}) + if (enablePrimary) { + const procPrimary = Bun.spawn(["xsel", "--primary", "--input"], { + stdin: "pipe", + stdout: "ignore", + stderr: "ignore", + }) + procPrimary.stdin.write(text) + procPrimary.stdin.end() + await procPrimary.exited.catch(() => {}) + } } } } diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 66f42e5a851..cc5a73393ec 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -651,6 +651,20 @@ export namespace Config { .object({ $schema: z.string().optional().describe("JSON schema reference for configuration validation"), theme: z.string().optional().describe("Theme name to use for the interface"), + clipboard: z + .object({ + linux: z + .object({ + enablePrimaryCopy: z + .boolean() + .optional() + .default(false) + .describe("Copy to primary clipboard in addition to regular clipboard on Linux (Wayland/X11)"), + }) + .optional(), + }) + .optional() + .describe("Clipboard configuration"), keybinds: Keybinds.optional().describe("Custom keybind configurations"), logLevel: Log.Level.optional().describe("Log level"), tui: TUI.optional().describe("TUI specific settings"),