From c2bc7dd721cea676ba979b8f0582679f5115b600 Mon Sep 17 00:00:00 2001 From: maskar Date: Fri, 27 Mar 2026 22:11:25 -0500 Subject: [PATCH 1/2] feat(terminal): paste clipboard images as temp file paths When Cmd+V is pressed and the clipboard contains an image instead of text, save it to a temp PNG file and paste the file path into the terminal. Useful for CLI tools that accept image paths. --- electron/ipc/channels.ts | 3 +++ electron/ipc/register.ts | 14 +++++++++++++- electron/preload.cjs | 2 ++ src/components/TerminalView.tsx | 14 +++++++++++--- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/electron/ipc/channels.ts b/electron/ipc/channels.ts index 82cf105a..a656462d 100644 --- a/electron/ipc/channels.ts +++ b/electron/ipc/channels.ts @@ -102,6 +102,9 @@ export enum IPC { OpenPath = 'open_path', ReadFileText = 'read_file_text', + // Clipboard + SaveClipboardImage = 'save_clipboard_image', + // Notifications ShowNotification = 'show_notification', NotificationClicked = 'notification_clicked', diff --git a/electron/ipc/register.ts b/electron/ipc/register.ts index 30ddd569..067eaa18 100644 --- a/electron/ipc/register.ts +++ b/electron/ipc/register.ts @@ -1,5 +1,6 @@ -import { ipcMain, dialog, shell, app, BrowserWindow, Notification } from 'electron'; +import { ipcMain, dialog, shell, app, clipboard, BrowserWindow, Notification } from 'electron'; import fs from 'fs'; +import os from 'os'; import { fileURLToPath } from 'url'; import { IPC } from './channels.js'; import { @@ -463,6 +464,17 @@ export function registerAllHandlers(win: BrowserWindow): void { return fs.readFileSync(args.filePath, 'utf8'); }); + // --- Clipboard --- + ipcMain.handle(IPC.SaveClipboardImage, () => { + const img = clipboard.readImage(); + if (img.isEmpty()) return null; + const buf = img.toPNG(); + const tmpDir = os.tmpdir(); + const filePath = path.join(tmpDir, `clipboard-${Date.now()}.png`); + fs.writeFileSync(filePath, buf); + return filePath; + }); + // --- System --- ipcMain.handle(IPC.GetSystemFonts, () => getSystemMonospaceFonts()); diff --git a/electron/preload.cjs b/electron/preload.cjs index 7027ddb6..0abbb51f 100644 --- a/electron/preload.cjs +++ b/electron/preload.cjs @@ -93,6 +93,8 @@ const ALLOWED_CHANNELS = new Set([ // File links 'open_path', 'read_file_text', + // Clipboard + 'save_clipboard_image', // Notifications 'show_notification', 'notification_clicked', diff --git a/src/components/TerminalView.tsx b/src/components/TerminalView.tsx index 7252801b..e2ff215b 100644 --- a/src/components/TerminalView.tsx +++ b/src/components/TerminalView.tsx @@ -191,9 +191,17 @@ export function TerminalView(props: TerminalViewProps) { if (isPaste) { e.preventDefault(); - navigator.clipboard.readText().then((text) => { - if (text) enqueueInput(text); - }); + (async () => { + // Try text first (readText throws if clipboard has no text) + const text = await navigator.clipboard.readText().catch(() => ''); + if (text) { + enqueueInput(text); + return; + } + // Fall back to clipboard image → save to temp file and paste path + const filePath = await invoke(IPC.SaveClipboardImage); + if (filePath) enqueueInput(filePath); + })(); return false; } From 404bc5969ed808f22f531ed5fa5d00f8ae4ec84e Mon Sep 17 00:00:00 2001 From: Johannes Millan Date: Sat, 28 Mar 2026 17:27:02 +0100 Subject: [PATCH 2/2] fix(clipboard): use async write, add error handling, prevent temp file leak - Use async fs.promises.writeFile instead of writeFileSync to avoid blocking the main process on large clipboard images - Add try/catch returning null on write failure for graceful degradation - Use a fixed filename to prevent unbounded temp file accumulation - Add .catch() on paste IIFE to prevent unhandled rejections --- electron/ipc/register.ts | 19 +++++++++++-------- src/components/TerminalView.tsx | 3 +-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/electron/ipc/register.ts b/electron/ipc/register.ts index 067eaa18..5e2c6891 100644 --- a/electron/ipc/register.ts +++ b/electron/ipc/register.ts @@ -465,14 +465,17 @@ export function registerAllHandlers(win: BrowserWindow): void { }); // --- Clipboard --- - ipcMain.handle(IPC.SaveClipboardImage, () => { - const img = clipboard.readImage(); - if (img.isEmpty()) return null; - const buf = img.toPNG(); - const tmpDir = os.tmpdir(); - const filePath = path.join(tmpDir, `clipboard-${Date.now()}.png`); - fs.writeFileSync(filePath, buf); - return filePath; + const clipboardImagePath = path.join(os.tmpdir(), 'parallel-code-clipboard.png'); + ipcMain.handle(IPC.SaveClipboardImage, async () => { + try { + const img = clipboard.readImage(); + if (img.isEmpty()) return null; + const buf = img.toPNG(); + await fs.promises.writeFile(clipboardImagePath, buf); + return clipboardImagePath; + } catch { + return null; + } }); // --- System --- diff --git a/src/components/TerminalView.tsx b/src/components/TerminalView.tsx index e2ff215b..6e66fb33 100644 --- a/src/components/TerminalView.tsx +++ b/src/components/TerminalView.tsx @@ -192,7 +192,6 @@ export function TerminalView(props: TerminalViewProps) { if (isPaste) { e.preventDefault(); (async () => { - // Try text first (readText throws if clipboard has no text) const text = await navigator.clipboard.readText().catch(() => ''); if (text) { enqueueInput(text); @@ -201,7 +200,7 @@ export function TerminalView(props: TerminalViewProps) { // Fall back to clipboard image → save to temp file and paste path const filePath = await invoke(IPC.SaveClipboardImage); if (filePath) enqueueInput(filePath); - })(); + })().catch(() => {}); return false; }