Skip to content

Commit d816447

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 5e699c9 commit d816447

2 files changed

Lines changed: 54 additions & 0 deletions

File tree

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import path from "path"
77
import { Filesystem } from "../../../../util/filesystem"
88
import { Process } from "../../../../util/process"
99
import { which } from "../../../../util/which"
10+
import { Config } from "../../../../config/config.js"
1011

1112
/**
1213
* Writes text to clipboard via OSC 52 escape sequence.
@@ -89,16 +90,31 @@ export namespace Clipboard {
8990
if (process.env["WAYLAND_DISPLAY"] && which("wl-copy")) {
9091
console.log("clipboard: using wl-copy")
9192
return async (text: string) => {
93+
const config = await Config.get().catch(() => ({}))
94+
const enablePrimary = (config as Config.Info).clipboard?.linux?.enablePrimaryCopy ?? false
9295
const proc = Process.spawn(["wl-copy"], { stdin: "pipe", stdout: "ignore", stderr: "ignore" })
9396
if (!proc.stdin) return
9497
proc.stdin.write(text)
9598
proc.stdin.end()
9699
await proc.exited.catch(() => {})
100+
if (enablePrimary) {
101+
const procPrimary = Process.spawn(["wl-copy", "--primary"], {
102+
stdin: "pipe",
103+
stdout: "ignore",
104+
stderr: "ignore",
105+
})
106+
if (!procPrimary.stdin) return
107+
procPrimary.stdin.write(text)
108+
procPrimary.stdin.end()
109+
await procPrimary.exited.catch(() => {})
110+
}
97111
}
98112
}
99113
if (which("xclip")) {
100114
console.log("clipboard: using xclip")
101115
return async (text: string) => {
116+
const config = await Config.get().catch(() => ({}))
117+
const enablePrimary = (config as Config.Info).clipboard?.linux?.enablePrimaryCopy ?? false
102118
const proc = Process.spawn(["xclip", "-selection", "clipboard"], {
103119
stdin: "pipe",
104120
stdout: "ignore",
@@ -108,11 +124,24 @@ export namespace Clipboard {
108124
proc.stdin.write(text)
109125
proc.stdin.end()
110126
await proc.exited.catch(() => {})
127+
if (enablePrimary) {
128+
const procPrimary = Process.spawn(["xclip", "-selection", "primary"], {
129+
stdin: "pipe",
130+
stdout: "ignore",
131+
stderr: "ignore",
132+
})
133+
if (!procPrimary.stdin) return
134+
procPrimary.stdin.write(text)
135+
procPrimary.stdin.end()
136+
await procPrimary.exited.catch(() => {})
137+
}
111138
}
112139
}
113140
if (which("xsel")) {
114141
console.log("clipboard: using xsel")
115142
return async (text: string) => {
143+
const config = await Config.get().catch(() => ({}))
144+
const enablePrimary = (config as Config.Info).clipboard?.linux?.enablePrimaryCopy ?? false
116145
const proc = Process.spawn(["xsel", "--clipboard", "--input"], {
117146
stdin: "pipe",
118147
stdout: "ignore",
@@ -122,6 +151,17 @@ export namespace Clipboard {
122151
proc.stdin.write(text)
123152
proc.stdin.end()
124153
await proc.exited.catch(() => {})
154+
if (enablePrimary) {
155+
const procPrimary = Process.spawn(["xsel", "--primary", "--input"], {
156+
stdin: "pipe",
157+
stdout: "ignore",
158+
stderr: "ignore",
159+
})
160+
if (!procPrimary.stdin) return
161+
procPrimary.stdin.write(text)
162+
procPrimary.stdin.end()
163+
await procPrimary.exited.catch(() => {})
164+
}
125165
}
126166
}
127167
}

packages/opencode/src/config/config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,20 @@ export namespace Config {
985985
export const Info = z
986986
.object({
987987
$schema: z.string().optional().describe("JSON schema reference for configuration validation"),
988+
clipboard: z
989+
.object({
990+
linux: z
991+
.object({
992+
enablePrimaryCopy: z
993+
.boolean()
994+
.optional()
995+
.default(false)
996+
.describe("Copy to primary clipboard in addition to regular clipboard on Linux (Wayland/X11)"),
997+
})
998+
.optional(),
999+
})
1000+
.optional()
1001+
.describe("Clipboard configuration"),
9881002
logLevel: Log.Level.optional().describe("Log level"),
9891003
server: Server.optional().describe("Server configuration for opencode serve and web commands"),
9901004
command: z

0 commit comments

Comments
 (0)