From 2e81a45fcc1dc931706da03b726fc99f2168bc74 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Apr 2026 10:04:40 +0200 Subject: [PATCH] fix(term): provide linkHandler so OSC 8 hyperlinks open on Cmd/Ctrl-click MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xterm.js falls back to window.confirm() + window.open() for OSC 8 hyperlinks when no linkHandler is configured. In Electron's renderer, window.open() is blocked by the navigation guards — so the confirmation dialog appears but clicking OK silently fails. Provide a linkHandler that routes OSC 8 clicks through openLink() with the same Cmd/Ctrl-click requirement used by WebLinksAddon for regex- detected URLs. Also wire up hover/leave to feed hoveredLinkUri, which TermLinkTooltip uses to show the "Cmd-click to open link" tooltip. Fixes #3165 --- frontend/app/view/term/termwrap.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/frontend/app/view/term/termwrap.ts b/frontend/app/view/term/termwrap.ts index d10b600459..f796b0a451 100644 --- a/frontend/app/view/term/termwrap.ts +++ b/frontend/app/view/term/termwrap.ts @@ -143,6 +143,36 @@ export class TermWrap { this.lastCommandAtom = jotai.atom(null) as jotai.PrimitiveAtom; this.claudeCodeActiveAtom = jotai.atom(false); this.webglEnabledAtom = jotai.atom(false) as jotai.PrimitiveAtom; + // Custom linkHandler for OSC 8 hyperlinks. Without this, xterm.js falls + // back to window.confirm() + window.open(), which Electron's renderer + // blocks — so the confirmation dialog appears but clicking OK silently + // fails. Route OSC 8 clicks through the same Cmd/Ctrl-click path that + // WebLinksAddon uses for regex-detected URLs. + options.linkHandler = { + activate: (event: MouseEvent, uri: string) => { + event.preventDefault(); + switch (PLATFORM) { + case PlatformMacOS: + if (event.metaKey) { + fireAndForget(() => openLink(uri)); + } + break; + default: + if (event.ctrlKey) { + fireAndForget(() => openLink(uri)); + } + break; + } + }, + hover: (event: MouseEvent, uri: string) => { + this.hoveredLinkUri = uri; + this.onLinkHover?.(uri, event.clientX, event.clientY); + }, + leave: () => { + this.hoveredLinkUri = null; + this.onLinkHover?.(null, 0, 0); + }, + }; this.terminal = new Terminal(options); this.fitAddon = new FitAddon(); this.serializeAddon = new SerializeAddon();