Alt+F1..F6 was wrong in three separate ways, and the third is the one that
matters most here.
Alt+F4 the host window manager closes the QEMU window
Alt+F2 GNOME and KDE open a run dialog
Ctrl+Alt+F1..F6 the host switches its *own* virtual terminals
AltGr+anything Polish diacritics -- a c e l n o s x z with their marks --
and on some setups AltGr is delivered as Ctrl+Alt
Ctrl+Shift+key widely bound inside applications
Scroll Lock not present on many keyboards
Alt+1..6 survives all of it: the number row, so no function key is involved,
and left Alt, so AltGr is untouched.
Right Alt is tracked as its own modifier bit now. It used to share ALT, which
is harmless for typing -- neither is used to look up a character -- and wrong
for chords, because it made AltGr indistinguishable from Alt.
And the test is an equality rather than a mask:
if ((shift & (CTL | SHIFT | ALT | RALT)) == ALT && ...)
`shift & ALT` fired on Ctrl+Alt+1 and AltGr+1 as well, so a chord aimed at the
host -- or an ordinary Polish keystroke -- would have switched terminals
behind your back. That was a real bug in what shipped yesterday.
Verified with QEMU's sendkey, comparing screendump hashes:
start (tty1) 86cee2284878
Ctrl+Alt+2 86cee2284878 identical -- did not fire
AltGr+2 86cee2284878 identical -- did not fire
Alt+2 114562804a78 switched
Alt+1 86cee2284878 back, pixel-identical to the start
schfs-check passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
146 lines
5.6 KiB
C
146 lines
5.6 KiB
C
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// 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 "types.h"
|
|
#include "param.h"
|
|
#include "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/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 {
|
|
uint8 ch;
|
|
uint8 fg;
|
|
uint8 bg;
|
|
uint8 attr; // reserved: bold/underline/reverse, which do not change
|
|
}; // the glyph and so can be added without touching a grid
|
|
|
|
#define VT_FG_DEFAULT 16 // SCH_FG from include/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
|
|
};
|
|
|
|
// 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
|
|
|
|
// 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
|
|
|
|
#endif // VT_H
|