SchOS/include/vt.h
Nexoniarz 65906639da tty: AltGr+F1..F6 by default, and AltGr+-/+ to step
The sweet spot was available the whole time and I ruled it out by writing
"AltGr+anything" when the true statement is "AltGr+*letter*".

A Polish layout wants AltGr with a letter -- ą ć ę ł ń ó ś ź ż. AltGr with a
function key produces nothing on any layout, no window manager binds it, and
no application asks for it. It is the one combination the host, applications
and the keyboard layout all leave alone, and the previous three commits talked
themselves out of it on a technicality that was mine, not the hardware's.

Verified rather than argued, with QEMU sendkey and screendump hashes:

    AltGr+F2   -> tty2
    AltGr+F1   -> back, pixel-identical to the start
    AltGr+a    -> reaches zsh, so ą still types

That distinction is only expressible because Right Alt became its own modifier
bit two commits ago. Sharing ALT is harmless for typing -- neither is used to
look up a character -- and made this impossible to say.

Also added, since it was asked for in the same breath: minus and equals step
to the previous and next terminal, on whatever the chord's modifiers are. They
skip terminals that are not in use -- the same `opened` count VT_GETSTATE
reports -- because six terminals with two shells on them means four blank
screens in between, and cycling past those is worse than not having the key.

    AltGr+=  -> tty2, again -> wraps to tty1   (only two in use)
    AltGr+-  -> tty2

chvt -c still moves all of it, and `chvt` reports both halves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-13 13:22:01 +02:00

152 lines
5.9 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
// 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;
};
// 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
#endif // VT_H