- tty1-tty12 all run login; the image has only root. /dev/tty7..12 could not be written (cdev.c knew minors 10-16 only), which sent their jobs into a restart storm that took the supervisor down; fixed, and stat reports them as Linux's tty7..12. - Erasing text at a prompt did nothing: erase_one sent a bare backspace, which only moves; it and the echo path now send back-blank-back through vt_feed. - A white cursor block was left at the end of every line of a verbose boot: a line feed at the bottom scrolls the pixels, cursor included, and the cursor was erased after the scroll. It comes off first now. A blank cell is also marked dirty, which it was not. - ldlsd died with a general protection fault on real hardware in its variadic log function: Linux module code calls with an 8-byte-aligned stack and an unset %al, and the prologue's movaps faults. LDLS guest code is built with -mstackrealign. - make iso took over a minute for any change, because staging r8169 rewrote the same files and every mtime moved; it now replaces files only if their contents differ (about 10 s). - The loader's splash is redrawn in the boot manager's style, and the boot manager shows its cursor whenever a pointer device exists. - xf86-input-libinput is out of the image again: with it Xorg took both keyboard and pointer from libinput and, on the user's laptop, neither worked. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
1266 lines
42 KiB
C
1266 lines
42 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// Virtual terminals. See include/dev/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 "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "core/spinlock.h"
|
|
#include "core/sleeplock.h"
|
|
#include "uapi/posix.h"
|
|
#include "gfx/theme.h"
|
|
#include "gfx/gfx.h"
|
|
#include "dev/vt.h"
|
|
#include "arch/mmu.h"
|
|
#include "core/proc.h"
|
|
#include "uapi/errno.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];
|
|
}
|
|
|
|
// On screen, and the console's to draw. A terminal in KD_GRAPHICS belongs to
|
|
// a display server: its grid still takes writes -- a kernel message lands in
|
|
// it -- but nothing is drawn, or the text would be painted over the desktop.
|
|
static int
|
|
is_active(struct vt *v)
|
|
{
|
|
return vt_ready && v->index == vt_active && v->kdmode != KD_GRAPHICS;
|
|
}
|
|
|
|
// 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;
|
|
|
|
// Idempotent. A verbose boot brings the terminals up early, before the PCI
|
|
// bus is walked, so that the kernel log has somewhere to go; main() then
|
|
// calls this again in its usual place. The second call must not wipe six
|
|
// screens of boot messages.
|
|
if (vt_ready) {
|
|
vt_resize();
|
|
return;
|
|
}
|
|
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;
|
|
v->leaving_to = -1;
|
|
v->kbmode = K_UNICODE;
|
|
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
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// The text cursor. See struct vt. Drawn as the cell with its colours swapped
|
|
// -- relative to what the cell would show, so a cursor on reverse-video text
|
|
// is the plain text and every cursor on screen is visible against its cell.
|
|
static void
|
|
cursor_erase(struct vt *v)
|
|
{
|
|
if (!v->curs_drawn)
|
|
return;
|
|
v->curs_drawn = 0;
|
|
if (v->curs_x < v->cols && v->curs_y < v->rows)
|
|
show(v, v->curs_x, v->curs_y);
|
|
}
|
|
|
|
static void
|
|
cursor_draw(struct vt *v)
|
|
{
|
|
if (v->curs_hidden || !is_active(v))
|
|
return;
|
|
// A glyph in the last column leaves the cursor *on* it with the wrap
|
|
// pending, and that is where it is drawn.
|
|
int col = v->cx >= v->cols ? v->cols - 1 : v->cx;
|
|
int row = v->cy >= v->rows ? v->rows - 1 : v->cy;
|
|
if (col < 0 || row < 0)
|
|
return;
|
|
struct vtcell *k = cell_at(v, col, row);
|
|
if (k->attr & VT_ATTR_REVERSE)
|
|
gfx_draw_cell(col, row, k->ch, vt_rgb(k->fg), vt_rgb(k->bg));
|
|
else
|
|
gfx_draw_cell(col, row, k->ch, vt_rgb(k->bg), vt_rgb(k->fg));
|
|
v->curs_x = col;
|
|
v->curs_y = row;
|
|
v->curs_drawn = 1;
|
|
}
|
|
|
|
void
|
|
vt_repaint(struct vt *v)
|
|
{
|
|
if (!is_active(v))
|
|
return;
|
|
v->curs_drawn = 0; // the repaint below paints over it
|
|
|
|
// The margins first: the screen is rarely a whole number of cells, and the
|
|
// strip right of the last column and below the last row belongs to nobody.
|
|
// Coming back from X, that strip still held the bottom of the desktop.
|
|
int cw, ch;
|
|
gfx_cell_size(&cw, &ch);
|
|
if (cw > 0 && ch > 0) {
|
|
int w = (int)gfx_width(), h = (int)gfx_height();
|
|
uint32 bg = vt_rgb(v->bg);
|
|
if (w > v->cols * cw)
|
|
gfx_fill_rect(v->cols * cw, 0, w - v->cols * cw, h, bg);
|
|
if (h > v->rows * ch)
|
|
gfx_fill_rect(0, v->rows * ch, w, h - v->rows * ch, bg);
|
|
}
|
|
|
|
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));
|
|
}
|
|
cursor_draw(v);
|
|
}
|
|
|
|
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--;
|
|
}
|
|
|
|
// --- the scrolling region ----------------------------------------------------
|
|
//
|
|
// tmux, htop, less and every curses program on TERM=linux scroll part of the
|
|
// screen: a status line stays put while the rest moves. Without a region the
|
|
// whole screen scrolled instead, and a pane's output smeared into its
|
|
// neighbours and over the status bar.
|
|
|
|
static int
|
|
region_bot(struct vt *v)
|
|
{
|
|
if (v->scroll_bot > v->scroll_top && v->scroll_bot < v->rows)
|
|
return v->scroll_bot;
|
|
return v->rows - 1;
|
|
}
|
|
|
|
static int
|
|
region_top(struct vt *v)
|
|
{
|
|
return v->scroll_top < region_bot(v) ? v->scroll_top : 0;
|
|
}
|
|
|
|
static void
|
|
draw_rows(struct vt *v, int r0, int r1)
|
|
{
|
|
if (!is_active(v))
|
|
return;
|
|
for (int r = r0; r <= r1; r++)
|
|
for (int c = 0; c < v->cols; c++)
|
|
show(v, c, r);
|
|
}
|
|
|
|
// Rows top..bot move up n; the n exposed at the bottom are blank.
|
|
static void
|
|
region_up(struct vt *v, int top, int bot, int n)
|
|
{
|
|
if (n <= 0 || top > bot)
|
|
return;
|
|
if (n > bot - top + 1)
|
|
n = bot - top + 1;
|
|
if (top == 0 && bot == v->rows - 1) {
|
|
// The whole screen: the framebuffer can be moved instead of redrawn.
|
|
while (n-- > 0) {
|
|
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));
|
|
}
|
|
return;
|
|
}
|
|
memmove(cell_at(v, 0, top), cell_at(v, 0, top + n),
|
|
(uint64)(bot - top + 1 - n) * v->cols * sizeof(struct vtcell));
|
|
for (int r = bot - n + 1; r <= bot; r++)
|
|
blank_row(v, r);
|
|
draw_rows(v, top, bot);
|
|
}
|
|
|
|
// Rows top..bot move down n; the n exposed at the top are blank.
|
|
static void
|
|
region_down(struct vt *v, int top, int bot, int n)
|
|
{
|
|
if (n <= 0 || top > bot)
|
|
return;
|
|
if (n > bot - top + 1)
|
|
n = bot - top + 1;
|
|
memmove(cell_at(v, 0, top + n), cell_at(v, 0, top),
|
|
(uint64)(bot - top + 1 - n) * v->cols * sizeof(struct vtcell));
|
|
for (int r = top; r < top + n; r++)
|
|
blank_row(v, r);
|
|
draw_rows(v, top, bot);
|
|
}
|
|
|
|
// Down a line, scrolling the region when the cursor is on its last row. Below
|
|
// the region the cursor only moves, and stops at the bottom of the screen.
|
|
static void
|
|
linefeed(struct vt *v)
|
|
{
|
|
if (v->cy == region_bot(v))
|
|
region_up(v, region_top(v), region_bot(v), 1);
|
|
else if (v->cy < v->rows - 1)
|
|
v->cy++;
|
|
}
|
|
|
|
// Up a line, the reverse: the region scrolls down from its first row.
|
|
static void
|
|
reverse_index(struct vt *v)
|
|
{
|
|
if (v->cy == region_top(v))
|
|
region_down(v, region_top(v), region_bot(v), 1);
|
|
else 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;
|
|
linefeed(v);
|
|
}
|
|
|
|
void
|
|
vt_putc(struct vt *v, int glyph)
|
|
{
|
|
switch (glyph) {
|
|
// LF is a line feed: down one row, same column. The carriage return a
|
|
// shell expects with it is ONLCR's, so it happens only while the terminal
|
|
// asks for it. Doing it always broke every program that turns OPOST off and
|
|
// moves down with a bare LF -- tmux does, row after row, and each row of a
|
|
// right-hand pane landed in column 0, where its ESC [ K erased the left one.
|
|
case '\n':
|
|
v->wrap_pending = 0;
|
|
if ((v->tio.c_oflag & (OPOST | ONLCR)) == (OPOST | ONLCR))
|
|
v->cx = 0;
|
|
linefeed(v);
|
|
break;
|
|
|
|
case '\r':
|
|
v->wrap_pending = 0;
|
|
v->cx = 0;
|
|
break;
|
|
|
|
// Backspace moves the cursor. It does **not** erase.
|
|
//
|
|
// This blanked the cell it moved onto, which is what a line-discipline erase
|
|
// does, not what the BS control character does. A program that wants to rub
|
|
// a character out sends `\b \b` -- backspace, space, backspace -- and the
|
|
// separation matters because programs also use plain `\b` simply to go back.
|
|
//
|
|
// zsh does, on every edit of a line you have already typed. Inserting a
|
|
// character in the middle, it sends:
|
|
//
|
|
// echo alpha \b\b zha \b\b
|
|
//
|
|
// -- move back two, rewrite the tail, then move back two again to leave the
|
|
// cursor where the user expects it. Against a destructive backspace that
|
|
// last pair deleted the `ha` it had just drawn, so the line appeared to lose
|
|
// its tail while the buffer was perfectly correct. Which is exactly how it
|
|
// looked: type in the middle of a line and the end of it vanishes.
|
|
case '\b':
|
|
v->wrap_pending = 0;
|
|
if (v->cx > 0)
|
|
v->cx--;
|
|
break;
|
|
|
|
// DEL is ignored, as it is on a real terminal. It is not a movement and it
|
|
// is not an erase; it exists to be discarded on a paper tape.
|
|
case 0x7f:
|
|
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;
|
|
// No upper bound any more. The cell holds a codepoint, so a character the
|
|
// font may not have is stored as itself and the font decides -- which is
|
|
// the difference between a terminal that cannot show Ą and one whose font
|
|
// happens not to. It used to be clamped to 255 and replaced with '?' here,
|
|
// before any font was ever consulted.
|
|
if (glyph < 0)
|
|
return;
|
|
wrap_now(v); // the previous glyph filled the line
|
|
struct vtcell *k = cell_at(v, v->cx, v->cy);
|
|
k->ch = (uint32)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.
|
|
//
|
|
// The grid is updated cell by cell -- it has to be -- but the *drawing* is
|
|
// done as rectangles, one per row span. Blanking a cell through the glyph
|
|
// path writes 128 pixels through the generic pixel routine; blanking a whole
|
|
// row as one fill writes the same pixels with the bounds checks hoisted out,
|
|
// and the console erases to end of screen on every shell prompt. At 160x50
|
|
// that is the difference between a million checked pixel writes per prompt and
|
|
// fifty row fills.
|
|
static void
|
|
erase_run(struct vt *v, int col, int row, int count)
|
|
{
|
|
int active = is_active(v);
|
|
|
|
while (count > 0 && row < v->rows) {
|
|
int n = v->cols - col; // to the end of this row
|
|
if (n > count)
|
|
n = count;
|
|
|
|
// Only draw what actually changes.
|
|
//
|
|
// This is the whole reason a terminal keeps a grid. `ESC[J` -- erase to
|
|
// end of screen -- arrives after every shell prompt, and almost every
|
|
// time the screen below the cursor is *already* blank in exactly the
|
|
// colours it is being asked to blank to. Drawing it anyway means writing
|
|
// four megabytes to a framebuffer that runs at about 128 MB/s, which is
|
|
// 32 ms of nothing, on every prompt, and zsh redraws the prompt on every
|
|
// keystroke.
|
|
//
|
|
// The grid already knows. Comparing is free; writing is not.
|
|
int dirty = 0;
|
|
for (int i = 0; i < n; i++) {
|
|
struct vtcell *k = cell_at(v, col + i, row);
|
|
if (k->ch != ' ' || k->fg != v->fg || k->bg != v->bg || k->attr != 0)
|
|
dirty = 1;
|
|
k->ch = ' ';
|
|
k->fg = v->fg;
|
|
k->bg = v->bg;
|
|
k->attr = 0;
|
|
}
|
|
if (active && dirty)
|
|
gfx_fill_rect(col * gfx_char_w(), row * gfx_char_h(),
|
|
n * gfx_char_w(), gfx_char_h(), vt_rgb(v->bg));
|
|
|
|
count -= n;
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Insert and delete characters within a line: ESC[n@ and ESC[nP.
|
|
//
|
|
// These were missing, and what was missing with them was **editing a line you
|
|
// have already typed**. `TERM=linux` declares `ich1=\E[@` and `dch1=\E[P`, so a
|
|
// line editor that inserts a character in the middle sends one of these and
|
|
// then writes the character -- and against a terminal that silently drops the
|
|
// escape, the text after the cursor is overwritten instead of shifted. What
|
|
// that looks like is the end of the line disappearing as you type into it,
|
|
// with the *buffer* perfectly correct, which is a maddening pair of symptoms
|
|
// because the program is doing nothing wrong.
|
|
//
|
|
// The row is shifted in the grid and then repainted from the cursor to the end
|
|
// of the line. No attempt at a hardware scroll: a line is at most a couple of
|
|
// hundred cells and this happens once per keystroke.
|
|
static void
|
|
vt_shift_line(struct vt *v, int n, int insert)
|
|
{
|
|
int row = v->cy, start = v->cx, width = v->cols;
|
|
int i;
|
|
|
|
if (n < 1)
|
|
n = 1;
|
|
if (n > width - start)
|
|
n = width - start;
|
|
|
|
if (insert) {
|
|
for (i = width - 1; i >= start + n; i--)
|
|
*cell_at(v, i, row) = *cell_at(v, i - n, row);
|
|
for (i = start; i < start + n; i++) {
|
|
struct vtcell *k = cell_at(v, i, row);
|
|
k->ch = ' '; k->fg = v->fg; k->bg = v->bg; k->attr = 0;
|
|
}
|
|
} else {
|
|
for (i = start; i < width - n; i++)
|
|
*cell_at(v, i, row) = *cell_at(v, i + n, row);
|
|
for (i = width - n; i < width; i++) {
|
|
struct vtcell *k = cell_at(v, i, row);
|
|
k->ch = ' '; k->fg = v->fg; k->bg = v->bg; k->attr = 0;
|
|
}
|
|
}
|
|
|
|
for (i = start; i < width; i++)
|
|
show(v, i, row);
|
|
}
|
|
|
|
// ESC[nX -- erase n characters where they stand, without moving anything.
|
|
static void
|
|
vt_erase_chars(struct vt *v, int n)
|
|
{
|
|
if (n < 1)
|
|
n = 1;
|
|
if (n > v->cols - v->cx)
|
|
n = v->cols - v->cx;
|
|
erase_run(v, v->cx, v->cy, n);
|
|
}
|
|
|
|
void
|
|
vt_clear(struct vt *v)
|
|
{
|
|
int dirty = 0;
|
|
|
|
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->ch != ' ' || k->fg != v->fg || k->bg != v->bg || k->attr != 0) {
|
|
dirty = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (dirty)
|
|
break;
|
|
}
|
|
for (int r = 0; r < v->rows; r++)
|
|
blank_row(v, r);
|
|
v->cx = v->cy = 0;
|
|
v->wrap_pending = 0;
|
|
|
|
// A screen fill, not a repaint. vt_repaint draws every cell through the
|
|
// glyph path -- eight thousand of them -- to produce a screen that is
|
|
// entirely one colour.
|
|
if (is_active(v) && dirty)
|
|
gfx_clear(vt_rgb(v->bg));
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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 '@': vt_shift_line(v, n1, 1); break; // ICH -- insert characters
|
|
case 'P': vt_shift_line(v, n1, 0); break; // DCH -- delete characters
|
|
case 'X': vt_erase_chars(v, n1); break; // ECH -- erase characters
|
|
|
|
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;
|
|
|
|
// DECSTBM: the scrolling region, 1-based and inclusive, and the cursor home.
|
|
case 'r': {
|
|
int t = v->esc_nparm > 0 && v->esc_parm[0] ? v->esc_parm[0] : 1;
|
|
int b = v->esc_nparm > 1 && v->esc_parm[1] ? v->esc_parm[1] : v->rows;
|
|
if (b > v->rows) b = v->rows;
|
|
if (t < b) {
|
|
v->scroll_top = t - 1;
|
|
v->scroll_bot = b - 1;
|
|
} else {
|
|
v->scroll_top = v->scroll_bot = 0;
|
|
}
|
|
vt_cursor_set(v, 0, 0);
|
|
break;
|
|
}
|
|
|
|
// Insert and delete lines at the cursor, within the region: what a curses
|
|
// program sends to open or close a gap rather than repaint everything below.
|
|
case 'L':
|
|
if (row >= region_top(v) && row <= region_bot(v))
|
|
region_down(v, row, region_bot(v), n1);
|
|
v->wrap_pending = 0;
|
|
break;
|
|
case 'M':
|
|
if (row >= region_top(v) && row <= region_bot(v))
|
|
region_up(v, row, region_bot(v), n1);
|
|
v->wrap_pending = 0;
|
|
break;
|
|
|
|
// Scroll the region without moving the cursor.
|
|
case 'S': region_up(v, region_top(v), region_bot(v), n1); break;
|
|
case 'T': region_down(v, region_top(v), region_bot(v), n1); 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.
|
|
static void
|
|
vt_feed_1(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; v->esc_qmark = 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;
|
|
switch (c) {
|
|
case '7': // DECSC
|
|
v->saved_cx = v->cx;
|
|
v->saved_cy = v->cy;
|
|
break;
|
|
case '8': // DECRC
|
|
vt_cursor_set(v, v->saved_cx, v->saved_cy);
|
|
break;
|
|
case 'M': // RI: up, scrolling back
|
|
v->wrap_pending = 0;
|
|
reverse_index(v);
|
|
break;
|
|
case 'D': // IND: down, scrolling
|
|
v->wrap_pending = 0;
|
|
linefeed(v);
|
|
break;
|
|
case 'E': // NEL: CR and IND
|
|
v->wrap_pending = 0;
|
|
v->cx = 0;
|
|
linefeed(v);
|
|
break;
|
|
case '(': case ')': case '*': case '+':
|
|
// A character set designation. The set itself is not offered, but the
|
|
// byte naming it must be swallowed: ESC ( B printed a stray "B".
|
|
v->esc_state = VE_CHARSET;
|
|
break;
|
|
}
|
|
return;
|
|
|
|
case VE_CHARSET:
|
|
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:
|
|
// Private markers (< = > ?) and intermediates (space to /): sequences
|
|
// this terminal does not speak, but must still consume to the final byte.
|
|
// Ending at the marker instead printed the rest -- tmux asking for
|
|
// extended keys as ESC [ > 4 ; 1 m left "4;1m" on the screen.
|
|
if (c >= '<' && c <= '?') {
|
|
v->esc_private = 1;
|
|
if (c == '?') v->esc_qmark = 1;
|
|
return;
|
|
}
|
|
if (c >= 0x20 && c <= 0x2F) { 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);
|
|
} else if (v->esc_qmark && (c == 'h' || c == 'l')) {
|
|
// DECSET/DECRST. Only the cursor's visibility is ours; the rest of
|
|
// ?1049 and its kin are still ignored, as before.
|
|
for (int i = 0; i < v->esc_nparm && i < VT_MAXPARM; i++)
|
|
if (v->esc_parm[i] == 25)
|
|
v->curs_hidden = (c == 'l');
|
|
}
|
|
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;
|
|
// The codepoint itself, not a CP437 approximation of it. glyph_for() is
|
|
// still here for the bitmap fallback, but the grid no longer needs it:
|
|
// what reaches the font is what the program sent.
|
|
vt_putc(v, (int)v->u8_cp);
|
|
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
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// A terminal whose VT_PROCESS owner is gone goes back to the kernel, whole:
|
|
// switching, drawing and the keyboard. Otherwise a display server that
|
|
// crashed would leave the screen frozen on its last frame, the keyboard
|
|
// deaf and the chord ignored, with no way back short of a reset.
|
|
static int
|
|
owner_alive(struct vt *v)
|
|
{
|
|
if (v->mode != VT_PROCESS)
|
|
return 0;
|
|
if (v->owner && proc_exists(v->owner))
|
|
return 1;
|
|
v->mode = VT_AUTO;
|
|
v->owner = 0;
|
|
v->leaving_to = -1;
|
|
v->kbmode = K_UNICODE;
|
|
if (v->kdmode == KD_GRAPHICS) {
|
|
v->kdmode = KD_TEXT;
|
|
if (v->index == vt_active) {
|
|
vt_repaint(v);
|
|
gfx_flush();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
switch_now(int i)
|
|
{
|
|
struct vt *to = &vts[i];
|
|
|
|
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. A terminal in graphics mode is
|
|
// not repainted -- its owner is told, and draws its own.
|
|
if (to->kdmode != KD_GRAPHICS) {
|
|
vt_repaint(to);
|
|
gfx_flush();
|
|
}
|
|
if (owner_alive(to) && to->acqsig)
|
|
sigsend(to->owner, to->acqsig);
|
|
}
|
|
|
|
// Switch terminals.
|
|
//
|
|
// Away from one in VT_PROCESS, the switch is a request: its owner is sent
|
|
// relsig and the switch waits for VT_RELDISP. That is how a display server
|
|
// learns it is about to lose the screen -- it stops drawing, and only then
|
|
// says yes. Switching without asking left it drawing straight over the text
|
|
// console it had been switched away from. Back to such a terminal, the
|
|
// switch happens and the owner is sent acqsig, to redraw.
|
|
int
|
|
vt_switch(int i)
|
|
{
|
|
if (i < 0 || i >= NVT)
|
|
return -1;
|
|
if (!vt_ready)
|
|
return -1;
|
|
if (i == vt_active)
|
|
return 0;
|
|
|
|
struct vt *from = &vts[vt_active];
|
|
if (owner_alive(from) && from->relsig) {
|
|
from->leaving_to = i;
|
|
if (sigsend(from->owner, from->relsig) == 0)
|
|
return 0;
|
|
owner_alive(from); // died in between; fall through
|
|
}
|
|
switch_now(i);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
vt_reldisp(struct vt *v, int arg)
|
|
{
|
|
if (arg == VT_ACKACQ)
|
|
return 0;
|
|
if (v->mode != VT_PROCESS || v->leaving_to < 0)
|
|
return -EINVAL;
|
|
int to = v->leaving_to;
|
|
v->leaving_to = -1;
|
|
if (arg && v->index == vt_active)
|
|
switch_now(to);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
vt_set_process(struct vt *v, int mode, int relsig, int acqsig)
|
|
{
|
|
v->mode = mode;
|
|
v->leaving_to = -1;
|
|
if (mode == VT_PROCESS) {
|
|
v->owner = myproc() ? myproc()->pid : 0;
|
|
v->relsig = relsig > 0 && relsig < 32 ? relsig : 0;
|
|
v->acqsig = acqsig > 0 && acqsig < 32 ? acqsig : 0;
|
|
} else {
|
|
v->owner = v->relsig = v->acqsig = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
vt_set_kdmode(struct vt *v, int mode)
|
|
{
|
|
int was = v->kdmode;
|
|
v->kdmode = mode;
|
|
// Back to text on the terminal on screen: the grid has been kept current
|
|
// underneath the whole time, so putting it back is a repaint.
|
|
if (was == KD_GRAPHICS && mode == KD_TEXT && v->index == vt_active) {
|
|
vt_repaint(v);
|
|
gfx_flush();
|
|
}
|
|
}
|
|
|
|
int
|
|
vt_takes_keys(struct vt *v)
|
|
{
|
|
if (v->kbmode == K_XLATE || v->kbmode == K_UNICODE)
|
|
return 1;
|
|
// A raw or K_OFF terminal whose owner died gets its keyboard back.
|
|
return v->mode == VT_PROCESS && !owner_alive(v);
|
|
}
|
|
|
|
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 !owner_alive(vt_current());
|
|
}
|
|
|
|
// Step to the previous or next terminal that is actually in use.
|
|
//
|
|
// Skipping the idle ones matters: six terminals with two shells on them means
|
|
// four blank screens between the things you wanted, and cycling past them is
|
|
// worse than not having the key. "In use" is `opened`, the same count
|
|
// VT_GETSTATE reports, so what the key does and what `chvt` says are the same
|
|
// fact.
|
|
//
|
|
// If nothing else is open this stays put rather than wrapping to a blank one.
|
|
int
|
|
vt_cycle(int dir)
|
|
{
|
|
if (!vt_ready)
|
|
return -1;
|
|
|
|
for (int step = 1; step <= NVT; step++) {
|
|
int i = vt_active + dir * step;
|
|
while (i < 0) i += NVT;
|
|
while (i >= NVT) i -= NVT;
|
|
if (i == vt_active)
|
|
break;
|
|
if (vts[i].opened > 0)
|
|
return vt_switch(i);
|
|
}
|
|
return 0; // nowhere else to go; not an error
|
|
}
|
|
|
|
void
|
|
vt_feed(struct vt *v, int c)
|
|
{
|
|
// A plain glyph is drawn over the cell the cursor sits on, which repaints it
|
|
// anyway (unless a wrap is pending, when it lands on the next line), so the
|
|
// cursor needs no separate erase there.
|
|
int mid = v->esc_state != VE_NORMAL;
|
|
int over = !mid && !v->wrap_pending && c >= 0x20 && c < 0x7f &&
|
|
v->u8_need == 0;
|
|
|
|
if (over) {
|
|
vt_feed_1(v, c);
|
|
v->curs_drawn = 0;
|
|
cursor_draw(v);
|
|
return;
|
|
}
|
|
|
|
// Anything else may move the cursor, and a line feed at the bottom scrolls
|
|
// the *pixels*, block and all. So the cursor comes off first. Erased after
|
|
// the scroll, it was repainted where the cell had been and the old block
|
|
// went up a row with the text: a white block at the end of every line of a
|
|
// verbose boot.
|
|
cursor_erase(v);
|
|
vt_feed_1(v, c);
|
|
cursor_draw(v);
|
|
}
|