Xorg on a framebuffer -- the fbdev video driver, the evdev input driver, libpciaccess -- asks the kernel a fixed set of questions before it draws anything, and gives up on the first wrong answer. These are those answers. evdev: EVIOCGVERSION, GID, GNAME, GPHYS, GUNIQ, GPROP, GBIT, GKEY, GABS, GREP and the accepted setters. Xorg and libinput decide what a device *is* from the capability bitmaps; /dev/input/eventN answered only EVIOCGRAB, which reads as no device at all. Each device's bitmaps are its defaults plus whatever it has reported, so a USB tablet makes the pointer absolute. fbdev: FBIOPUT_VSCREENINFO succeeds for the mode already set, as on Linux -- the fbdev driver sets it every time it starts -- and still refuses any other. FBIOPAN_DISPLAY to the origin; FBIOBLANK. The console: KDSETMODE(KD_GRAPHICS) stops it drawing on a terminal, KDSKBMODE(K_OFF) stops keys reaching its tty, and VT_PROCESS now does the switch handshake: leaving the terminal sends relsig and waits for VT_RELDISP, arriving sends acqsig. A terminal whose owner has died goes back to the kernel whole, so a crashed X cannot leave the console dead. /sys: boot_vga, a writable enable, and graphics/fb0 under the display device, which is how the fbdev driver pairs a card with /dev/fb0. ports/linux-headers: <linux/*.h> into musl's prefix, from sabotage- linux's kernel-headers 4.19.88-2. Its checksum is recorded from the first download; upstream publishes none. cmd/xready makes each call and checks the answer, including the switch handshake on tty2 and an owner that dies holding it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
174 lines
7.2 KiB
C
174 lines
7.2 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// Virtual terminals.
|
|
//
|
|
// Until this existed there was one console. `/dev/tty` was an alias for
|
|
// `/dev/console` with the same major and minor, `tty_fg_pgrp` was a single
|
|
// global int, and TIOCSCTTY was `return 0`. One screen, one foreground
|
|
// process group, one line discipline, no way to have a second shell.
|
|
//
|
|
// The thing that makes switching 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 ever wants the screen: there was
|
|
// nowhere to put terminal 2 while terminal 1 was visible, and nothing to
|
|
// repaint terminal 1 from when it came back. So each VT owns a grid of cells
|
|
// and the framebuffer shows whichever grid is active.
|
|
//
|
|
// A VT that is not active is not a special case. Writes to it land in its
|
|
// grid, its cursor moves, it scrolls; the only thing it does not do is draw.
|
|
// That is the whole switching mechanism: vt_switch repaints, and everything
|
|
// else is written as though it were always on screen.
|
|
|
|
#ifndef VT_H
|
|
#define VT_H
|
|
|
|
#include "core/types.h"
|
|
#include "core/param.h"
|
|
#include "uapi/posix.h"
|
|
|
|
// Six, and Linux's numbering: /dev/tty1 through /dev/tty6, reached with
|
|
// Alt+1..Alt+6. A seventh is deliberately not bound -- that is where a
|
|
// display server goes, and Schcom will want it.
|
|
#define NVT 6
|
|
|
|
// A cell.
|
|
//
|
|
// `ch` is the glyph the font draws, not a codepoint: the console maps UTF-8
|
|
// to CP437 before it gets here, because that is what include/gfx/font.h contains.
|
|
// Storing the codepoint would mean re-running that mapping on every repaint
|
|
// and getting a different answer for anything the font cannot draw.
|
|
//
|
|
// The colours are palette indices rather than RGB. Sixteen ANSI colours plus
|
|
// the two theme defaults fit in a byte each and make a cell four bytes; the
|
|
// same cell holding two 32-bit colours would be twelve, and the grids are
|
|
// static.
|
|
struct vtcell {
|
|
// A Unicode codepoint, not a byte.
|
|
//
|
|
// This was a uint8, which is to say the grid could hold 256 distinct glyphs
|
|
// and every terminal was a CP437 terminal. A codepoint outside that was not
|
|
// stored wrongly, it was *converted* -- `glyph_for()` mapped what it could
|
|
// and turned the rest into `?`. Ą, Ę, Ś, Ω, Œ, Ə, ∞ and every other
|
|
// character anybody actually types were question marks before they reached
|
|
// the font, so no font could have fixed it.
|
|
//
|
|
// Four bytes a cell instead of one. A 1920x1080 screen at 9x18 is 12,720
|
|
// cells, so the grid costs 100 KB instead of 50 KB per terminal.
|
|
uint32 ch;
|
|
uint8 fg;
|
|
uint8 bg;
|
|
uint8 attr; // reserved: bold/underline/reverse, which do not change
|
|
uint8 pad; // the glyph and so can be added without touching a grid
|
|
};
|
|
|
|
#define VT_FG_DEFAULT 16 // SCH_FG from include/gfx/theme.h
|
|
#define VT_BG_DEFAULT 17 // SCH_BG
|
|
#define VT_NCOLOUR 18
|
|
|
|
// Cell attributes that do not change which glyph is drawn, only how.
|
|
#define VT_ATTR_REVERSE 0x01
|
|
|
|
// The largest console a grid can hold, which is 1920x1200 with the 8x16 font.
|
|
// A framebuffer wider or taller than this still works -- vt_init clamps the
|
|
// grid and the rest of the screen stays background -- but it is worth raising
|
|
// rather than living with, because the clamp is invisible until someone
|
|
// notices the bottom of their screen never has text on it.
|
|
#define VT_MAXCOLS 240
|
|
#define VT_MAXROWS 75
|
|
|
|
#define VT_INPUT_BUF 256
|
|
#define VT_MAXPARM 8
|
|
|
|
// Escape parser states. E_OSC and E_OSC_ESC exist for hyperlinks: a terminal
|
|
// that does not follow OSC 8 must still consume it, or the URL is printed as
|
|
// text. See the comment in kernel/core/console.c that this came from.
|
|
enum { VE_NORMAL, VE_ESC, VE_CSI, VE_OSC, VE_OSC_ESC };
|
|
|
|
struct vt {
|
|
int index; // 0-based; /dev/tty<index+1>
|
|
int cols, rows;
|
|
|
|
struct vtcell *cells; // cols*rows, row-major, into vt_storage
|
|
|
|
int cx, cy; // cursor, in cells
|
|
// Deferred wrap, the rule every real terminal follows and none of them
|
|
// document well: a glyph printed in the last column does NOT move the
|
|
// cursor to the next line. It sets this instead, and the wrap happens when
|
|
// the *next* glyph arrives. Anything that moves the cursor deliberately --
|
|
// CR, LF, backspace, an escape sequence -- clears it.
|
|
int wrap_pending;
|
|
int saved_cx, saved_cy; // DECSC/DECRC
|
|
|
|
uint8 fg, bg; // current SGR colours
|
|
int bold;
|
|
int attr; // VT_ATTR_* applied to new cells
|
|
|
|
// Escape parser.
|
|
int esc_state;
|
|
int esc_parm[VT_MAXPARM];
|
|
int esc_nparm;
|
|
int esc_private;
|
|
|
|
// UTF-8 decoder.
|
|
uint32 u8_cp;
|
|
int u8_need;
|
|
|
|
// Line discipline. One per terminal, because ICANON and ECHO are properties
|
|
// of a terminal and not of the machine.
|
|
struct termios tio;
|
|
|
|
struct {
|
|
char buf[VT_INPUT_BUF];
|
|
uint r, w, e;
|
|
uint abort_read;
|
|
uint eof;
|
|
} in;
|
|
|
|
// Job control. This was one global for the whole system; a terminal each
|
|
// is the entire point of having more than one.
|
|
int fg_pgrp;
|
|
int sid; // session that owns this terminal, 0 = none
|
|
|
|
int opened; // times it has been opened, for /dev/tty
|
|
|
|
// VT_AUTO or VT_PROCESS. In VT_PROCESS a program has claimed switching and
|
|
// the kernel stops acting on the chord -- see vt_kernel_switches().
|
|
int mode;
|
|
int owner; // pid that set VT_PROCESS
|
|
int relsig, acqsig; // what to send it; see vt_switch()
|
|
int leaving_to; // a switch waiting on VT_RELDISP, or -1
|
|
|
|
int kdmode; // KD_TEXT, or KD_GRAPHICS: not drawn
|
|
int kbmode; // K_UNICODE..., or K_OFF: no keys
|
|
};
|
|
|
|
// The active terminal, and the one the framebuffer is showing.
|
|
extern int vt_active;
|
|
|
|
void vt_init(void);
|
|
void vt_resize(void); // a scanout arrived; re-measure
|
|
struct vt *vt_get(int i); // 0-based, clamped, never null
|
|
struct vt *vt_current(void);
|
|
int vt_switch(int i); // 0-based; repaints. -1 if out of range
|
|
int vt_cycle(int dir); // -1 previous, +1 next, in-use only
|
|
|
|
// Output, per terminal. Drawing happens only if `v` is the active one.
|
|
void vt_feed(struct vt *v, int c); // one output byte, through the parser
|
|
void vt_putc(struct vt *v, int glyph); // one glyph, already mapped
|
|
void vt_cursor_set(struct vt *v, int col, int row);
|
|
void vt_cursor_get(struct vt *v, int *col, int *row);
|
|
void vt_erase_display(struct vt *v, int mode);
|
|
void vt_erase_line(struct vt *v, int mode);
|
|
void vt_clear(struct vt *v);
|
|
void vt_scroll(struct vt *v);
|
|
void vt_repaint(struct vt *v); // grid -> framebuffer, if active
|
|
|
|
uint32 vt_rgb(uint8 idx); // palette index -> RGB
|
|
int vt_index(struct vt *v); // 0-based number of a terminal
|
|
int vt_kernel_switches(void); // 0 if a program owns switching
|
|
int vt_reldisp(struct vt *v, int arg); // VT_RELDISP
|
|
void vt_set_process(struct vt *v, int mode, int relsig, int acqsig);
|
|
void vt_set_kdmode(struct vt *v, int mode);
|
|
int vt_takes_keys(struct vt *v); // 0 in K_OFF or a raw mode
|
|
|
|
#endif // VT_H
|