// SPDX-License-Identifier: Apache-2.0 // // Virtual terminals. See include/vt.h for why this exists at all; the short // version is that switching needs a backing store, and the console drew // straight into the framebuffer. // // The rule this file is built on: **a terminal does not know whether it is // visible.** Writes land in the grid, the cursor moves, the grid scrolls -- // all of it unconditionally. Drawing is a separate question asked once, at // the bottom, and answered by comparing against vt_active. Every alternative // spreads "am I on screen" through the escape parser, and then a terminal // that was switched away mid-sequence comes back wrong. #include "types.h" #include "defs.h" #include "param.h" #include "spinlock.h" #include "sleeplock.h" #include "posix.h" #include "theme.h" #include "gfx.h" #include "vt.h" int vt_active = 0; // The grids. Static rather than allocated: there is no contiguous multi-page // allocator, a grid is 72 KB, and six of them are a fixed cost the kernel can // pay once at link time instead of failing to pay at run time. static struct vtcell vt_storage[NVT][VT_MAXCOLS * VT_MAXROWS]; static struct vt vts[NVT]; static int vt_ready; // The sixteen ANSI colours, then the two theme defaults. Moved here from // console.c, where they were the only palette because there was only one // terminal to paint. static const uint32 palette[VT_NCOLOUR] = { 0x000000, 0xAA0000, 0x00AA00, 0xAA5500, // black red green yellow(brown) 0x0000AA, 0xAA00AA, 0x00AAAA, 0xAAAAAA, // blue magenta cyan white 0x555555, 0xFF5555, 0x55FF55, 0xFFFF55, // bright versions 0x5555FF, 0xFF55FF, 0x55FFFF, 0xFFFFFF, SCH_FG, // VT_FG_DEFAULT SCH_BG, // VT_BG_DEFAULT }; uint32 vt_rgb(uint8 idx) { return palette[idx < VT_NCOLOUR ? idx : VT_FG_DEFAULT]; } static struct vtcell * cell_at(struct vt *v, int col, int row) { return &v->cells[row * v->cols + col]; } static int is_active(struct vt *v) { return vt_ready && v->index == vt_active; } // Paint one cell from the grid. The single place that turns stored state into // pixels, so anything that changes a cell can simply call this and not think // about whether the terminal is visible. static void show(struct vt *v, int col, int row) { if (!is_active(v)) return; struct vtcell *k = cell_at(v, col, row); // Reverse video swaps the two colours at draw time rather than at store // time, so turning it off restores what was underneath instead of leaving // the swapped pair behind. if (k->attr & VT_ATTR_REVERSE) gfx_draw_cell(col, row, k->ch, vt_rgb(k->bg), vt_rgb(k->fg)); else gfx_draw_cell(col, row, k->ch, vt_rgb(k->fg), vt_rgb(k->bg)); } static void blank_row(struct vt *v, int row) { for (int c = 0; c < v->cols; c++) { struct vtcell *k = cell_at(v, c, row); k->ch = ' '; k->fg = v->fg; k->bg = v->bg; k->attr = 0; } } // --------------------------------------------------------------------------- // Setup // --------------------------------------------------------------------------- // The termios every terminal starts with. This was a single file-static in // console.c called `tio`; it is per-terminal now because ICANON and ECHO // describe a terminal, and a program that turns off echo on tty2 has said // nothing whatever about tty1. static void termios_defaults(struct termios *t) { memset(t, 0, sizeof(*t)); t->c_iflag = ICRNL; t->c_oflag = OPOST | ONLCR; t->c_cflag = CS8 | CREAD; t->c_lflag = ISIG | ICANON | ECHO | ECHOE; t->c_cc[VINTR] = 'C' & 0x1f; t->c_cc[VQUIT] = '\\' & 0x1f; t->c_cc[VERASE] = '\x7f'; t->c_cc[VKILL] = 'U' & 0x1f; t->c_cc[VEOF] = 'D' & 0x1f; t->c_cc[VSUSP] = 'Z' & 0x1f; t->c_cc[VWERASE]= 'W' & 0x1f; t->c_cc[VMIN] = 1; t->c_ispeed = 15; t->c_ospeed = 15; } // The screen size, clamped to what a grid can hold. // // 80x24 when there is no framebuffer yet -- which is the usual case when // vt_init runs, because the display driver is found on the PCI bus and the // terminals are set up before the bus is walked. vt_resize() corrects it the // moment a scanout arrives. static void measure(int *out_cols, int *out_rows) { int cols = gfx_cols(), rows = gfx_rows(); if (cols <= 0) cols = 80; if (rows <= 0) rows = 24; if (cols > VT_MAXCOLS) cols = VT_MAXCOLS; if (rows > VT_MAXROWS) rows = VT_MAXROWS; *out_cols = cols; *out_rows = rows; } void vt_init(void) { int cols, rows; measure(&cols, &rows); for (int i = 0; i < NVT; i++) { struct vt *v = &vts[i]; memset(v, 0, sizeof(*v)); v->index = i; v->cols = cols; v->rows = rows; v->cells = vt_storage[i]; v->fg = VT_FG_DEFAULT; v->bg = VT_BG_DEFAULT; v->esc_state = VE_NORMAL; // pgid 1, which is init's, so the first shell on each terminal already // sees itself as the foreground job rather than stopping with SIGTTIN. v->fg_pgrp = 1; termios_defaults(&v->tio); for (int r = 0; r < rows; r++) blank_row(v, r); } vt_active = 0; vt_ready = 1; } // The real screen has arrived. Re-measure and re-lay the grids. // // This exists because of the order the kernel comes up in: terminals are // initialised before the PCI bus is walked, so the display driver attaches its // scanout *after* vt_init has already sized every grid from a framebuffer that // was not there yet. Without this the grids stayed 80x24 on a 160x50 screen -- // text stopped two thirds of the way down, `ls` formatted for a width the grid // did not have and split words across the wrap, and zsh's cursor tracking was // wrong on every line. // // Content is not preserved. Reflowing a grid into a different width is a real // problem and there is nothing here worth reflowing: this runs during boot, // before console_takeover(), when every grid is still blank. void vt_resize(void) { int cols, rows; if (!vt_ready) return; // vt_init has not run; it will measure itself measure(&cols, &rows); if (cols == vts[0].cols && rows == vts[0].rows) return; // same screen, nothing to do for (int i = 0; i < NVT; i++) { struct vt *v = &vts[i]; v->cols = cols; v->rows = rows; if (v->cx >= cols) v->cx = cols - 1; if (v->cy >= rows) v->cy = rows - 1; for (int r = 0; r < rows; r++) blank_row(v, r); } vt_repaint(vt_current()); } struct vt * vt_get(int i) { if (i < 0) i = 0; if (i >= NVT) i = NVT - 1; return &vts[i]; } struct vt * vt_current(void) { return &vts[vt_active]; } // --------------------------------------------------------------------------- // Output // --------------------------------------------------------------------------- void vt_repaint(struct vt *v) { if (!is_active(v)) return; for (int r = 0; r < v->rows; r++) for (int c = 0; c < v->cols; c++) { struct vtcell *k = cell_at(v, c, r); if (k->attr & VT_ATTR_REVERSE) gfx_draw_cell(c, r, k->ch, vt_rgb(k->bg), vt_rgb(k->fg)); else gfx_draw_cell(c, r, k->ch, vt_rgb(k->fg), vt_rgb(k->bg)); } } void vt_scroll(struct vt *v) { // The grid moves whether or not anyone is watching. If they are, the // framebuffer is scrolled with a memmove rather than repainted cell by // cell -- a full repaint of a 160x50 screen is a million pixel writes, and // a shell printing a long file would spend all its time in this function. memmove(v->cells, cell_at(v, 0, 1), (uint64)(v->rows - 1) * v->cols * sizeof(struct vtcell)); blank_row(v, v->rows - 1); if (is_active(v)) { gfx_scroll_up(vt_rgb(v->bg)); // The exposed row was blanked in v->bg; gfx_scroll_up painted it the same // colour, so there is nothing further to draw. } if (v->cy > 0) v->cy--; } void vt_cursor_set(struct vt *v, int col, int row) { // Clamped rather than validated: an escape sequence is untrusted input from // any program that can write to the terminal, and "CUP to row 9999" should // land on the last row rather than off the end of the grid. if (col < 0) col = 0; if (row < 0) row = 0; if (col >= v->cols) col = v->cols - 1; if (row >= v->rows) row = v->rows - 1; v->cx = col; v->cy = row; v->wrap_pending = 0; // an explicit move is not a wrap } void vt_cursor_get(struct vt *v, int *col, int *row) { if (col) *col = v->cx; if (row) *row = v->cy; } static void put_blank_at(struct vt *v, int col, int row) { struct vtcell *k = cell_at(v, col, row); k->ch = ' '; k->fg = v->fg; k->bg = v->bg; k->attr = 0; show(v, col, row); } // Take the deferred wrap, if one is owed. Called before anything that needs // the cursor to be at a real position. static void wrap_now(struct vt *v) { if (!v->wrap_pending) return; v->wrap_pending = 0; v->cx = 0; v->cy++; while (v->cy >= v->rows) vt_scroll(v); } void vt_putc(struct vt *v, int glyph) { switch (glyph) { case '\n': v->wrap_pending = 0; v->cx = 0; v->cy++; break; case '\r': v->wrap_pending = 0; v->cx = 0; break; case '\b': case 0x7f: v->wrap_pending = 0; if (v->cx > 0) { v->cx--; put_blank_at(v, v->cx, v->cy); } break; case '\t': // To the next multiple of eight, blanking as it goes so a tab overwrites // rather than skipping over stale characters. Without a case here '\t' // reaches the font as glyph 9, which in CP437 is a hollow circle: pressing // Tab printed a circle. wrap_now(v); do { put_blank_at(v, v->cx, v->cy); v->cx++; } while (v->cx % 8 != 0 && v->cx < v->cols); if (v->cx >= v->cols) { v->cx = v->cols - 1; v->wrap_pending = 1; } break; default: { // A C0 control character that got this far has no handled meaning, and a // terminal never *draws* one. The font has a picture for every byte below // 0x20 and none of those pictures is what the sender meant: zsh emits // 0x0F to leave alt-charset mode, because that is what the `linux` // terminfo says rmacs is, and CP437 glyph 0x0F is a sun. It appeared in // the prompt on every line. if (glyph < 0x20) return; if (glyph < 0 || glyph > 255) glyph = '?'; wrap_now(v); // the previous glyph filled the line struct vtcell *k = cell_at(v, v->cx, v->cy); k->ch = (uint8)glyph; k->fg = v->fg; k->bg = v->bg; k->attr = (uint8)v->attr; show(v, v->cx, v->cy); // Filling the last column does not move to the next line. It owes a wrap, // and pays it when the next glyph arrives -- which is what makes a line // that is exactly as wide as the screen take one row instead of two, and // what lets a program write to the bottom-right cell without scrolling. if (v->cx + 1 >= v->cols) v->wrap_pending = 1; else v->cx++; break; } } while (v->cy >= v->rows) vt_scroll(v); } // Fill a run of cells with the current background, wrapping at the right edge. static void erase_run(struct vt *v, int col, int row, int count) { while (count-- > 0 && row < v->rows) { put_blank_at(v, col, row); if (++col >= v->cols) { col = 0; row++; } } } // ED -- erase in display. 0: cursor to end, 1: start to cursor, 2: all. void vt_erase_display(struct vt *v, int mode) { switch (mode) { case 0: erase_run(v, v->cx, v->cy, (v->rows - v->cy) * v->cols - v->cx); break; case 1: erase_run(v, 0, 0, v->cy * v->cols + v->cx + 1); break; default: vt_clear(v); break; } } // EL -- erase in line. The same three modes, bounded to the current row. void vt_erase_line(struct vt *v, int mode) { switch (mode) { case 0: erase_run(v, v->cx, v->cy, v->cols - v->cx); break; case 1: erase_run(v, 0, v->cy, v->cx + 1); break; default: erase_run(v, 0, v->cy, v->cols); break; } } void vt_clear(struct vt *v) { for (int r = 0; r < v->rows; r++) blank_row(v, r); v->cx = v->cy = 0; v->wrap_pending = 0; vt_repaint(v); } // --------------------------------------------------------------------------- // The escape parser // // Moved here from console.c along with the state it needs. It was a set of // file-statics there -- one parser for one screen -- and a parser is per // terminal for the same reason a cursor is: a program halfway through a CSI // sequence on tty2 has said nothing about tty1. // // The serial line does NOT go through any of this. On the other end of it is // a real terminal emulator that understands ANSI far better than this ever // will, so escapes are passed to it verbatim and interpreted only for the // framebuffer. Two consumers, two right answers. // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // Unicode -> glyph // // The font's upper half is CP437, not Latin-1, so a codepoint cannot be used // as an index. It looks like it can -- font.h's own comments say so, labelling // each glyph with the Unicode character of the same number -- but the bitmaps // disagree: index 0xE9 draws a capital theta, and rendering U+00E9 there turns // "cafe" into "caf" followed by a theta. Every accented character came out as // an unrelated symbol, consistently enough to look almost deliberate. // // So: a table. Entry i covers codepoint 0x80 + i, and 0 means "this font has // no glyph for that". The C1 range (0x80-0x9F) has no printable characters at // all, hence the leading run of zeros. // --------------------------------------------------------------------------- static const uchar latin1_to_cp437[128] = { /* 0x80-0x9F: C1 controls, nothing to draw */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, /* 0xA0 */ 0x20, 0xAD, 0x9B, 0x9C, 0, 0x9D, 0, 0x15, /* 0xA8 */ 0, 0, 0xA6, 0xAE, 0xAA, 0x2D, 0, 0, /* 0xB0 */ 0xF8, 0xF1, 0xFD, 0, 0, 0xE6, 0x14, 0xFA, /* 0xB8 */ 0, 0, 0xA7, 0xAF, 0xAC, 0xAB, 0, 0xA8, /* 0xC0 */ 0, 0, 0, 0, 0x8E, 0x8F, 0x92, 0x80, /* 0xC8 */ 0, 0x90, 0, 0, 0, 0, 0, 0, /* 0xD0 */ 0, 0xA5, 0, 0, 0, 0, 0x99, 0, /* 0xD8 */ 0, 0, 0, 0, 0x9A, 0, 0, 0xE1, /* 0xE0 */ 0x85, 0xA0, 0x83, 0, 0x84, 0x86, 0x91, 0x87, /* 0xE8 */ 0x8A, 0x82, 0x88, 0x89, 0x8D, 0xA1, 0x8C, 0x8B, /* 0xF0 */ 0, 0xA4, 0x95, 0xA2, 0x93, 0, 0x94, 0xF6, /* 0xF8 */ 0, 0x97, 0xA3, 0x96, 0x81, 0, 0, 0x98, }; // The handful of characters above U+00FF that CP437 does have. Box drawing // and block elements matter because they are what every text-mode program // draws frames and progress bars out of. struct cp_pair { uint32 cp; uchar glyph; }; static const struct cp_pair cp437_extra[] = { { 0x2022, 0x07 }, /* bullet */ { 0x2500, 0xC4 }, { 0x2502, 0xB3 }, /* light horizontal/vertical */ { 0x250C, 0xDA }, { 0x2510, 0xBF }, { 0x2514, 0xC0 }, { 0x2518, 0xD9 }, { 0x251C, 0xC3 }, { 0x2524, 0xB4 }, { 0x252C, 0xC2 }, { 0x2534, 0xC1 }, { 0x253C, 0xC5 }, { 0x2550, 0xCD }, { 0x2551, 0xBA }, /* double */ { 0x2554, 0xC9 }, { 0x2557, 0xBB }, { 0x255A, 0xC8 }, { 0x255D, 0xBC }, { 0x2580, 0xDF }, { 0x2584, 0xDC }, { 0x2588, 0xDB }, { 0x258C, 0xDD }, { 0x2590, 0xDE }, { 0x2591, 0xB0 }, { 0x2592, 0xB1 }, { 0x2593, 0xB2 }, { 0x25A0, 0xFE }, /* black square */ { 0x221A, 0xFB }, { 0x221E, 0xEC }, /* root, infinity */ { 0x2260, 0, }, { 0x2264, 0xF3 }, { 0x2265, 0xF2 }, { 0x2248, 0xF7 }, { 0x2261, 0xF0 }, }; // Returns the glyph to draw for a codepoint, or 0 if there is none. static int glyph_for(uint32 cp) { if (cp < 0x80) return (int)cp; // ASCII is CP437's lower half if (cp < 0x100) return latin1_to_cp437[cp - 0x80]; for (unsigned i = 0; i < NELEM(cp437_extra); i++) if (cp437_extra[i].cp == cp) return cp437_extra[i].glyph; return 0; } // SGR. Simpler than it was, because a cell stores a palette index rather than // an RGB value: "bright" is +8 and "default" is a named index, where before // both had to be recovered by searching the palette for a matching colour. static void vt_sgr(struct vt *v) { // "ESC[m" with no parameters means ESC[0m -- reset -- and ECMA-48 says so // for every CSI: an omitted parameter takes its default, and SGR's is 0. // // This loop used to start at a count of zero and therefore do nothing, so a // bare reset was silently dropped. Programs emit it constantly: fastfetch // writes "\x1b[32m1%\x1b[m", and with the reset ignored everything after // the first coloured character stayed green forever. Worse with background // colours -- its palette swatch sets a white background and resets, so the // whole console went white-on-white and stayed there, which looks like a // corrupted framebuffer rather than a missing branch. int nparm = v->esc_nparm ? v->esc_nparm : 1; // esc_parm[0] is already 0 for (int i = 0; i < nparm; i++) { int p = v->esc_parm[i]; if (p == 0) { v->fg = VT_FG_DEFAULT; v->bg = VT_BG_DEFAULT; v->bold = 0; v->attr = 0; } else if (p == 1) { v->bold = 1; if (v->fg < 8) v->fg += 8; // brighten a colour already chosen } else if (p == 7) { v->attr |= VT_ATTR_REVERSE; } else if (p == 27) { v->attr &= ~VT_ATTR_REVERSE; } else if (p == 22) { v->bold = 0; if (v->fg >= 8 && v->fg < 16) v->fg -= 8; } else if (p >= 30 && p <= 37) { v->fg = (uint8)((p - 30) + (v->bold ? 8 : 0)); } else if (p == 39) { v->fg = VT_FG_DEFAULT; } else if (p >= 40 && p <= 47) { v->bg = (uint8)(p - 40); } else if (p == 49) { v->bg = VT_BG_DEFAULT; } else if (p >= 90 && p <= 97) { v->fg = (uint8)((p - 90) + 8); } else if (p >= 100 && p <= 107) { v->bg = (uint8)((p - 100) + 8); } } } // One final byte of a CSI sequence, with esc_parm[] already filled in. static void vt_csi(struct vt *v, int final) { int col = v->cx, row = v->cy; int n = v->esc_nparm > 0 ? v->esc_parm[0] : 0; int n1 = n ? n : 1; // most sequences default to 1 switch (final) { case 'A': vt_cursor_set(v, col, row - n1); break; case 'B': vt_cursor_set(v, col, row + n1); break; case 'C': vt_cursor_set(v, col + n1, row); break; case 'D': vt_cursor_set(v, col - n1, row); break; case 'E': vt_cursor_set(v, 0, row + n1); break; case 'F': vt_cursor_set(v, 0, row - n1); break; case 'G': vt_cursor_set(v, n1 - 1, row); break; case 'd': vt_cursor_set(v, col, n1 - 1); break; case 'H': case 'f': { int r = v->esc_nparm > 0 && v->esc_parm[0] ? v->esc_parm[0] : 1; int c = v->esc_nparm > 1 && v->esc_parm[1] ? v->esc_parm[1] : 1; vt_cursor_set(v, c - 1, r - 1); // ANSI counts from 1 break; } case 'J': vt_erase_display(v, n); break; case 'K': vt_erase_line(v, n); break; case 'm': vt_sgr(v); break; case 's': v->saved_cx = col; v->saved_cy = row; break; case 'u': vt_cursor_set(v, v->saved_cx, v->saved_cy); break; // Cursor visibility (DECTCEM) and the other private modes. There is no // hardware cursor being drawn, so there is nothing to show or hide -- // swallowing them silently is correct, and far better than printing // "?25l" into the middle of someone's output. case 'h': case 'l': break; default: break; // unknown: drop it, do not print } } // Feed one output byte to a terminal. The serial side has already had the // raw byte; this is the framebuffer's interpretation of it. void vt_feed(struct vt *v, int c) { switch (v->esc_state) { case VE_ESC: if (c == '[') { v->esc_state = VE_CSI; v->esc_nparm = 0; v->esc_private = 0; for (int i = 0; i < VT_MAXPARM; i++) v->esc_parm[i] = 0; return; } if (c == ']') { v->esc_state = VE_OSC; return; } v->esc_state = VE_NORMAL; return; case VE_OSC: // Consume to BEL or to ESC \. A terminal that does not follow OSC 8 must // still swallow it, or the URL is printed as text -- which is what // "Disk (]8;;file:///\]8;;\): 4.6 MiB" was: fastfetch wrapping a path in // a hyperlink, and this console spelling it out. if (c == 0x07) { v->esc_state = VE_NORMAL; return; } if (c == 0x1b) { v->esc_state = VE_OSC_ESC; return; } return; case VE_OSC_ESC: v->esc_state = (c == '\\') ? VE_NORMAL : VE_OSC; return; case VE_CSI: if (c == '?') { v->esc_private = 1; return; } if (c >= '0' && c <= '9') { if (v->esc_nparm == 0) v->esc_nparm = 1; if (v->esc_nparm <= VT_MAXPARM) v->esc_parm[v->esc_nparm - 1] = v->esc_parm[v->esc_nparm - 1] * 10 + (c - '0'); return; } if (c == ';') { if (v->esc_nparm < VT_MAXPARM) v->esc_nparm++; return; } v->esc_state = VE_NORMAL; if (!v->esc_private) vt_csi(v, c); return; } // VE_NORMAL if (c == 0x1b) { v->esc_state = VE_ESC; return; } // UTF-8 continuation. A codepoint the font cannot draw becomes '?' -- the // one thing it must not become is a *different* character, which is exactly // what indexing the font by codepoint did. if (v->u8_need > 0) { if ((c & 0xC0) != 0x80) { // malformed: abandon the sequence v->u8_need = 0; vt_putc(v, '?'); return; } v->u8_cp = (v->u8_cp << 6) | (c & 0x3F); if (--v->u8_need > 0) return; int g = glyph_for(v->u8_cp); vt_putc(v, g ? g : '?'); return; } if (c >= 0xF0) { v->u8_cp = c & 0x07; v->u8_need = 3; return; } else if (c >= 0xE0) { v->u8_cp = c & 0x0F; v->u8_need = 2; return; } else if (c >= 0xC0) { v->u8_cp = c & 0x1F; v->u8_need = 1; return; } else if (c >= 0x80) { vt_putc(v, '?'); return; } // stray continuation vt_putc(v, c); } // --------------------------------------------------------------------------- // Switching // --------------------------------------------------------------------------- int vt_switch(int i) { if (i < 0 || i >= NVT) return -1; if (!vt_ready) return -1; if (i == vt_active) return 0; vt_active = i; // Repaint from the grid. Nothing was saved on the way out because nothing // needed to be: the outgoing terminal's grid has been current all along, // which is the whole reason the grid exists. vt_repaint(&vts[i]); return 0; } int vt_index(struct vt *v) { return v ? v->index : 0; } // May the kernel act on the switch chord? // // Not if the terminal on screen is in VT_PROCESS: a program has said it will // handle switching itself, and the whole point of saying so is that the chord // belongs to it now. This is the documented Unix answer to "my application // wants that key", and it is the one a display server will use -- Schcom, on // the terminal it owns, so that Ctrl+Alt+Shift+F2 reaches its own compositor // bindings instead of yanking the screen out from under it. // // What this does *not* do yet: deliver relsig/acqsig and wait for VT_RELDISP. // Linux signals the owning process on every switch request and blocks until it // answers, which is how a display server gets to release the GPU first. There // is nothing here that needs to release anything, so the kernel simply stops // switching rather than negotiating. When Schcom needs the handshake, this is // where it goes, and struct vt_mode already carries the fields for it. int vt_kernel_switches(void) { return vt_current()->mode != VT_PROCESS; }