Problem / Background
After a bssh PTY session disconnects (whether normally, via Ctrl+C, network failure, or panic), moving the mouse on the local terminal causes raw escape sequences to be printed to the screen, e.g.:
0;59;35M32;58;35M32;57;35M32;56;35M ... 35;38;36M35;38;37M ...
These are X11 mouse tracking events in SGR mode (1006). Format: ESC[<button;x;yM (press/move) and ESC[<button;x;ym (release).
Root cause
When a remote interactive program (vim, tmux, htop, less, etc.) runs inside the PTY, it enables mouse tracking on the local terminal by sending sequences like ESC[?1000h, ESC[?1002h, ESC[?1003h, ESC[?1006h. These sequences travel through the SSH channel and are interpreted by the local terminal emulator.
When the SSH session terminates abruptly, the corresponding disable sequences (ESC[?1000l, ESC[?1002l, ESC[?1003l, ESC[?1006l) are never delivered to the local terminal. The local terminal therefore remains in mouse tracking mode, and any subsequent mouse motion is emitted as raw input — which appears as garbled text since no application is reading it.
Code analysis
In src/pty/terminal.rs:
TerminalStateGuard::restore_terminal_state() (line 159) — only restores raw mode and bracketed paste; does not disable mouse tracking.
force_terminal_cleanup() (line 198) — only disables raw mode.
TerminalOps::disable_mouse() (line 222) — exists but is not called from any cleanup path.
Thus bssh never sends the mouse-tracking-off sequences on exit, even though it has the helper.
Proposed Solution
-
Extend TerminalStateGuard::restore_terminal_state() in src/pty/terminal.rs to disable all common mouse tracking modes. Use crossterm's DisableMouseCapture and additionally write raw sequences to cover modes that crossterm may not reset (1002, 1003, 1006, 1015). Wrap each call so a failure doesn't abort the rest of the cleanup.
use std::io::Write;
use crossterm::event::DisableMouseCapture;
// Best-effort: reset every mouse tracking mode the remote app may have enabled
let _ = execute!(std::io::stdout(), DisableMouseCapture);
let _ = std::io::stdout()
.write_all(b"\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[?1015l");
let _ = std::io::stdout().flush();
-
Update force_terminal_cleanup() to perform the same mouse-tracking reset, since this function is the global last-resort cleanup.
-
Verify panic & signal handling paths call force_terminal_cleanup() reliably:
- Confirm a
std::panic::set_hook is installed that invokes force_terminal_cleanup() before the default panic handler runs.
- Confirm SIGINT/SIGTERM handlers (e.g.,
tokio::signal::ctrl_c) trigger the cleanup before exiting. If missing, add them.
-
(Optional / nice-to-have) Also reset alternate screen buffer (ESC[?1049l) and cursor visibility (ESC[?25h) in the same cleanup path, since interactive programs also leave those toggled.
Acceptance Criteria
Reproduction Steps
bssh -H user@host (or any cluster) into a PTY-enabled session.
- Run
vim, then :set mouse=a, then :q.
- Disconnect the SSH session abruptly (e.g., kill the remote process or
~. escape).
- Move the mouse over the local terminal — observe garbled
0;X;YM strings being printed.
Technical Considerations
- File to modify:
src/pty/terminal.rs
- Related helpers:
TerminalOps::disable_mouse() (already exists, currently unused in cleanup)
- Mouse tracking modes: 1000 (X11), 1002 (button event), 1003 (any event), 1006 (SGR), 1015 (urxvt)
- Cleanup must be best-effort — each escape sequence write should be independently fallible so one failure doesn't prevent subsequent resets.
Problem / Background
After a
bsshPTY session disconnects (whether normally, via Ctrl+C, network failure, or panic), moving the mouse on the local terminal causes raw escape sequences to be printed to the screen, e.g.:These are X11 mouse tracking events in SGR mode (1006). Format:
ESC[<button;x;yM(press/move) andESC[<button;x;ym(release).Root cause
When a remote interactive program (vim, tmux, htop, less, etc.) runs inside the PTY, it enables mouse tracking on the local terminal by sending sequences like
ESC[?1000h,ESC[?1002h,ESC[?1003h,ESC[?1006h. These sequences travel through the SSH channel and are interpreted by the local terminal emulator.When the SSH session terminates abruptly, the corresponding disable sequences (
ESC[?1000l,ESC[?1002l,ESC[?1003l,ESC[?1006l) are never delivered to the local terminal. The local terminal therefore remains in mouse tracking mode, and any subsequent mouse motion is emitted as raw input — which appears as garbled text since no application is reading it.Code analysis
In
src/pty/terminal.rs:TerminalStateGuard::restore_terminal_state()(line 159) — only restores raw mode and bracketed paste; does not disable mouse tracking.force_terminal_cleanup()(line 198) — only disables raw mode.TerminalOps::disable_mouse()(line 222) — exists but is not called from any cleanup path.Thus bssh never sends the mouse-tracking-off sequences on exit, even though it has the helper.
Proposed Solution
Extend
TerminalStateGuard::restore_terminal_state()insrc/pty/terminal.rsto disable all common mouse tracking modes. Use crossterm'sDisableMouseCaptureand additionally write raw sequences to cover modes that crossterm may not reset (1002, 1003, 1006, 1015). Wrap each call so a failure doesn't abort the rest of the cleanup.Update
force_terminal_cleanup()to perform the same mouse-tracking reset, since this function is the global last-resort cleanup.Verify panic & signal handling paths call
force_terminal_cleanup()reliably:std::panic::set_hookis installed that invokesforce_terminal_cleanup()before the default panic handler runs.tokio::signal::ctrl_c) trigger the cleanup before exiting. If missing, add them.(Optional / nice-to-have) Also reset alternate screen buffer (
ESC[?1049l) and cursor visibility (ESC[?25h) in the same cleanup path, since interactive programs also leave those toggled.Acceptance Criteria
bsshexits a PTY session (normal exit, Ctrl+C, network drop, panic), the local terminal no longer prints mouse tracking escape sequences when the mouse is moved.vimwith:set mouse=a, ortmuxwith mouse mode on, orhtop).-9the remote sshd, network unplug, Ctrl+C on local).bssh exec).force_terminal_cleanup()is reachable from panic and signal paths (document or test).Reproduction Steps
bssh -H user@host(or any cluster) into a PTY-enabled session.vim, then:set mouse=a, then:q.~.escape).0;X;YMstrings being printed.Technical Considerations
src/pty/terminal.rsTerminalOps::disable_mouse()(already exists, currently unused in cleanup)