Skip to content

Commit d2d5398

Browse files
committed
tui: enable primary clipboard copy for Wayland/X11 to fix Linux middle-click paste
Adds optional config `clipboard.linux.enablePrimaryCopy` to copy to primary clipboard in addition to regular clipboard, enabling middle-click paste on Linux and improving compatibility with environments like VMware Workstation that sync only the primary clipboard.
1 parent 8de9e47 commit d2d5398

2 files changed

Lines changed: 51 additions & 0 deletions

File tree

packages/opencode/src/cli/cmd/tui/util/clipboard.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import clipboardy from "clipboardy"
55
import { lazy } from "../../../../util/lazy.js"
66
import { tmpdir } from "os"
77
import path from "path"
8+
import { Config } from "../../../../config/config.js"
89

910
const rendererRef = { current: undefined as CliRenderer | undefined }
1011

@@ -80,15 +81,29 @@ export namespace Clipboard {
8081
if (process.env["WAYLAND_DISPLAY"] && Bun.which("wl-copy")) {
8182
console.log("clipboard: using wl-copy")
8283
return async (text: string) => {
84+
const config = await Config.get().catch(() => ({}))
85+
const enablePrimary = (config as Config.Info).clipboard?.linux?.enablePrimaryCopy ?? false
8386
const proc = Bun.spawn(["wl-copy"], { stdin: "pipe", stdout: "ignore", stderr: "ignore" })
8487
proc.stdin.write(text)
8588
proc.stdin.end()
8689
await proc.exited.catch(() => {})
90+
if (enablePrimary) {
91+
const procPrimary = Bun.spawn(["wl-copy", "--primary"], {
92+
stdin: "pipe",
93+
stdout: "ignore",
94+
stderr: "ignore",
95+
})
96+
procPrimary.stdin.write(text)
97+
procPrimary.stdin.end()
98+
await procPrimary.exited.catch(() => {})
99+
}
87100
}
88101
}
89102
if (Bun.which("xclip")) {
90103
console.log("clipboard: using xclip")
91104
return async (text: string) => {
105+
const config = await Config.get().catch(() => ({}))
106+
const enablePrimary = (config as Config.Info).clipboard?.linux?.enablePrimaryCopy ?? false
92107
const proc = Bun.spawn(["xclip", "-selection", "clipboard"], {
93108
stdin: "pipe",
94109
stdout: "ignore",
@@ -97,11 +112,23 @@ export namespace Clipboard {
97112
proc.stdin.write(text)
98113
proc.stdin.end()
99114
await proc.exited.catch(() => {})
115+
if (enablePrimary) {
116+
const procPrimary = Bun.spawn(["xclip", "-selection", "primary"], {
117+
stdin: "pipe",
118+
stdout: "ignore",
119+
stderr: "ignore",
120+
})
121+
procPrimary.stdin.write(text)
122+
procPrimary.stdin.end()
123+
await procPrimary.exited.catch(() => {})
124+
}
100125
}
101126
}
102127
if (Bun.which("xsel")) {
103128
console.log("clipboard: using xsel")
104129
return async (text: string) => {
130+
const config = await Config.get().catch(() => ({}))
131+
const enablePrimary = (config as Config.Info).clipboard?.linux?.enablePrimaryCopy ?? false
105132
const proc = Bun.spawn(["xsel", "--clipboard", "--input"], {
106133
stdin: "pipe",
107134
stdout: "ignore",
@@ -110,6 +137,16 @@ export namespace Clipboard {
110137
proc.stdin.write(text)
111138
proc.stdin.end()
112139
await proc.exited.catch(() => {})
140+
if (enablePrimary) {
141+
const procPrimary = Bun.spawn(["xsel", "--primary", "--input"], {
142+
stdin: "pipe",
143+
stdout: "ignore",
144+
stderr: "ignore",
145+
})
146+
procPrimary.stdin.write(text)
147+
procPrimary.stdin.end()
148+
await procPrimary.exited.catch(() => {})
149+
}
113150
}
114151
}
115152
}

packages/opencode/src/config/config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,20 @@ export namespace Config {
929929
.object({
930930
$schema: z.string().optional().describe("JSON schema reference for configuration validation"),
931931
theme: z.string().optional().describe("Theme name to use for the interface"),
932+
clipboard: z
933+
.object({
934+
linux: z
935+
.object({
936+
enablePrimaryCopy: z
937+
.boolean()
938+
.optional()
939+
.default(false)
940+
.describe("Copy to primary clipboard in addition to regular clipboard on Linux (Wayland/X11)"),
941+
})
942+
.optional(),
943+
})
944+
.optional()
945+
.describe("Clipboard configuration"),
932946
keybinds: Keybinds.optional().describe("Custom keybind configurations"),
933947
logLevel: Log.Level.optional().describe("Log level"),
934948
tui: TUI.optional().describe("TUI specific settings"),

0 commit comments

Comments
 (0)