TermBuf::Tty
Stability: internal
The terminal device itself: the modes it is in, the screen it is showing, and how big it is.
Everything here is reversible, and reversing it is the point. A program
that leaves a terminal in raw mode on the alternate screen has made the
user's shell unusable, so #leave undoes exactly what #enter did and can
be called any number of times.
It works over any pair of IOs. When those are not a terminal — a pipe, a
spec — the mode changes are skipped and the escape sequences still go out,
which is what makes the whole driver testable without a device.
Constants
Pasted text arrives marked as pasted, rather than as a very fast typist triggering every key binding on the way past.
The terminal reports the window gaining and losing focus.
The kitty keyboard protocol, which tells apart keystrokes an ordinary terminal reports identically. This pushes a flag set onto the terminal's own stack and the reset pops it.
Mouse reporting in the SGR encoding, which is the one that can name a column past 223. Both are asked for together, and given back in the reverse order.
The tracking mode is 1002, button-event tracking: press, release, and motion while a button is held. Mode 1000 reports only the press and the release, so a widget that takes the pointer on press never learns where it went and nothing can be dragged. 1002 supersedes 1000 on every terminal that has the SGR encoding, which is the only kind this mode is for, so there is no reason to ask for the lesser one.
Enabling this is the application's call — terminal.enable TermBuf::Tty::MOUSE_SGR — and nothing in this shard does it uninvited: a
terminal reporting the mouse is one that no longer lets the person select
and copy text with it, which is a trade only the application can weigh.
Once it is on, a report arrives as Events::Mouse with its coordinates
already converted to buffer cells. Tty#leave sends the reset.
See MOUSE_SGR_ANY for the mode that reports motion with no button held
as well, and MOUSE_SGR_CLICKS for the lesser one, which is kept to be
measured rather than to be used.
Mouse reporting as MOUSE_SGR, but with mode 1003 — any-event tracking,
which reports motion with no button held too. That is what hover needs:
a highlight that follows the pointer, a tooltip, a cursor shape that
changes over a hot spot.
It is not the default because of what it costs. Every cell the pointer
travels over is a report, so crossing a wide terminal is a hundred events
to read, decode, and act on, and over ssh it is a hundred packets. An
application that only drags wants MOUSE_SGR.
All three share the registry name mouse-sgr, because the terminal has
one mouse tracking mode and not three: asking for 1003 after 1002
replaces the tracking rather than adding to it. Tty#enable writes a replacement of
the same name whose bytes differ, so the change reaches the terminal, and
#leave sends the one reset that belongs to whichever was asked for
last.
Mouse reporting in the SGR encoding with mode 1000 — normal tracking, which reports the press and the release and nothing in between.
This is the X10-compatible tracking every terminal has had for forty
years, and no application in this shard wants it: a widget that takes
the pointer on press never learns where it went, so nothing can be
dragged. It is here to be measured. What a terminal does under 1000 and
under 1002 when nothing is held down is a property of that terminal, and
one that has been seen to differ from what the mode is defined as — a
terminal that sends motion reports while no button is down makes a
widget reading a motion report as "a button is held" wrong.
scripts/caps_check.cr records the answer per terminal, and the mouse
page of examples/validate.cr cycles the three modes so it can be
watched.
Shares the registry name mouse-sgr with the two below it; see
MOUSE_SGR_ANY for what that means.
Constructors
Instance methods
Whether the alternate screen has been switched to. Tracked apart from
#entered? because the probe switches to it before the takeover, and
tracked at all so that #leave pops what was pushed rather than what a
capability set settled afterwards says should have been.
Turns mode off and forgets it, so a later #enter does not bring it
back. A mode that was never enabled is nothing to turn off.
Turns mode on: now if the terminal has been taken over, and at #enter
if it has not.
Registration is by name, so enabling the same mode twice registers it
once and writes it once. That is not tidiness. KITTY_KEYBOARD pushes
onto a stack the terminal keeps, and a second push against the single pop
#leave sends leaves the keyboard changed after the program has gone.
A mode of the same name carrying different bytes is a replacement
rather than a repeat, and does go out: cursor_shape is one mode
whichever shape it is asking for, and asking for a bar after a block has
to reach the terminal.
Takes the terminal over: raw mode, the alternate screen, no cursor.
capabilities decides which of the optional modes are worth asking for;
asking a terminal to enable something it does not have leaves the request
printed on screen. Bracketed paste is registered here, and every mode
registered by #enable is written now, which is what makes taking the
terminal back after a suspend put the modes back too.
Switches to the alternate screen, without the rest of the takeover.
This is where the probe goes. A terminal that does not recognise a query prints its payload instead of swallowing it, so asking on the screen the person was looking at leaves rubbish there — and it is still there once the program has given the screen back. On the alternate screen nobody sees any of it and leaving takes it away along with the screen.
Returns whether the screen was switched. Without Capability::AltScreen
there is nowhere to put the echo and the caller has #scrub_line
instead. Idempotent; #enter calls it, and #leave undoes it whether or
not #enter ever ran.
Gives the terminal back exactly as it was found. Safe to call twice, and
safe to call when #enter never ran, which is what makes it usable from
a signal handler and from at_exit.
Every mode registered on this terminal, in the order it was enabled.
#leave resets them in the reverse of that order.
Puts the terminal in raw mode, keeping what it was in so #restore_modes
can put it back. Crystal's own raw! would do most of this, but it
restores to a cooked terminal rather than to whatever was there before,
which is not the same thing when a program was started from something
other than an ordinary shell.
This has to happen before the terminal is asked anything. A cooked terminal echoes the replies onto the screen and holds them in the line discipline until a newline that never comes, so the queries appear to go unanswered and then all arrive at once the moment raw mode is set.
Idempotent: calling it again keeps the modes first found, not the raw
ones, so #restore_modes still has somewhere to go back to.
Set once the modes have been changed, which happens before #enter when
the terminal is about to be probed.
Puts the line discipline back the way it was found. Idempotent, and safe to call when raw mode was never entered.
Blanks the line the cursor is on, using nothing but a carriage return and spaces.
A terminal that does not recognise a query prints its payload instead of
swallowing it, so asking one leaves rubbish on the screen the person was
looking at — and it is still there once the program gives the screen
back. Terminal.app does this with XTGETTCAP, DECRPM and the kitty
graphics query, between them putting about forty five characters on the
line the program started on.
The fallback for a terminal with no alternate screen to ask on. Where
there is one, #enter_alternate puts the echo somewhere nobody is
looking and leaving takes it away; a line of spaces is as much as a
terminal that has just demonstrated it cannot parse an escape sequence
can be asked to understand, and an echo long enough to have wrapped
would leave its earlier lines behind.
How big the terminal is now. Asked afresh every time, since the answer changes whenever the window does.