Skip to content

feat(terminal): paste & drop images into terminal#90

Merged
johannesjo merged 1 commit into
johannesjo:mainfrom
ASRagab:feature/support-paste-images
Apr 29, 2026
Merged

feat(terminal): paste & drop images into terminal#90
johannesjo merged 1 commit into
johannesjo:mainfrom
ASRagab:feature/support-paste-images

Conversation

@ASRagab
Copy link
Copy Markdown
Contributor

@ASRagab ASRagab commented Apr 29, 2026

Summary

fixes #59 (comment)

Drag a file (or paste an image-from-Finder copy) into the terminal and the absolute path is typed into the active CLI agent — escaped, then delivered through term.paste() so xterm wraps it in bracketed-paste markers. Agents like Claude Code recognise that as a path paste and turn it into [Image #N] instead of the current "no such file" experience.

Fixes the v1.2.1 report:

When I try to drag/paste an image into a Claude Code session in Parallel Code, it only pastes the file name. I expected the behavior that Claude Code supports.

What changed

  • New IPC ResolveClipboardPaste — main-process picks file URL → image → text in priority order. macOS reads public.file-url; Linux reads x-special/gnome-copied-files (Files / Nemo / Caja) ahead of text/uri-list (KDE / Xfce). Without this, copying an image file in Finder returned only the basename via navigator.clipboard.readText().
  • New IPC SaveDroppedImage — bytes from path-less drops (e.g. <img> dragged from a browser tab) are base64-encoded over IPC and decoded in main with Buffer.from(b64, 'base64'). The base64 detour exists because the renderer's invoke() helper does a JSON.parse(JSON.stringify(args)) round-trip that destroys typed arrays.
  • webUtils.getPathForFile exposed via the existing preload bridge as window.electron.getPathForFile — Electron 32+ replacement for the removed File.path.
  • TerminalView capture-phase dragover / drop listeners so xterm's own bubble-phase handler can't insert the basename first. Multiple files are space-joined.
  • escapePath helper backslash-escapes whitespace and shell metacharacters; output round-trips cleanly through both POSIX shells and agent prompt parsers, matching macOS Terminal / iTerm2 / VS Code drag-insert convention.
  • term.paste() delivery for paste and drop payloads so xterm emits bracketed-paste markers (\x1b[200~ … \x1b[201~) when the agent has bracketed-paste mode on. Without these markers Claude Code reads the path as literal typing and skips the file-attachment step.
  • Robustness: per-file resolution runs in its own try/catch so one unreadable item in a mixed drop doesn't cancel the resolvable siblings; temp filenames append a 6-char crypto.randomBytes(3) suffix so concurrent same-name drops don't overwrite each other.
  • Removed the legacy SaveClipboardImage IPC + handler — fully superseded by ResolveClipboardPaste.
  • OpenSpec: new capability terminal-image-paste (6 requirements, 32 scenarios) at openspec/specs/terminal-image-paste/spec.md. The change record is archived under openspec/changes/archive/2026-04-28-support-paste-images/.
2026-04-28 17 26 09

Scope

macOS and Linux only, per the project's published platforms. Windows clipboard formats (FileNameW) and quoting ("…" for cmd / PowerShell) are explicitly out of scope and called out in the design doc as a future change if Windows is ever added.

Test plan

  • npm run typecheck (renderer + electron tsconfigs) ✅
  • npx vitest run — 322 passed, 0 failed (9 new tests for escapePath + dataTransferToShellArgs)
  • npm run lint (eslint, --max-warnings 0) ✅
  • npx prettier --check
  • openspec validate --all --strict — 9 / 0 ✅
  • Manual: drag a .png from Finder into a claude session — expect [Image #N] placeholder
  • Manual: Cmd+V on a file copied in Finder — same expectation
  • Manual: Cmd+V on a screenshot still in clipboard buffer — temp PNG path inserted
  • Manual: drag an <img> from a browser tab — temp file written, path inserted
  • Manual: drop two files at once — both paths inserted, space-separated, escaped
  • Manual (Linux): copy from Nautilus — absolute path inserted (GNOME format) ✱ unverified by me

🤖 Generated with Claude Code

Drag a file or paste an image-from-Finder into the terminal and the
absolute path is typed (escaped, then wrapped via term.paste so xterm
emits bracketed-paste markers). CLI agents like Claude Code see the
path as a paste rather than literal typing, which triggers their
file-attachment recognition and turns "/Users/.../image.png" into
[Image #N] instead of a "no such file" error.

Implementation:
- new IPC ResolveClipboardPaste — main-process picks file URL → image
  → text in priority order, so Finder-copied image files pass an
  absolute path instead of the bare basename
- new IPC SaveDroppedImage — base64 round-trip (renderer's invoke()
  wrapper destroys typed arrays via JSON.parse(JSON.stringify())) so
  browser-origin <img> drops still produce a usable temp path
- webUtils.getPathForFile exposed via preload as
  window.electron.getPathForFile (Electron 32+ replacement for
  File.path)
- TerminalView capture-phase dragover/drop listeners so xterm's own
  bubble-phase handler doesn't insert the basename first
- escapePath helper backslash-escapes whitespace + shell metacharacters
  so paths round-trip cleanly through both POSIX shells and agent
  prompt parsers
- legacy SaveClipboardImage handler removed

Linux clipboard support reads x-special/gnome-copied-files (Files,
Nemo, Caja) ahead of text/uri-list (KDE, Xfce). Per-file resolution
runs in its own try/catch so one unreadable item in a mixed drop
doesn't cancel the resolvable siblings. Temp filenames append a 6-char
random suffix so concurrent same-name drops don't collide.

Spec captured under openspec capability terminal-image-paste (6
requirements, 32 scenarios) plus an archived change record at
openspec/changes/archive/2026-04-28-support-paste-images/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@johannesjo
Copy link
Copy Markdown
Owner

Very cool! Thank you very much!

@johannesjo johannesjo merged commit 944687f into johannesjo:main Apr 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Claude Code image copying and pasting

2 participants