Skip to content

Commit 72c4845

Browse files
NagyViktclaude
andcommitted
v1.21: image paste through menu via tmux send-keys C-v (bypass modal grab)
v1.20 fixed text paste from the menu by writing clipboard bytes into the pane via 'tmux load-buffer + paste-buffer'. v1.21 does the same thing structurally for images: the right-click menu Paste path now delivers the Ctrl-V *byte* (\\026) to the pane's pty via 'tmux send-keys -t <pane> C-v' instead of synthesizing the keystroke at OS level with ydotool. Why: ydotool key ctrl+v happens at the kernel input layer; tmux's display-menu has a modal keyboard grab while open, so the menu eats the synthesized keystroke before any host terminal (GNOME Terminal, kitty, etc.) sees it. tmux send-keys writes the byte directly into the target pane's pty *after* tmux's keytable layer — no menu interception, no recursion against tmux's own bind -n C-v. Inside the TUI (Claude Code, Codex, etc.) the \\026 byte still triggers the same image-paste handler, which reads the clipboard via wl-paste and gets the staged image via flashpaste's shim/xclip fallback chain. Two call sites patched in bin/tmux-paste-dispatch.sh: - Early FAST PATH non-kitty branch (~line 287) - Late image-paste branch fallback (~line 577) Kitty path unchanged — kitty @ send-text is already pty-direct and works fine. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 61604a3 commit 72c4845

2 files changed

Lines changed: 63 additions & 28 deletions

File tree

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,3 +680,33 @@ A non-exhaustive list of dead-ends so future contributors don't waste a week:
680680
MIT — see [LICENSE](LICENSE).
681681

682682
Wrenched into shape across a single multi-hour session of Wayland clipboard pain on a real GNOME 46 / kitty / tmux / Claude Code setup. The session log, including every dead-end and every fix, lives in the commit history.
683+
684+
---
685+
686+
<!-- Schema.org SoftwareApplication metadata for AI crawlers and search engines. -->
687+
688+
```json
689+
{
690+
"@context": "https://schema.org",
691+
"@type": "SoftwareApplication",
692+
"name": "FlashPaste",
693+
"description": "Sub-15ms image-paste glue for terminal AI agents (Claude Code, Codex CLI, Aider) on GNOME Wayland. Works around mutter's surfaceless-client clipboard refusal, kitty's map ctrl+v interception, and tmux's bind -n C-v recursion via three progressive performance tiers.",
694+
"url": "https://github.com/NagyVikt/flashpaste",
695+
"codeRepository": "https://github.com/NagyVikt/flashpaste",
696+
"applicationCategory": "DeveloperApplication",
697+
"applicationSubCategory": "Clipboard / Terminal Utility",
698+
"operatingSystem": "Linux (GNOME Wayland — Ubuntu 24.04, Debian 13, Fedora 40+, Pop!_OS 24.04+)",
699+
"license": "https://spdx.org/licenses/MIT.html",
700+
"programmingLanguage": ["Rust", "Bash"],
701+
"softwareRequirements": ["kitty", "tmux", "wl-clipboard", "xclip", "ydotool"],
702+
"author": {
703+
"@type": "Person",
704+
"name": "Viktor Nagy",
705+
"url": "https://github.com/NagyVikt"
706+
},
707+
"keywords": [
708+
"clipboard", "wayland", "gnome", "mutter", "kitty", "tmux",
709+
"claude-code", "codex-cli", "aider", "image-paste", "terminal-ai", "llm-agent"
710+
]
711+
}
712+
```

bin/tmux-paste-dispatch.sh

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -285,21 +285,26 @@ if [ "$_early_loaded" = "1" ]; then
285285
fi
286286
;;
287287
*)
288-
# GNOME Terminal / VTE — ydotool ctrl+v
289-
if command -v ydotool >/dev/null 2>&1; then
290-
export YDOTOOL_SOCKET="${YDOTOOL_SOCKET:-$XDG_RUNTIME_DIR/.ydotool_socket}"
291-
if [ -S "$YDOTOOL_SOCKET" ]; then
292-
tmux unbind -n C-v 2>>"$LOG"
293-
ydotool key ctrl+v 2>>"$LOG"
294-
clog "paste-dispatch" "event=fast-path-done" "transport=ydotool" "mime='$_early_mime'"
295-
log "FAST PATH: ydotool ctrl+v done"
296-
setsid -f sh -c '
297-
sleep 0.1
298-
tmux bind -n C-v run-shell -b "TMUX_PASTE_TRIGGER=ctrl-v /home/deadpool/.local/bin/tmux-paste-dispatch.sh '\''#{pane_id}'\''"
299-
' </dev/null >/dev/null 2>&1
300-
exit 0
301-
fi
302-
fi
288+
# GNOME Terminal / VTE — IMAGE PASTE FAST PATH.
289+
# v1.21: tmux send-keys writes the raw Ctrl-V byte (\026) directly
290+
# into the pane's pty. The TUI app (Claude Code) reads it from
291+
# stdin and triggers its image-paste handler, which then calls
292+
# wl-paste -t image/png (our shim falls back to xclip). This
293+
# bypasses two failure modes ydotool hit on this stack:
294+
# 1. The right-click `display-menu` is modal — it captures every
295+
# keystroke until dismissed, so an OS-level ydotool ctrl+v
296+
# gets eaten before the host terminal can see it. (Bug
297+
# reproduced repeatedly when user reported "menu Paste does
298+
# nothing".)
299+
# 2. tmux's `bind -n C-v` would recurse on a ydotool keystroke,
300+
# requiring unbind/rebind theatrics. send-keys writes to the
301+
# pty AFTER the keytable layer, so no recursion to guard.
302+
log "image FAST PATH (GNOME Terminal): tmux send-keys C-v -> $pane"
303+
tmux send-keys -t "$pane" C-v 2>>"$LOG"
304+
rc=$?
305+
log "tmux send-keys C-v exit=$rc"
306+
clog "paste-dispatch" "event=fast-path-done" "transport=tmux-send-keys-cv" "mime='$_early_mime'"
307+
exit 0
303308
;;
304309
esac
305310
fi
@@ -569,19 +574,19 @@ if [ "$has_image" -eq 1 ]; then
569574
log "image-paste branch (kitty): no socket → falling through to ydotool"
570575
;;
571576
esac
572-
# Non-kitty host: ydotool 0.1.8 ctrl+v. The kitty Ctrl+V keybind
573-
# interception doesn't apply outside kitty.
574-
if command -v ydotool >/dev/null 2>&1; then
575-
export YDOTOOL_SOCKET="${YDOTOOL_SOCKET:-$XDG_RUNTIME_DIR/.ydotool_socket}"
576-
log "image-paste branch (ydotool): socket='$YDOTOOL_SOCKET'"
577-
if [ -S "$YDOTOOL_SOCKET" ]; then
578-
ydotool key ctrl+v 2>>"$LOG"
579-
rc=$?
580-
log "ydotool key ctrl+v exit=$rc"
581-
clog "paste-dispatch" "event=image-ctrlv-sent" "transport=ydotool" "rc=$rc"
582-
exit 0
583-
fi
584-
fi
577+
# Non-kitty host: v1.21 — write a raw Ctrl-V byte into the pane's pty
578+
# via tmux send-keys instead of synthesizing the keystroke at OS level
579+
# with ydotool. ydotool gets eaten by tmux's right-click `display-menu`
580+
# modal grab; tmux send-keys writes to the pty AFTER the keytable
581+
# layer, so the byte reaches the TUI directly without intermediaries.
582+
# Claude Code (and other TUIs) trigger their image-paste handler on
583+
# the \026 byte and read the clipboard via wl-paste (our shim).
584+
log "image-paste branch (tmux send-keys): pane=$pane"
585+
tmux send-keys -t "$pane" C-v 2>>"$LOG"
586+
rc=$?
587+
log "tmux send-keys C-v exit=$rc"
588+
clog "paste-dispatch" "event=image-ctrlv-sent" "transport=tmux-send-keys-cv" "rc=$rc"
589+
exit 0
585590
fi
586591

587592
case "$client_term" in

0 commit comments

Comments
 (0)