fix(terminal): smooth copy/paste under tmux via OSC 52 + bracketed paste #75

Closed
ashraf wants to merge 6 commits from development_terminal_clipboard_tmux into development
Member

Summary

Makes browser ⇄ remote-PTY copy/paste work reliably under tmux without lag or quirks.

  • Adds an OSC 52 handler in xterm.js so tmux yanks (set -g set-clipboard on) land in the local browser clipboard.
  • Routes paste through term.paste(text) so xterm.js wraps it in bracketed-paste markers — multi-line pastes are now treated as paste (not typing) by tmux, vim, and bracketed-paste-aware shells.
  • Adds an execCommand-based fallback for the async Clipboard API when running in an insecure context or older browser.
  • Documents the recommended tmux config and the Shift+drag selection-bypass tip.

No new dependencies; server-side WS framing unchanged.

Closes #69

Changes

  • crates/hero_router/static/js/terminal.js
    • New helpers copyToClipboard / readFromClipboard with <textarea> + execCommand fallback.
    • New per-pane term.parser.registerOscHandler(52, ...) for tmux yanks → local clipboard. OSC 52 queries deliberately ignored (security). Decoded payloads capped at ~1 MB.
    • Paste now goes through term.paste(text) (honours bracketed-paste); both Ctrl+Shift+V and right-click → Paste use it.
    • Both copy paths (Ctrl+Shift+C and right-click → Copy) now share copyToClipboard.
    • Added rightClickSelectsWord: true on the Terminal constructor.
  • crates/hero_router/templates/terminal.html
    • Shortcuts modal: updated copy/paste row description, new Shift+drag row.
  • crates/hero_router/src/server/terminal.rs
    • Two #[cfg(test)] regression tests pinning that strip_dsr_cpr_queries does not consume OSC 52 frames. No production-code change.
  • README.md
    • New “Clipboard & tmux (admin terminal)” section: required tmux config, key bindings, Shift-drag tip.

Test Results

cargo test -p hero_router — 81 passed, 0 failed, 0 ignored. Includes both new regression tests:

  • server::terminal::tests::strip_dsr_cpr_queries_leaves_osc52_intact
  • server::terminal::tests::strip_dsr_cpr_queries_strips_only_dsr

Manual verification

  • tmux yank with set -g set-clipboard on lands in local clipboard.
  • Ctrl+Shift+V and right-click → Paste both work; multi-line paste does not auto-execute under bracketed-paste shells.
  • Plain Ctrl+C still sends SIGINT when there is no selection.
  • Shift+drag selects natively even with set -g mouse on.
## Summary Makes browser ⇄ remote-PTY copy/paste work reliably under tmux without lag or quirks. - Adds an OSC 52 handler in xterm.js so tmux yanks (`set -g set-clipboard on`) land in the local browser clipboard. - Routes paste through `term.paste(text)` so xterm.js wraps it in bracketed-paste markers — multi-line pastes are now treated as paste (not typing) by tmux, vim, and bracketed-paste-aware shells. - Adds an `execCommand`-based fallback for the async Clipboard API when running in an insecure context or older browser. - Documents the recommended tmux config and the `Shift`+drag selection-bypass tip. No new dependencies; server-side WS framing unchanged. ## Related Issue Closes https://forge.ourworld.tf/lhumina_code/hero_router/issues/69 ## Changes - `crates/hero_router/static/js/terminal.js` - New helpers `copyToClipboard` / `readFromClipboard` with `<textarea>` + `execCommand` fallback. - New per-pane `term.parser.registerOscHandler(52, ...)` for tmux yanks → local clipboard. OSC 52 *queries* deliberately ignored (security). Decoded payloads capped at ~1 MB. - Paste now goes through `term.paste(text)` (honours bracketed-paste); both Ctrl+Shift+V and right-click → Paste use it. - Both copy paths (Ctrl+Shift+C and right-click → Copy) now share `copyToClipboard`. - Added `rightClickSelectsWord: true` on the `Terminal` constructor. - `crates/hero_router/templates/terminal.html` - Shortcuts modal: updated copy/paste row description, new `Shift`+drag row. - `crates/hero_router/src/server/terminal.rs` - Two `#[cfg(test)]` regression tests pinning that `strip_dsr_cpr_queries` does not consume OSC 52 frames. No production-code change. - `README.md` - New “Clipboard & tmux (admin terminal)” section: required tmux config, key bindings, Shift-drag tip. ## Test Results `cargo test -p hero_router` — 81 passed, 0 failed, 0 ignored. Includes both new regression tests: - `server::terminal::tests::strip_dsr_cpr_queries_leaves_osc52_intact` - `server::terminal::tests::strip_dsr_cpr_queries_strips_only_dsr` ## Manual verification - [ ] tmux yank with `set -g set-clipboard on` lands in local clipboard. - [ ] `Ctrl+Shift+V` and right-click → Paste both work; multi-line paste does not auto-execute under bracketed-paste shells. - [ ] Plain `Ctrl+C` still sends SIGINT when there is no selection. - [ ] `Shift`+drag selects natively even with `set -g mouse on`.
fix(terminal): smooth copy/paste under tmux via OSC 52 + bracketed paste
All checks were successful
Build & Test / check (pull_request) Successful in 2m26s
c5d8af44c9
#69
fix(terminal): surface clipboard failures + improve execCommand fallback
Some checks failed
Build & Test / check (pull_request) Has been cancelled
8f8e7934ef
- copyWithFeedback() shows a toast + console.error on copy failure so silent
  failures (insecure context, denied permission, fallback flaky) become visible
- showClipboardToast() — small bottom-of-page toast for non-blocking feedback
- execCopyFallback now positions the textarea in-viewport (1×1, opacity 0)
  with focus management so Chrome and Safari accept the copy gesture
- Right-click Copy now warns when there is no selection
- Paste failures (Ctrl+Shift+V and right-click) also toast and console.warn
- OSC 52 path stays silent on UI but logs a console.warn for debuggability

#69
fix(terminal): copy falls back to window.getSelection under tmux mouse mode
All checks were successful
Build & Test / check (pull_request) Successful in 1m32s
3ed22ca289
Under `set -g mouse on`, tmux captures mouse events so xterm.js never sees
the drag and term.getSelection() returns empty — even though Shift+drag
shows a visible browser-native selection over the canvas. Copy was therefore
toasting "Nothing selected" exactly when the user could see text highlighted.

getActiveSelection() prefers term.getSelection() when present, otherwise
falls back to window.getSelection().toString(). Both Copy paths use it.

#69
fix(terminal): enable tmux set-clipboard on by default for new sessions
All checks were successful
Build & Test / check (pull_request) Successful in 1m41s
f1c78bfc88
New tmux sessions launched from hero_router now run
`tmux set-option -g set-clipboard on ; new-session`, so OSC 52 yanks
reach the browser clipboard without requiring per-user ~/.tmux.conf changes.

Each whitespace-separated token becomes its own argv entry under
interpreter("exec"); tmux parses a lone ";" as a command separator.

#69
fix(terminal): force tmux mouse-drag yank to OSC 52, ignore user config
All checks were successful
Build & Test / check (pull_request) Successful in 1m48s
50b4d51bee
Many users' ~/.tmux.conf binds MouseDragEnd1Pane to copy-pipe with an
external program like pbcopy. On Linux that silently fails and OSC 52 is
never emitted, so the browser clipboard never receives anything.

Hero_router-launched tmux sessions now override that binding to
copy-pipe-no-clear (no external program), so set-clipboard on can do its
job and yanks reach navigator.clipboard via the OSC 52 handler.

Also enables `mouse on` globally, in case the user's config doesn't.

#69
fix(terminal): vi-mode + bind y/Enter/MouseDrag to OSC 52 in launched tmux
All checks were successful
Build & Test / check (pull_request) Successful in 3m19s
8844b16ec4
Default tmux uses emacs-mode where `y` is unbound in copy-mode, so users
selecting text and pressing `y` get nothing — no yank, no OSC 52, no
clipboard. Switch hero_router-spawned tmux to vi-mode and wire `y`, `Enter`,
and MouseDragEnd1Pane all to copy-pipe-no-clear so set-clipboard's OSC 52
emission is the sole copy path.

#69
despiegk closed this pull request 2026-05-08 05:04:24 +00:00
All checks were successful
Build & Test / check (pull_request) Successful in 3m19s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_router!75
No description provided.