From d769694ba41a7b0165cc6065c242145b1ca2fb50 Mon Sep 17 00:00:00 2001 From: gitmpr <89863774+gitmpr@users.noreply.github.com> Date: Sat, 9 Aug 2025 01:33:23 +0200 Subject: [PATCH] Fix WebSocket utilities and Add OSC 52 clipboard support - Fix WebSocket utilities timing issue: * Replace async import() with synchronous require() for Node.js 'ws' module * Prevents race condition where newWebSocket was called before import resolved * Fixes fallback to browser WebSocket in Node.js environment - Add handleOsc52Command() for set/query/clear clipboard operations - Register OSC 52 handler in TermWrap constructor - Support base64 encoding/decoding for clipboard data - Add error handling for clipboard access failures --- frontend/app/view/term/termwrap.ts | 52 ++++++++++++++++++++++++++++++ frontend/util/wsutil.ts | 14 ++++---- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/frontend/app/view/term/termwrap.ts b/frontend/app/view/term/termwrap.ts index 19e632a548..1c524bb720 100644 --- a/frontend/app/view/term/termwrap.ts +++ b/frontend/app/view/term/termwrap.ts @@ -134,6 +134,55 @@ function handleOsc7Command(data: string, blockId: string, loaded: boolean): bool return true; } +function handleOsc52Command(data: string, terminal: Terminal, loaded: boolean): boolean { + if (!loaded) { + return false; + } + if (!data || data.length === 0) { + console.log("Invalid OSC 52 command received (empty)"); + return false; + } + + // OSC 52 format: "c;{base64_data}" or "c;?" (query) or "c" (clear) + const parts = data.split(";", 2); + if (parts.length < 1 || parts[0] !== "c") { + console.log("Invalid OSC 52 command received (unsupported clipboard)", data); + return false; + } + + if (parts.length === 1 || parts[1] === "") { + // Clear clipboard - not implemented + return true; + } + + if (parts[1] === "?") { + // Query clipboard - read from clipboard and send back + navigator.clipboard.readText().then((text) => { + const base64Data = btoa(unescape(encodeURIComponent(text))); + // Send OSC 52 response back to terminal + terminal.write(`\x1b]52;c;${base64Data}\x07`); + }).catch((e) => { + console.log("Failed to read clipboard:", e); + }); + return true; + } + + // Set clipboard data + try { + const decodedData = decodeURIComponent(escape(atob(parts[1]))); + navigator.clipboard.writeText(decodedData).then(() => { + // Successfully wrote to clipboard + }).catch((e) => { + console.log("Failed to write to clipboard:", e); + }); + } catch (e) { + console.log("Failed to decode OSC 52 data:", e); + return false; + } + + return true; +} + export class TermWrap { blockId: string; ptyOffset: number; @@ -211,6 +260,9 @@ export class TermWrap { this.terminal.parser.registerOscHandler(7, (data: string) => { return handleOsc7Command(data, this.blockId, this.loaded); }); + this.terminal.parser.registerOscHandler(52, (data: string) => { + return handleOsc52Command(data, this.terminal, this.loaded); + }); this.terminal.attachCustomKeyEventHandler(waveOptions.keydownHandler); this.connectElem = connectElem; this.mainFileSubject = null; diff --git a/frontend/util/wsutil.ts b/frontend/util/wsutil.ts index 0e3baaf7d8..40de1adddf 100644 --- a/frontend/util/wsutil.ts +++ b/frontend/util/wsutil.ts @@ -6,12 +6,14 @@ import type { WebSocket as NodeWebSocketType } from "ws"; let NodeWebSocket: typeof NodeWebSocketType = null; if (typeof window === "undefined") { - // Necessary to avoid issues with Rollup: https://github.com/websockets/ws/issues/2057 - import("ws") - .then((ws) => (NodeWebSocket = ws.default)) - .catch((e) => { - console.log("Error importing 'ws':", e); - }); + // Synchronous require in Node.js (Electron main process) + // The async import was causing timing issues where newWebSocket was called + // before the dynamic import resolved, falling back to browser WebSocket which doesn't exist in Node.js + try { + NodeWebSocket = require("ws"); + } catch (e) { + console.log("Error importing 'ws':", e); + } } type ComboWebSocket = NodeWebSocketType | WebSocket;