/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
117 lines
3.4 KiB
C
117 lines
3.4 KiB
C
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "spinlock.h"
|
|
#include "sleeplock.h"
|
|
#include "fs.h"
|
|
#include "file.h"
|
|
#include "x86.h"
|
|
#include "errno.h"
|
|
|
|
extern int consoleread(struct inode*, uint64, char*, int);
|
|
extern int consolewrite(struct inode*, uint64, char*, int);
|
|
extern int mouseread(struct inode*, uint64, char*, int);
|
|
|
|
static uint64 rseed = 0x2A99554433221100ULL;
|
|
|
|
int
|
|
nullwrite(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
return n;
|
|
}
|
|
|
|
int
|
|
nullread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
zeroread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
memset(dst, 0, n);
|
|
return n;
|
|
}
|
|
|
|
int
|
|
randomread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
for(int i = 0; i < n; i++){
|
|
rseed = rseed * 6364136223846793005ULL + 1442695040888963407ULL;
|
|
dst[i] = (rseed >> 32) & 0xFF;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// /dev/full — reads as an endless run of zero bytes, every write fails with
|
|
// ENOSPC. Programs use it to exercise their out-of-disk-space paths.
|
|
int
|
|
fullwrite(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
return -ENOSPC;
|
|
}
|
|
|
|
int
|
|
mousewrite(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
return n;
|
|
}
|
|
|
|
int
|
|
memread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
switch(ip->minor) {
|
|
case 1: return consoleread(ip, off, dst, n);
|
|
case 2: return nullread(ip, off, dst, n);
|
|
case 3: return consoleread(ip, off, dst, n);
|
|
/* 10 = /dev/tty, 11..16 = /dev/tty1..tty6. consoleread resolves the
|
|
minor to a terminal; see vt_for_minor(). */
|
|
case 10: case 11: case 12: case 13: case 14: case 15: case 16:
|
|
return consoleread(ip, off, dst, n);
|
|
case 5: return zeroread(ip, off, dst, n);
|
|
case 7: return zeroread(ip, off, dst, n); // /dev/full reads as zeros
|
|
case 8:
|
|
case 9: return randomread(ip, off, dst, n);
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
int
|
|
memwrite(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
switch(ip->minor) {
|
|
case 1: return consolewrite(ip, off, dst, n);
|
|
case 10: case 11: case 12: case 13: case 14: case 15: case 16:
|
|
return consolewrite(ip, off, dst, n);
|
|
case 7: return fullwrite(ip, off, dst, n); // /dev/full: always ENOSPC
|
|
case 2:
|
|
case 5:
|
|
case 8:
|
|
case 9: return nullwrite(ip, off, dst, n);
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
struct devsw devsw[] = {
|
|
[0] = { 0, 0 },
|
|
[1] = { memread, memwrite },
|
|
// /dev/fb0 (major 2) — the framebuffer, kernel/dev/fb.c. This used to be
|
|
// { nullread, nullwrite }, so writing pixels to it succeeded and did
|
|
// nothing.
|
|
// The close hook gives the text console its screen back when whatever was
|
|
// drawing on the framebuffer exits — see fbclose().
|
|
[2] = { fbread, fbwrite, 0, fbclose },
|
|
[3] = { mouseread, mousewrite },
|
|
[4] = { 0, 0 },
|
|
// /dev/tty (major 5) — alias for the console
|
|
[5] = { memread, memwrite },
|
|
// /proc status files (major 6) — see kernel/dev/procfs.c
|
|
[6] = { procread, procwrite },
|
|
// /dev/input/eventN (major 7) — raw evdev events, kernel/dev/input.c. The
|
|
// minor is the device index, so event0 is the keyboard and event1 the mouse.
|
|
[7] = { inputread, inputwrite, inputready, inputclose, inputopen },
|
|
// /dev/hdN (major 8) — raw disks, kernel/dev/disk.c. The minor is the IDE
|
|
// device number. No `ready` hook: a disk is always ready, which is what
|
|
// fileread assumes when one is absent.
|
|
[8] = { diskread, diskwrite },
|
|
};
|