Skip to content

Add MOSH_GRAB_MOUSE so the terminal stops faking cursor keys on scroll - #1407

Open
18woldemar wants to merge 1 commit into
mobile-shell:masterfrom
18woldemar:grab-mouse
Open

Add MOSH_GRAB_MOUSE so the terminal stops faking cursor keys on scroll#1407
18woldemar wants to merge 1 commit into
mobile-shell:masterfrom
18woldemar:grab-mouse

Conversation

@18woldemar

Copy link
Copy Markdown

Problem

While the alternate screen is in use, terminals translate the mouse wheel into
Up/Down cursor keys ("alternate scroll"). mosh-client cannot tell those apart
from the user actually pressing an arrow key, so scrolling types into the
remote application: on a phone a stray swipe walks the shell's history or edits
the line being entered. Over plain ssh the same terminals behave differently,
which makes this look like a mosh bug to users.

Approach

A terminal stops substituting cursor keys once something asks it for mouse
reporting. With MOSH_GRAB_MOUSE set, mosh-client requests DECSET 1000+1006 on
its own behalf, and the wheel then arrives as an SGR report rather than as
keystrokes. Reports that arrive while the client holds the mouse are dropped,
so the wheel does nothing at all instead of typing.

The grab yields to the remote application: whenever it requests mouse reporting
itself, the client releases the mouse and passes the wheel through untouched.
That keeps tmux scrolling its own history, and it also lets a full-screen
application that understands the wheel finally receive it — until now the
terminal had already converted it to cursor keys before mosh saw anything.

The mode is reasserted whenever new_frame() redraws from scratch, since that
emits the mouse-mode resets. That covers both an explicit repaint and a resize.

Why opt-in

A terminal reporting the mouse will not do its own text selection, which is not
a trade every user wants. Without the variable set, behaviour is byte-for-byte
unchanged and no extra escape sequences are emitted.

Testing

New unit test src/tests/wheel-filter covers the report parser, including
reports split across two read() calls, clicks and drags passing through
untouched, malformed sequences being left alone, and a bare ESC never being
held back (which would otherwise delay the user's own Escape key).

make check passes: 31 PASS, 2 expected XFAIL, 0 FAIL. Built with
--enable-compile-warnings=error.

One caveat: I could only run clang-format 22 locally, not the version 14 that
CI pins. It reported no differences against the in-tree .clang-format, but
I could not verify against 14 itself.

Terminals substitute Up and Down cursor keys for the wheel while the
alternate screen is in use ("alternate scroll"), and mosh-client cannot
tell those apart from real keystrokes.  Scrolling therefore types into the
remote application: on a phone a stray swipe walks the shell's history or
edits the line being entered.

Asking the terminal for mouse reporting suppresses the substitution, so
when MOSH_GRAB_MOUSE is set the client holds DECSET 1000+1006 itself and
the wheel arrives as an SGR report instead of as keystrokes.  Every report
that arrives while we hold the mouse is dropped -- clicks and drags as much
as the wheel, since no remote application asked for the mouse, and a click
delivered to one that did not enable mouse reporting is the same garbage on
the command line that the cursor keys were.

The grab is deliberately polite: whenever the remote application requests
mouse reporting of its own, the client stops holding the mouse and passes
the events through.  That is what lets tmux scroll its history, and what
lets a full-screen application that understands the wheel finally receive
it -- until now the terminal had already turned it into cursor keys before
mosh ever saw it.  Handing the mouse back also releases the SGR encoding,
which new_frame() cannot do for us: it emits an encoding change only when
the server's own encoding changed, and knows nothing of the 1006 we set.
The mode is reasserted after a repaint or a resize, since new_frame() emits
the reset sequences when it redraws from scratch.

A report split across the three-byte "\033[<" prefix still reaches the
application: holding a trailing ESC back would delay the user's own Escape
key until the next keystroke, which is the worse trade.  With
MOSH_GRAB_MOUSE unset nothing changes, down to process_user_input()
iterating the read buffer with no copy.

Off by default: a terminal reporting the mouse will not do its own text
selection, which is not a trade every user wants to make.
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