tty: six virtual terminals, and the plumbing that makes them terminals
/dev/tty1..tty6, Alt+F1..F6 or `chvt N`. Each is an independent terminal --
its own screen, cursor, line discipline, foreground process group and
session. Two shells on two terminals are unrelated processes that share a
keyboard and a screen.
What made it possible is a backing store. The console drew straight into the
framebuffer and scrolled by moving pixels, which works exactly as long as
nothing else wants the screen: there was nowhere to put terminal 2 while
terminal 1 was visible, and nothing to repaint terminal 1 from. Each terminal
owns a grid of cells now and the framebuffer shows whichever grid is active,
so vt_switch() is set-index, repaint, return -- nothing is saved on the way
out because the outgoing grid has been current all along.
The rule the layer is built on: a terminal does not know whether it is
visible. Writes land in the grid, the cursor moves, the grid scrolls, all
unconditionally; drawing is one question asked at the bottom of vt.c. Every
alternative spreads "am I on screen" through the escape parser, and then a
terminal switched away from mid-sequence comes back wrong.
Everything that was a single global is per-terminal now: tty_fg_pgrp (deleted),
termios, the input ring, the escape parser, the UTF-8 decoder, and the cursor
-- which also stops being pixels in gfx.c and becomes cells in the terminal.
TIOCSCTTY was `return 0` and now records proc->ctty, which is what /dev/tty
resolves through; /dev/tty was an alias for /dev/console with the same minor,
and they are different questions.
kbd.c names Alt+F1..F6 as KEY_ALT_F1+n, above 0xFF on purpose -- every byte
value is already a character or a UTF-8 continuation, and a chord colliding
with one would be typed into the terminal instead of switching away from it.
What the chord means is decided in console.c, where the terminals are.
VT_ACTIVATE / VT_WAITACTIVE / VT_GETSTATE / VT_OPENQRY, Linux's numbers.
The argument to ACTIVATE is by value, not a pointer -- same shape as TCFLSH.
Two bugs found by screenshotting the result rather than reading the log:
- zsh emits 0x0F to leave alt-charset mode, because that is what the `linux`
terminfo says rmacs is, and the console drew CP437 glyph 0x0F: a sun, in
the prompt, on every line. A terminal never draws a C0 control character.
- SGR 7 was unimplemented, so zsh's partial-line marker rendered as a bare
'#' instead of a reversed block. Reverse video swaps the colours at draw
time, not at store time, so turning it off restores what was underneath.
Verified by screendump: tty1 and tty2 show different screens, and switching
back repaints tty1's earlier content from its grid. schfs-check passes.
docs/architecture/terminals.md, including what it does not do -- no
scrollback, no reflow, no VT_PROCESS mode for a display server to veto a
switch, which Schcom will need before it owns Alt+F7.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
parent
14f0e4d0e2
commit
1c1b0c9570
24 changed files with 1460 additions and 458 deletions
93
cmd/chvt/main.c
Normal file
93
cmd/chvt/main.c
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// chvt N -- show virtual terminal N.
|
||||
//
|
||||
// The same name and the same argument as the Linux tool, because it does the
|
||||
// same thing and nobody should have to learn a second one. Alt+F1..F6 does
|
||||
// this from the keyboard; this does it from a script, and from a serial
|
||||
// console where there are no function keys to press.
|
||||
//
|
||||
// chvt report which terminal is active and which are in use
|
||||
// chvt 2 switch to /dev/tty2
|
||||
|
||||
#include "types.h"
|
||||
#include "user.h"
|
||||
#include "fcntl.h"
|
||||
#include "posix.h"
|
||||
|
||||
// No printf in lib/, and this needs one string and two small numbers.
|
||||
static void
|
||||
puts(const char *s)
|
||||
{
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
write(1, (void *)s, n);
|
||||
}
|
||||
|
||||
static void
|
||||
putn(int v)
|
||||
{
|
||||
char b[12];
|
||||
int i = 0;
|
||||
if (v < 0) { write(1, "-", 1); v = -v; }
|
||||
if (v == 0) { write(1, "0", 1); return; }
|
||||
while (v > 0) { b[i++] = '0' + (v % 10); v /= 10; }
|
||||
while (i-- > 0) write(1, &b[i], 1);
|
||||
}
|
||||
|
||||
static int
|
||||
num(const char *s)
|
||||
{
|
||||
int v = 0;
|
||||
while (*s >= '0' && *s <= '9')
|
||||
v = v * 10 + (*s++ - '0');
|
||||
return v;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
// /dev/console rather than fd 0: the caller may have redirected its input,
|
||||
// and the terminal being asked about is the machine's, not whichever one
|
||||
// this process happens to be reading from.
|
||||
int fd = open("/dev/console", O_RDWR);
|
||||
if (fd < 0) {
|
||||
puts("chvt: cannot open /dev/console\n");
|
||||
exit();
|
||||
}
|
||||
|
||||
if (argc == 1) {
|
||||
struct vt_stat st;
|
||||
if (ioctl(fd, VT_GETSTATE, &st) < 0) {
|
||||
puts("chvt: VT_GETSTATE failed\n");
|
||||
exit();
|
||||
}
|
||||
puts("active: tty");
|
||||
putn(st.v_active);
|
||||
puts("\nin use:");
|
||||
for (int i = 1; i <= 16; i++)
|
||||
if (st.v_state & (1 << i)) {
|
||||
puts(" tty");
|
||||
putn(i);
|
||||
}
|
||||
puts("\n");
|
||||
exit();
|
||||
}
|
||||
|
||||
int n = num(argv[1]);
|
||||
if (n < 1) {
|
||||
puts("usage: chvt N\n");
|
||||
exit();
|
||||
}
|
||||
|
||||
// The argument goes by value, not through a pointer -- Linux defines
|
||||
// VT_ACTIVATE that way and so does SchOS. Passing a pointer here would
|
||||
// switch to whatever the address happened to be.
|
||||
if (ioctl(fd, VT_ACTIVATE, (void *)(long)n) < 0) {
|
||||
puts("chvt: no terminal ");
|
||||
putn(n);
|
||||
puts("\n");
|
||||
exit();
|
||||
}
|
||||
exit();
|
||||
}
|
||||
Loading…
Reference in a new issue