/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
84 lines
2.2 KiB
C
84 lines
2.2 KiB
C
#include "types.h"
|
|
#include "x86.h"
|
|
#include "defs.h"
|
|
#include "kbd.h"
|
|
#include "vt.h" /* NVT -- how many terminals the chords reach */
|
|
#include "input.h"
|
|
|
|
int
|
|
kbdgetc(void)
|
|
{
|
|
static uint shift;
|
|
static uchar *charcode[4] = {
|
|
normalmap, shiftmap, ctlmap, ctlmap
|
|
};
|
|
uint st, data, c;
|
|
|
|
st = inb(KBSTATP);
|
|
if((st & KBS_DIB) == 0)
|
|
return -1;
|
|
|
|
if(st & 0x20)
|
|
return -1;
|
|
|
|
data = inb(KBDATAP);
|
|
|
|
if(data == 0xE0){
|
|
shift |= E0ESC;
|
|
return 0;
|
|
}
|
|
|
|
// Feed /dev/input/event0 before the byte is turned into a character. This
|
|
// has to happen here rather than in a second handler because the byte can
|
|
// only be read from the port once -- whoever reads it owes it to everyone
|
|
// else. The console path below is untouched: both consumers see every key.
|
|
input_ps2_scancode(data, (shift & E0ESC) != 0);
|
|
|
|
// While something holds an exclusive grab on the keyboard, the console gets
|
|
// nothing. The shift/caps state below is still tracked, so releasing the
|
|
// grab does not leave the line discipline believing a modifier is held.
|
|
if(input_is_grabbed(INPUT_KBD)){
|
|
if(data & 0x80){
|
|
data = (shift & E0ESC ? data : data & 0x7F);
|
|
shift &= ~(shiftcode[data] | E0ESC);
|
|
} else {
|
|
shift |= shiftcode[data];
|
|
shift ^= togglecode[data];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if(data & 0x80){
|
|
data = (shift & E0ESC ? data : data & 0x7F);
|
|
shift &= ~(shiftcode[data] | E0ESC);
|
|
return 0;
|
|
} else if(shift & E0ESC){
|
|
data |= 0x80;
|
|
shift &= ~E0ESC;
|
|
}
|
|
|
|
shift |= shiftcode[data];
|
|
shift ^= togglecode[data];
|
|
|
|
// Alt+F1..F6 before the character tables, because there is no character to
|
|
// look up: it is a chord that means "show me a different terminal". What it
|
|
// means is decided in console.c, which is where the terminals are; this only
|
|
// has to name it. See console_vt_hotkey().
|
|
if ((shift & ALT) && data >= SCAN_F1 && data < SCAN_F1 + NVT)
|
|
return KEY_ALT_F1 + (data - SCAN_F1);
|
|
|
|
c = charcode[shift & (CTL | SHIFT)][data];
|
|
if(shift & CAPSLOCK){
|
|
if('a' <= c && c <= 'z')
|
|
c += 'A' - 'a';
|
|
else if('A' <= c && c <= 'Z')
|
|
c += 'a' - 'A';
|
|
}
|
|
return c;
|
|
}
|
|
|
|
void
|
|
kbdintr(void)
|
|
{
|
|
consoleintr(kbdgetc);
|
|
}
|