Skip to content

fix(ssh): let typing leave tmux copy-mode after scrolling up - #355

Merged
kipavy merged 1 commit into
devfrom
fix/tmux-copy-mode-typing-344
Sep 24, 2026
Merged

kipavy merged 1 commit into
devfrom
fix/tmux-copy-mode-typing-344

Conversation

@kipavy

@kipavy kipavy commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #344.

Cause

Persistent SSH sessions run inside our tmux wrapper with mouse on. Wheel-up puts tmux into copy-mode, and tmux's default (emacs) copy-mode table has no Enter binding, so keystrokes were silently dropped until q/Escape. The [25/47880] in the report's screenshot is tmux's copy-mode position indicator.

Change

  • persistent_copy_mode_keys_command (shell_integration.rs): waits for the session, then binds keys in copy-mode and copy-mode-vi to send -X cancel \; send-keys <key>.
    • tmux 3.4+: all printable keys plus Enter/BSpace/Tab/Space.
    • tmux 2.6–3.3: Enter only. These versions run a paste's first key through bindings, which drops the bracketed-paste start and would make a multi-line paste execute line by line.
    • q, Escape, C-c and navigation keys are untouched.
  • client.rs runs it once per persistent connection on its own exec channel. The create payload is byte-identical: it is capped at 8192 bytes, and going over silently falls back to a non-persistent shell. Adding the bindings there cost ~920 bytes.
  • The three exec-and-read copies in connect() now share exec_collect.

Verification

Container matrix driving the real payload through an outer tmux pane (raw client input, incl. SGR mouse and bracketed paste), old vs new, on tmux 2.6, 2.7, 3.0a, 3.1c, 3.2a, 3.3a, 3.4, 3.7c:

  • 3.4+: typing, punctuation, BSpace and the vi table leave copy-mode and reach the shell.
  • 2.6–3.3: Enter exits copy-mode and runs.
  • Identical before/after on every version: q/Escape/C-c, arrows/PageUp, wheel back to bottom, click, drag-copy, motion, right-click, non-ASCII input, and bracketed paste into bash (never executes).
  • Servers started by the old wrapper pick up the bindings on the next connection. With no session, the command exits after 15 s without starting a server.

cargo fmt, clippy -D warnings and the shell_integration tests pass.

Live-tested in the headless app (debug build of this branch) against real sshd hosts, driven with X mouse/keyboard events:

  • tmux 3.7c: the app's side channel pushed 97 bindings per table on connect. Wheel-up showed the [20/364] indicator, and typing echo live-344-ok + Enter exited copy-mode and ran it. q/Escape/C-c exit with nothing typed; Up and a click stay in copy-mode; wheel-down exits; a multi-line paste via the app's paste path is swallowed and never runs.
  • tmux 3.2a (Ubuntu 22.04): only Enter is bound, and bare Enter exits copy-mode. Typed letters behave identically with and without the change, confirmed by replaying the same input with the binding removed.

Scrolling the wheel up in a persistent session puts our tmux wrapper
into copy-mode, whose default key table has no Enter binding, so every
keystroke was swallowed until q/Escape (#344).

A one-shot exec per persistent connection now binds keys in both copy
mode tables to cancel and forward themselves. It runs on its own channel
so the size-capped create payload is untouched, waits for the session so
a cold server is covered, and re-applies to servers started before this
change. tmux 3.4+ gets every printable key; 2.6-3.3 get Enter only,
because those versions run a paste's first key through bindings and
would drop the bracketed-paste start. q, Escape and C-c keep exiting
without forwarding.

Also folds the three exec-and-read copies in connect() into
exec_collect.
@kipavy
kipavy merged commit 74201ac into dev Sep 24, 2026
4 checks passed
@kipavy
kipavy deleted the fix/tmux-copy-mode-typing-344 branch September 24, 2026 13:22
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.

1 participant