Skip to content

Conversation

@gbasin
Copy link
Owner

@gbasin gbasin commented Feb 4, 2026

Summary

  • Fixes mouse wheel scrolling in pipe-pane terminal mode by intercepting SGR scroll sequences server-side
  • Only affects pipe-pane mode - PTY mode continues to use native tmux mouse handling

Changes

  • Intercept ESC[<64;col;rowM (scroll-up) and ESC[<65;col;rowM (scroll-down) in PipePaneTerminalProxy.write()
  • Enter tmux copy-mode and send scroll commands instead of passing sequences as literal text to shell
  • Added unit tests for scroll handling

Why this approach

The original PR (#42) intercepted scroll events client-side and sent a separate tmux-scroll WebSocket message. This refactor:

  • Keeps scroll logic server-side in the proxy that needs it
  • Avoids extra WebSocket round-trips
  • Leaves PTY mode completely unchanged (no performance impact)

Co-Authored-By: Marty Martin croakingtoad@users.noreply.github.com

🤖 Generated with Claude Code

Marty and others added 6 commits February 3, 2026 21:18
…heel scrolling

When scrolling with the mouse wheel in the browser, the previous implementation
sent SGR mouse sequences (\x1b[<64;..M) via `tmux send-keys -l`, which has two issues:

1. The `-l` flag sends keys literally, which doesn't trigger tmux's WheelUpPane binding
2. Even without `-l`, `send-keys` sends input TO the pane, not to tmux itself

This caused the shell to receive the sequences instead of tmux entering copy-mode,
resulting in command history scrolling instead of scrollback buffer scrolling.

The fix:
- Added new `tmux-scroll` message type to bypass send-keys entirely
- Directly invoke `tmux copy-mode` and `tmux send-keys -X scroll-up/down`
- Works consistently with both desktop mouse wheel and touch scrolling
- Matches native tmux behavior when connected directly

This ensures mouse wheel scrolling in the browser enters tmux copy-mode and
scrolls the scrollback buffer, just like scrolling in native tmux.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
After scrolling, check tmux's pane_in_mode status and report it to the client.
When scrolling down reaches the bottom, tmux automatically exits copy-mode,
and we now detect this and update the UI accordingly.

This eliminates the need to press 'q' to exit copy-mode - just scroll down
to the bottom and it exits automatically, matching native tmux behavior.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Tmux doesn't auto-exit copy-mode when reaching the bottom - it stays in
copy-mode until explicitly exited. Now we check #{scroll_position} after
scrolling down, and if it's 0 (at the bottom), we explicitly send the
cancel command to exit copy-mode.

This provides the expected behavior: scroll down to bottom → automatically
exits copy-mode and returns to live terminal.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Instead of intercepting scroll events client-side and sending a separate
tmux-scroll message, handle SGR mouse scroll sequences directly in
PipePaneTerminalProxy.write().

This approach:
- Only affects pipe-pane mode (where send-keys -l doesn't trigger tmux mouse handling)
- Leaves PTY mode unchanged (native tmux mouse handling works correctly)
- Reduces complexity by keeping scroll logic server-side
- Avoids extra WebSocket message round-trips

The fix intercepts ESC[<64;col;rowM (scroll-up) and ESC[<65;col;rowM (scroll-down)
sequences before they're sent as literal text to the shell, and instead:
1. Enters tmux copy-mode (idempotent)
2. Sends scroll-up or scroll-down via send-keys -X

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Improvements to SGR mouse scroll handling in pipe-pane mode:

- Support modifier keys (Shift/Alt/Ctrl) by masking off modifier bits
  (button & 67) to extract the base wheel button (64=up, 65=down)
- Handle batched scroll sequences in a single write() call
- Only enter copy-mode once per write batch for efficiency
- Properly handle mixed scroll + text input
- Ignore release events (lowercase 'm')

Added tests for:
- Shift+scroll, Ctrl+scroll, Alt+Shift+scroll variants
- Multiple scroll sequences in one write
- Mixed scroll and text input
- Release event handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
After scroll-down events in pipe-pane mode, check #{scroll_position}
and cancel copy-mode when at position 0 (bottom). This prevents getting
stuck in copy-mode from incidental scroll-down input (especially common
with trackpads that generate many down deltas).

Added tests for:
- Copy-mode exit when scroll_position=0
- No exit when scrolled up (position > 0)
- Scroll-up does not trigger position check

Co-Authored-By: Brenner Spear <BrennerSpear@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@gbasin gbasin force-pushed the fix/tmux-scroll-wheel-in-browser branch from 39b4518 to 9c389d5 Compare February 4, 2026 01:18
@gbasin gbasin merged commit 69e8373 into master Feb 4, 2026
2 checks passed
@gbasin gbasin deleted the fix/tmux-scroll-wheel-in-browser branch February 4, 2026 01:19
gbasin added a commit that referenced this pull request Feb 4, 2026
## Summary
- Fixes mouse wheel scrolling in pipe-pane terminal mode by intercepting
SGR scroll sequences server-side
- Only affects pipe-pane mode - PTY mode continues to use native tmux
mouse handling

## Changes
- Intercept `ESC[<64;col;rowM` (scroll-up) and `ESC[<65;col;rowM`
(scroll-down) in `PipePaneTerminalProxy.write()`
- Enter tmux copy-mode and send scroll commands instead of passing
sequences as literal text to shell
- Added unit tests for scroll handling

## Why this approach
The original PR (#42) intercepted scroll events client-side and sent a
separate `tmux-scroll` WebSocket message. This refactor:
- Keeps scroll logic server-side in the proxy that needs it
- Avoids extra WebSocket round-trips
- Leaves PTY mode completely unchanged (no performance impact)

Based on PR #42 by @croakingtoad - thank you for identifying the issue
and initial implementation!

Co-Authored-By: Marty Martin <croakingtoad@users.noreply.github.com>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Marty <marty@example.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
gbasin added a commit that referenced this pull request Feb 4, 2026
## Summary
- Fixes mouse wheel scrolling in pipe-pane terminal mode by intercepting
SGR scroll sequences server-side
- Only affects pipe-pane mode - PTY mode continues to use native tmux
mouse handling

## Changes
- Intercept `ESC[<64;col;rowM` (scroll-up) and `ESC[<65;col;rowM`
(scroll-down) in `PipePaneTerminalProxy.write()`
- Enter tmux copy-mode and send scroll commands instead of passing
sequences as literal text to shell
- Added unit tests for scroll handling

## Why this approach
The original PR (#42) intercepted scroll events client-side and sent a
separate `tmux-scroll` WebSocket message. This refactor:
- Keeps scroll logic server-side in the proxy that needs it
- Avoids extra WebSocket round-trips
- Leaves PTY mode completely unchanged (no performance impact)

Based on PR #42 by @croakingtoad - thank you for identifying the issue
and initial implementation!

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Marty Martin <croakingtoad@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
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