tty: six virtual terminals, and the plumbing that makes them terminals
/dev/tty1..tty6, Alt+F1..F6 or `chvt N`. Each is an independent terminal --
its own screen, cursor, line discipline, foreground process group and
session. Two shells on two terminals are unrelated processes that share a
keyboard and a screen.
What made it 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 wants the screen: there was nowhere to put terminal 2 while
terminal 1 was visible, and nothing to repaint terminal 1 from. Each terminal
owns a grid of cells now and the framebuffer shows whichever grid is active,
so vt_switch() is set-index, repaint, return -- nothing is saved on the way
out because the outgoing grid has been current all along.
The rule the layer is built on: a terminal does not know whether it is
visible. Writes land in the grid, the cursor moves, the grid scrolls, all
unconditionally; drawing is one question asked at the bottom of vt.c. Every
alternative spreads "am I on screen" through the escape parser, and then a
terminal switched away from mid-sequence comes back wrong.
Everything that was a single global is per-terminal now: tty_fg_pgrp (deleted),
termios, the input ring, the escape parser, the UTF-8 decoder, and the cursor
-- which also stops being pixels in gfx.c and becomes cells in the terminal.
TIOCSCTTY was `return 0` and now records proc->ctty, which is what /dev/tty
resolves through; /dev/tty was an alias for /dev/console with the same minor,
and they are different questions.
kbd.c names Alt+F1..F6 as KEY_ALT_F1+n, above 0xFF on purpose -- every byte
value is already a character or a UTF-8 continuation, and a chord colliding
with one would be typed into the terminal instead of switching away from it.
What the chord means is decided in console.c, where the terminals are.
VT_ACTIVATE / VT_WAITACTIVE / VT_GETSTATE / VT_OPENQRY, Linux's numbers.
The argument to ACTIVATE is by value, not a pointer -- same shape as TCFLSH.
Two bugs found by screenshotting the result rather than reading the log:
- zsh emits 0x0F to leave alt-charset mode, because that is what the `linux`
terminfo says rmacs is, and the console drew CP437 glyph 0x0F: a sun, in
the prompt, on every line. A terminal never draws a C0 control character.
- SGR 7 was unimplemented, so zsh's partial-line marker rendered as a bare
'#' instead of a reversed block. Reverse video swaps the colours at draw
time, not at store time, so turning it off restores what was underneath.
Verified by screendump: tty1 and tty2 show different screens, and switching
back repaints tty1's earlier content from its grid. schfs-check passes.
docs/architecture/terminals.md, including what it does not do -- no
scrollback, no reflow, no VT_PROCESS mode for a display server to veto a
switch, which Schcom will need before it owns Alt+F7.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
parent
14f0e4d0e2
commit
1c1b0c9570
24 changed files with 1460 additions and 458 deletions
4
Makefile
4
Makefile
|
|
@ -25,7 +25,7 @@ KERN_CORE = $(addprefix $(BUILD)/kernel/core/, \
|
|||
|
||||
KERN_DEV = $(addprefix $(BUILD)/kernel/dev/, \
|
||||
bio.o cdev.o device.o disk.o fb.o ide.o input.o irq.o kbd.o mouse.o \
|
||||
pci.o text.o ttf.o uart.o gfx.o ramdisk.o procfs.o)
|
||||
pci.o text.o ttf.o uart.o gfx.o vt.o ramdisk.o procfs.o)
|
||||
|
||||
# Drivers for hardware that has to be *found* rather than assumed. Anything
|
||||
# discovered by walking a bus belongs here; the fixed-address legacy devices
|
||||
|
|
@ -107,7 +107,7 @@ KERN_NOENTRY = $(filter-out $(BUILD)/kernel/arch/x86_64/entry.o, $(KERNOBJS))
|
|||
# Third-party software does NOT go here — see ports/.
|
||||
# -----------------------------------------------------------------------
|
||||
ULIB = $(addprefix $(BUILD)/lib/, start.o glue.o usys.o)
|
||||
CMDS = init schinit fbinfo shmtest socktest inputtest nettest reflink schattr ping
|
||||
CMDS = init schinit fbinfo shmtest socktest inputtest nettest reflink schattr ping chvt
|
||||
CMDBINS = $(addprefix $(BUILD)/cmd/, $(CMDS))
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
|
|
|||
93
cmd/chvt/main.c
Normal file
93
cmd/chvt/main.c
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// chvt N -- show virtual terminal N.
|
||||
//
|
||||
// The same name and the same argument as the Linux tool, because it does the
|
||||
// same thing and nobody should have to learn a second one. Alt+F1..F6 does
|
||||
// this from the keyboard; this does it from a script, and from a serial
|
||||
// console where there are no function keys to press.
|
||||
//
|
||||
// chvt report which terminal is active and which are in use
|
||||
// chvt 2 switch to /dev/tty2
|
||||
|
||||
#include "types.h"
|
||||
#include "user.h"
|
||||
#include "fcntl.h"
|
||||
#include "posix.h"
|
||||
|
||||
// No printf in lib/, and this needs one string and two small numbers.
|
||||
static void
|
||||
puts(const char *s)
|
||||
{
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
write(1, (void *)s, n);
|
||||
}
|
||||
|
||||
static void
|
||||
putn(int v)
|
||||
{
|
||||
char b[12];
|
||||
int i = 0;
|
||||
if (v < 0) { write(1, "-", 1); v = -v; }
|
||||
if (v == 0) { write(1, "0", 1); return; }
|
||||
while (v > 0) { b[i++] = '0' + (v % 10); v /= 10; }
|
||||
while (i-- > 0) write(1, &b[i], 1);
|
||||
}
|
||||
|
||||
static int
|
||||
num(const char *s)
|
||||
{
|
||||
int v = 0;
|
||||
while (*s >= '0' && *s <= '9')
|
||||
v = v * 10 + (*s++ - '0');
|
||||
return v;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
// /dev/console rather than fd 0: the caller may have redirected its input,
|
||||
// and the terminal being asked about is the machine's, not whichever one
|
||||
// this process happens to be reading from.
|
||||
int fd = open("/dev/console", O_RDWR);
|
||||
if (fd < 0) {
|
||||
puts("chvt: cannot open /dev/console\n");
|
||||
exit();
|
||||
}
|
||||
|
||||
if (argc == 1) {
|
||||
struct vt_stat st;
|
||||
if (ioctl(fd, VT_GETSTATE, &st) < 0) {
|
||||
puts("chvt: VT_GETSTATE failed\n");
|
||||
exit();
|
||||
}
|
||||
puts("active: tty");
|
||||
putn(st.v_active);
|
||||
puts("\nin use:");
|
||||
for (int i = 1; i <= 16; i++)
|
||||
if (st.v_state & (1 << i)) {
|
||||
puts(" tty");
|
||||
putn(i);
|
||||
}
|
||||
puts("\n");
|
||||
exit();
|
||||
}
|
||||
|
||||
int n = num(argv[1]);
|
||||
if (n < 1) {
|
||||
puts("usage: chvt N\n");
|
||||
exit();
|
||||
}
|
||||
|
||||
// The argument goes by value, not through a pointer -- Linux defines
|
||||
// VT_ACTIVATE that way and so does SchOS. Passing a pointer here would
|
||||
// switch to whatever the address happened to be.
|
||||
if (ioctl(fd, VT_ACTIVATE, (void *)(long)n) < 0) {
|
||||
puts("chvt: no terminal ");
|
||||
putn(n);
|
||||
puts("\n");
|
||||
exit();
|
||||
}
|
||||
exit();
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
["architecture/syscalls.md", "Syscalls"],
|
||||
["architecture/filesystem.md", "The VFS"],
|
||||
["architecture/schfs.md", "SchFS"],
|
||||
["architecture/terminals.md", "Virtual terminals"],
|
||||
["architecture/devices.md", "Device model"],
|
||||
["architecture/usb.md", "USB"],
|
||||
["architecture/graphics.md", "Graphics"],
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ its distribution.
|
|||
| API | **SchAPI** | The native programming interface — *designed, not built* |
|
||||
| Compositor | **Schcom** | Owns the display; v0 runs |
|
||||
|
||||
Six virtual terminals sit under all of it -- `/dev/tty1`..`tty6`, Alt+F1..F6 --
|
||||
each with its own screen, line discipline and foreground process group. See
|
||||
[terminals.md](terminals.md).
|
||||
|
||||
---
|
||||
|
||||
## The stack, bottom to top
|
||||
|
|
|
|||
128
docs/architecture/terminals.md
Normal file
128
docs/architecture/terminals.md
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
||||
# Virtual terminals
|
||||
|
||||
Six of them, `/dev/tty1` through `/dev/tty6`, reached with **Alt+F1..Alt+F6**
|
||||
or with `chvt N`. Each one is an independent terminal: its own screen, its own
|
||||
cursor, its own line discipline, its own foreground process group and its own
|
||||
session. Two shells on two terminals are two unrelated processes that happen to
|
||||
share a keyboard and a screen.
|
||||
|
||||
Alt+F7 is deliberately unused. That is where a display server goes.
|
||||
|
||||
---
|
||||
|
||||
## What makes switching possible
|
||||
|
||||
A backing store, and nothing else.
|
||||
|
||||
Before this, the console drew straight into the framebuffer and scrolled by
|
||||
moving pixels. That 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 terminal owns a grid of cells and the framebuffer shows whichever grid
|
||||
is active. `vt_switch()` is three lines — set the active index, repaint from
|
||||
the grid, return — because nothing has to be *saved* on the way out. The
|
||||
outgoing terminal's grid has been current all along.
|
||||
|
||||
The rule the whole layer 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 of
|
||||
`kernel/dev/vt.c`, answered by comparing against `vt_active`. Every alternative
|
||||
spreads "am I on screen" through the escape parser, and then a terminal
|
||||
switched away from mid-sequence comes back wrong.
|
||||
|
||||
## A cell
|
||||
|
||||
Four bytes:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `ch` | the glyph the font draws — **not** a codepoint |
|
||||
| `fg`, `bg` | palette indices, 0-15 ANSI plus two theme defaults |
|
||||
| `attr` | reverse video; room for underline and the rest |
|
||||
|
||||
`ch` stores the glyph rather than the codepoint because the console maps UTF-8
|
||||
to CP437 before it gets here, and that is what `include/font.h` contains.
|
||||
Storing the codepoint would mean re-running the mapping on every repaint and
|
||||
getting a different answer for anything the font cannot draw.
|
||||
|
||||
The colours are indices rather than RGB because a cell holding two 32-bit
|
||||
colours would be twelve bytes instead of four, and the grids are static: six
|
||||
terminals at 240x75 is 432 KB of BSS, paid once at link time rather than failed
|
||||
to pay at run time. There is no contiguous multi-page allocator to ask.
|
||||
|
||||
## What is per-terminal
|
||||
|
||||
Everything that used to be a single global:
|
||||
|
||||
| Was | Now |
|
||||
|---|---|
|
||||
| `int tty_fg_pgrp = 1;` | `v->fg_pgrp`, one per terminal |
|
||||
| one `struct termios tio` | `v->tio` |
|
||||
| one 256-byte input ring | `v->in` |
|
||||
| one escape-parser state | `v->esc_*` |
|
||||
| one UTF-8 decoder | `v->u8_*` |
|
||||
| one cursor, in pixels, in `gfx.c` | `v->cx`, `v->cy`, in cells |
|
||||
|
||||
`TIOCSCTTY` used to be `return 0` — it claimed to make a terminal the caller's
|
||||
controlling terminal and did nothing, because there was one terminal and
|
||||
nothing to choose between. It records `proc->ctty` now, which is what
|
||||
`/dev/tty` resolves through.
|
||||
|
||||
## The devices
|
||||
|
||||
| Path | Minor | What |
|
||||
|---|---|---|
|
||||
| `/dev/console` | 1 | the **active** terminal, so kernel messages follow a switch |
|
||||
| `/dev/tty` | 10 | the caller's **controlling** terminal, via `proc->ctty` |
|
||||
| `/dev/tty1`..`tty6` | 11-16 | that terminal, whether or not it is on screen |
|
||||
|
||||
`/dev/tty` was an alias for `/dev/console` with the same major and minor. They
|
||||
are different questions and now have different answers.
|
||||
|
||||
## The ioctls
|
||||
|
||||
Linux's numbers, because they do Linux's job.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `VT_ACTIVATE` | switch. Argument is a 1-based number **by value**, not a pointer |
|
||||
| `VT_WAITACTIVE` | returns immediately: `vt_switch` repaints synchronously, so there is nothing left to wait for |
|
||||
| `VT_GETSTATE` | active terminal and a bitmask of those in use |
|
||||
| `VT_OPENQRY` | the first terminal nobody has open |
|
||||
| `VT_GETMODE`/`VT_SETMODE` | accepted and ignored — the kernel drives switching and there is no process mode to set |
|
||||
|
||||
## The keyboard
|
||||
|
||||
`kbd.c` recognises Alt+F1..F6 and returns `KEY_ALT_F1 + n`, which is above
|
||||
0xFF on purpose: every value that fits in a byte is already either a character
|
||||
or a UTF-8 continuation byte, and a chord that collided with one would be typed
|
||||
into the terminal instead of switching away from it.
|
||||
|
||||
What the chord *means* is decided in `console.c`, where the terminals are. The
|
||||
keyboard driver names it and stops.
|
||||
|
||||
Input goes to whichever terminal is on screen. That is what makes a terminal a
|
||||
terminal: the keyboard follows the eyes.
|
||||
|
||||
## What it does not do
|
||||
|
||||
- **No scrollback.** The grid is exactly the visible screen. Shift+PageUp does
|
||||
nothing because there is nothing above the top row to show.
|
||||
- **No reflow on resize.** The geometry is read once at `vt_init()`. Nothing
|
||||
resizes a framebuffer console today; a compositor will, and that is when this
|
||||
has to grow.
|
||||
- **No process-mode switching.** `VT_SETMODE` is accepted and ignored. A
|
||||
display server that wants to veto a switch — which is what `VT_PROCESS` is
|
||||
for — cannot yet, and will need to before Schcom owns Alt+F7.
|
||||
- **No terminal past 6.** Linux allows 63. Six is what the chords reach and
|
||||
what the grids are sized for.
|
||||
- **Only 240x75 of any screen.** Larger framebuffers work; the grid is clamped
|
||||
and the remainder stays background. Raising `VT_MAXCOLS`/`VT_MAXROWS` is the
|
||||
fix, and costs BSS.
|
||||
- **No UTF-8 the font lacks.** CP437 has ~256 glyphs. Anything else draws as
|
||||
`?`, which is deliberate: drawing a *different* character would be worse.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# The interactive shell on the system console.
|
||||
# The interactive shell on /dev/tty1, the first virtual terminal.
|
||||
|
||||
Label = os.schos.console-shell
|
||||
# zsh, not /bin/sh. mksh stays as /bin/sh and is what schinit falls back to
|
||||
|
|
@ -23,9 +23,9 @@ Environment = SHELL=/bin/zsh
|
|||
# terminal input at all. Without this the shell echoes and executes nothing.
|
||||
ControllingTerminal = yes
|
||||
|
||||
StandardIn = /dev/console
|
||||
StandardOut = /dev/console
|
||||
StandardError = /dev/console
|
||||
StandardIn = /dev/tty1
|
||||
StandardOut = /dev/tty1
|
||||
StandardError = /dev/tty1
|
||||
|
||||
# Seconds between respawns. Guards against a shell that dies instantly
|
||||
# defaults to 10. One is enough for a shell someone just exited on purpose.
|
||||
|
|
|
|||
25
etc/schinit/tty2-shell.job
Normal file
25
etc/schinit/tty2-shell.job
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# A second interactive shell, on /dev/tty2.
|
||||
#
|
||||
# This is what virtual terminals are for. Alt+F2 shows it; Alt+F1 comes back.
|
||||
# The two shells are unrelated processes with their own sessions, their own
|
||||
# foreground process groups and their own line discipline -- which is exactly
|
||||
# what /dev/tty1 and /dev/tty2 being different terminals means.
|
||||
|
||||
Label = os.schos.tty2-shell
|
||||
Program = /bin/zsh
|
||||
Args = -zsh
|
||||
RunAtLoad = yes
|
||||
KeepAlive = yes
|
||||
|
||||
Environment = TERM=linux
|
||||
Environment = PATH=/bin:/usr/bin
|
||||
Environment = HOME=/root
|
||||
Environment = SHELL=/bin/zsh
|
||||
|
||||
ControllingTerminal = yes
|
||||
|
||||
StandardIn = /dev/tty2
|
||||
StandardOut = /dev/tty2
|
||||
StandardError = /dev/tty2
|
||||
|
||||
ThrottleInterval = 1
|
||||
|
|
@ -109,5 +109,8 @@ zstyle ':completion:*:descriptions' format '%F{244}%d%f'
|
|||
# the only thing here that ever turns red -- same rule as the rest of the
|
||||
# system: normal states are calm, only failure is loud.
|
||||
setopt PROMPT_SUBST
|
||||
PROMPT='%F{magenta}%B%n@schos%b%f %F{cyan}%~%f %(?.%F{green}❯.%F{red}❯)%f '
|
||||
# The chevron is », not ❯. The console font is CP437 and has a glyph for
|
||||
# the first and none for the second, so ❯ renders as '?' -- correctly, since
|
||||
# drawing a *different* character would be worse, but it still looks broken.
|
||||
PROMPT='%F{magenta}%B%n@schos%b%f %F{cyan}%~%f %(?.%F{green}».%F{red}»)%f '
|
||||
RPROMPT=''
|
||||
|
|
|
|||
|
|
@ -35,13 +35,16 @@ int bflush(uint dev);
|
|||
// console.c
|
||||
struct termios;
|
||||
void consoleinit(void);
|
||||
extern int tty_fg_pgrp;
|
||||
// The terminal's line-discipline settings. TCGETS/TCSETS in sys_fs.c are the
|
||||
// only callers; the console honours ICANON, ECHO, ECHOE, ECHONL, ISIG,
|
||||
// ICRNL, INLCR, IGNCR, ISTRIP and VMIN.
|
||||
void cons_get_termios(struct termios *);
|
||||
void cons_set_termios(const struct termios *);
|
||||
void cons_flush_input(void);
|
||||
void cons_get_termios(int minor, struct termios *);
|
||||
void cons_set_termios(int minor, const struct termios *);
|
||||
void cons_flush_input(int minor);
|
||||
int cons_fg_pgrp(int minor);
|
||||
void cons_set_fg_pgrp(int minor, int pg);
|
||||
int console_vt_hotkey(int key);
|
||||
struct vt *vt_for_minor(int minor);
|
||||
extern uint lapic_timer_hz; // measured, kernel/arch/x86_64/lapic.c
|
||||
extern uint64 tsc_hz;
|
||||
void cprintf(char*, ...);
|
||||
|
|
@ -619,7 +622,7 @@ pml4e_t* copyuvm(pml4e_t*, uint64);
|
|||
void switchuvm(struct proc*);
|
||||
void switchkvm(void);
|
||||
int pipe_poll(struct pipe*, int readable, int writable);
|
||||
int console_input_ready(void);
|
||||
int console_input_ready(int minor);
|
||||
int copyin(pml4e_t*, addr_t, void*, uint64);
|
||||
int copyout(pml4e_t*, addr_t, void*, uint64);
|
||||
void vm_unmap_range(pml4e_t*, uint64, uint64);
|
||||
|
|
|
|||
|
|
@ -72,6 +72,11 @@ void gfx_set_bg(uint32 color);
|
|||
void gfx_screen_reset(void);
|
||||
int gfx_cols(void);
|
||||
int gfx_rows(void);
|
||||
|
||||
// For the text layer in kernel/dev/vt.c: one cell with its own colours, and
|
||||
// a screen scroll that leaves the cursor alone.
|
||||
void gfx_draw_cell(int col, int row, uchar glyph, uint32 fg, uint32 bg);
|
||||
void gfx_scroll_up(uint32 bg);
|
||||
void gfx_cursor_get(int *col, int *row);
|
||||
void gfx_cursor_set(int col, int row);
|
||||
void gfx_erase_display(int mode);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,15 @@
|
|||
#define KEY_INS 0xE8
|
||||
#define KEY_DEL 0xE9
|
||||
|
||||
// Alt+F1..Alt+F6 -- switch virtual terminal.
|
||||
//
|
||||
// Above 0xFF deliberately. kbdgetc returns an int and consoleintr takes one,
|
||||
// and every value that fits in a byte is already either a character or a
|
||||
// UTF-8 continuation byte. A chord that collided with one of those would be
|
||||
// typed into the terminal instead of switching away from it.
|
||||
#define KEY_ALT_F1 0x120
|
||||
#define SCAN_F1 0x3B // F1..F6 are 0x3B..0x40
|
||||
|
||||
// C('A') == Control-A
|
||||
#define C(x) (x - '@')
|
||||
|
||||
|
|
|
|||
|
|
@ -123,6 +123,21 @@ struct rlimit {
|
|||
#define TIOCGWINSZ 0x5413
|
||||
#define TIOCSWINSZ 0x5414
|
||||
#define TIOCSCTTY 0x540E
|
||||
|
||||
// Virtual terminal control, Linux's numbers. The argument to VT_ACTIVATE and
|
||||
// VT_WAITACTIVE is a 1-based terminal number passed by value, not a pointer.
|
||||
#define VT_OPENQRY 0x5600 // first free terminal, out through a pointer
|
||||
#define VT_GETMODE 0x5601
|
||||
#define VT_SETMODE 0x5602
|
||||
#define VT_GETSTATE 0x5603 // struct vt_stat
|
||||
#define VT_ACTIVATE 0x5606
|
||||
#define VT_WAITACTIVE 0x5607
|
||||
|
||||
struct vt_stat {
|
||||
uint16 v_active; // active terminal, 1-based
|
||||
uint16 v_signal; // unused; Linux keeps it for VT_SETMODE
|
||||
uint16 v_state; // bitmask of terminals in use, bit 1 = tty1
|
||||
};
|
||||
#define TIOCGSERIAL 0x541E
|
||||
#define FIONREAD 0x541B
|
||||
// FICLONE: share another file's data blocks with this one. The argument is a
|
||||
|
|
|
|||
|
|
@ -245,6 +245,10 @@ struct proc {
|
|||
// which is why mksh's job control never made progress.
|
||||
int pgid;
|
||||
int sid;
|
||||
// Controlling terminal, 1-based (1 = /dev/tty1), 0 = none. TIOCSCTTY sets
|
||||
// it; /dev/tty resolves through it. Was impossible to have before there was
|
||||
// more than one terminal to choose between.
|
||||
int ctty;
|
||||
|
||||
// Real/effective/saved user and group ids. Nothing enforces permissions with
|
||||
// them yet (the on-disk format has no owner field until SchFS), but they
|
||||
|
|
|
|||
139
include/vt.h
Normal file
139
include/vt.h
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
// 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+F1..Alt+F6. Alt+F7 is deliberately left alone -- 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
|
||||
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);
|
||||
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
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
#include <stdarg.h>
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "vt.h"
|
||||
#include "kbd.h" /* KEY_ALT_F1 -- the terminal-switch chords */
|
||||
#include "gfx.h"
|
||||
#include "theme.h"
|
||||
#include "irq.h"
|
||||
|
|
@ -44,7 +46,6 @@ extern void gfx_screen_reset(void);
|
|||
// This was initialised to 7, a leftover from when it was a VGA text-mode
|
||||
// attribute byte. gfx_putc takes a 32-bit RGB value, so 7 meant "very dark
|
||||
// blue" -- it looked black against the cream background purely by accident.
|
||||
static uint32 cons_fg = SCH_FG;
|
||||
|
||||
static struct {
|
||||
struct spinlock lock;
|
||||
|
|
@ -170,12 +171,6 @@ console_takeover(void)
|
|||
gfx_screen_reset();
|
||||
}
|
||||
|
||||
// Foreground process group of the console, i.e. the controlling terminal.
|
||||
// tcgetpgrp()/tcsetpgrp() read and write this through TIOCGPGRP/TIOCSPGRP in
|
||||
// sys_fs.c. Initialised to 1 -- init's process group -- so the first shell,
|
||||
// which inherits that group, already sees itself as the foreground job and
|
||||
// does not immediately stop itself with SIGTTIN.
|
||||
int tty_fg_pgrp = 1;
|
||||
|
||||
#define BACKSPACE 0x100
|
||||
|
||||
|
|
@ -193,296 +188,9 @@ int tty_fg_pgrp = 1;
|
|||
// only for the framebuffer. Two consumers, two right answers.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// The sixteen ANSI colours as RGB, for a linear framebuffer.
|
||||
//
|
||||
// consoleinit() used to program these into the VGA DAC with set_vga_palette(),
|
||||
// which does nothing at all in a 32bpp linear mode -- there is no palette to
|
||||
// index. The colours had to live somewhere real, and this is it.
|
||||
//
|
||||
// That DAC code is now gone rather than merely unused, along with the blink
|
||||
// disable next to it: both were writes to VGA hardware registers made from the
|
||||
// console, which is neither a driver nor allowed to know it was running on a
|
||||
// VGA. Nothing observable changed, because nothing observable depended on it.
|
||||
static const uint32 ansi_rgb[16] = {
|
||||
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,
|
||||
};
|
||||
|
||||
#define CONS_DEFAULT_FG SCH_FG // see include/theme.h
|
||||
#define CONS_DEFAULT_BG SCH_BG
|
||||
|
||||
static uint32 cons_bg = CONS_DEFAULT_BG;
|
||||
static int cons_bold = 0;
|
||||
|
||||
// Escape parser state.
|
||||
// E_OSC and E_OSC_ESC exist for hyperlinks. A terminal that does not follow
|
||||
// them must still *consume* them, or the URL is printed as text -- which is
|
||||
// what "Disk (]8;;file:///\]8;;\): 4.6 MiB" was: fastfetch wrapping a path in
|
||||
// OSC 8, and this console spelling it out.
|
||||
enum { E_NORMAL, E_ESC, E_CSI, E_OSC, E_OSC_ESC };
|
||||
static int esc_state = E_NORMAL;
|
||||
static int esc_parm[8];
|
||||
static int esc_nparm;
|
||||
static int esc_private; // saw '?' after CSI
|
||||
static int saved_col, saved_row;
|
||||
|
||||
// UTF-8 decoder state. A codepoint arrives as up to four bytes and has to be
|
||||
// reassembled before it can be turned into a glyph.
|
||||
static uint32 u8_cp;
|
||||
static int u8_need;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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;
|
||||
}
|
||||
|
||||
static void
|
||||
sgr(void)
|
||||
{
|
||||
// "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 default 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 = esc_nparm ? esc_nparm : 1; // esc_parm[0] is already 0
|
||||
|
||||
for (int i = 0; i < nparm; i++) {
|
||||
int p = esc_parm[i];
|
||||
|
||||
if (p == 0) {
|
||||
cons_fg = CONS_DEFAULT_FG;
|
||||
cons_bg = CONS_DEFAULT_BG;
|
||||
cons_bold = 0;
|
||||
} else if (p == 1) {
|
||||
cons_bold = 1;
|
||||
// Re-apply brightness to a colour already chosen.
|
||||
for (int j = 0; j < 8; j++)
|
||||
if (cons_fg == ansi_rgb[j]) { cons_fg = ansi_rgb[j + 8]; break; }
|
||||
} else if (p == 22) {
|
||||
cons_bold = 0;
|
||||
} else if (p >= 30 && p <= 37) {
|
||||
cons_fg = ansi_rgb[(p - 30) + (cons_bold ? 8 : 0)];
|
||||
} else if (p == 39) {
|
||||
cons_fg = CONS_DEFAULT_FG;
|
||||
} else if (p >= 40 && p <= 47) {
|
||||
cons_bg = ansi_rgb[p - 40];
|
||||
} else if (p == 49) {
|
||||
cons_bg = CONS_DEFAULT_BG;
|
||||
} else if (p >= 90 && p <= 97) {
|
||||
cons_fg = ansi_rgb[(p - 90) + 8];
|
||||
} else if (p >= 100 && p <= 107) {
|
||||
cons_bg = ansi_rgb[(p - 100) + 8];
|
||||
}
|
||||
}
|
||||
gfx_set_bg(cons_bg);
|
||||
}
|
||||
|
||||
// One final byte of a CSI sequence, with esc_parm[] already filled in.
|
||||
static void
|
||||
csi_dispatch(int final)
|
||||
{
|
||||
int col, row;
|
||||
int n = esc_nparm > 0 ? esc_parm[0] : 0;
|
||||
int n1 = n ? n : 1; // most sequences default to 1
|
||||
|
||||
gfx_cursor_get(&col, &row);
|
||||
|
||||
switch (final) {
|
||||
case 'A': gfx_cursor_set(col, row - n1); break;
|
||||
case 'B': gfx_cursor_set(col, row + n1); break;
|
||||
case 'C': gfx_cursor_set(col + n1, row); break;
|
||||
case 'D': gfx_cursor_set(col - n1, row); break;
|
||||
case 'E': gfx_cursor_set(0, row + n1); break;
|
||||
case 'F': gfx_cursor_set(0, row - n1); break;
|
||||
case 'G': gfx_cursor_set(n1 - 1, row); break;
|
||||
case 'd': gfx_cursor_set(col, n1 - 1); break;
|
||||
|
||||
case 'H': case 'f': {
|
||||
int r = esc_nparm > 0 && esc_parm[0] ? esc_parm[0] : 1;
|
||||
int c = esc_nparm > 1 && esc_parm[1] ? esc_parm[1] : 1;
|
||||
gfx_cursor_set(c - 1, r - 1); // ANSI counts from 1
|
||||
break;
|
||||
}
|
||||
|
||||
case 'J': gfx_erase_display(n); break;
|
||||
case 'K': gfx_erase_line(n); break;
|
||||
|
||||
case 'm': sgr(); break;
|
||||
|
||||
case 's': saved_col = col; saved_row = row; break;
|
||||
case 'u': gfx_cursor_set(saved_col, saved_row); 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 the correct behaviour, 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 it
|
||||
}
|
||||
}
|
||||
|
||||
// Feed one output byte to the framebuffer side. Returns nothing; the serial
|
||||
// side has already had the raw byte.
|
||||
static void
|
||||
gfx_feed(int c)
|
||||
{
|
||||
switch (esc_state) {
|
||||
case E_ESC:
|
||||
if (c == '[') {
|
||||
esc_state = E_CSI;
|
||||
esc_nparm = 0;
|
||||
esc_private = 0;
|
||||
for (int i = 0; i < 8; i++) esc_parm[i] = 0;
|
||||
return;
|
||||
}
|
||||
if (c == ']') { // OSC: swallow it, see below
|
||||
esc_state = E_OSC;
|
||||
return;
|
||||
}
|
||||
esc_state = E_NORMAL;
|
||||
if (c == 'c') { // RIS -- full reset
|
||||
cons_fg = CONS_DEFAULT_FG;
|
||||
cons_bg = CONS_DEFAULT_BG;
|
||||
gfx_screen_reset();
|
||||
}
|
||||
return; // other two-byte escapes: ignored
|
||||
|
||||
// An operating-system command runs until BEL or until ST (ESC \). Nothing
|
||||
// here acts on one -- there is no window title to set and no hyperlink to
|
||||
// follow -- but discarding the payload is not optional: the alternative is
|
||||
// printing somebody's URL into the middle of their output.
|
||||
case E_OSC:
|
||||
if (c == '\a') { esc_state = E_NORMAL; return; }
|
||||
if (c == '\x1b') { esc_state = E_OSC_ESC; return; }
|
||||
return;
|
||||
|
||||
case E_OSC_ESC:
|
||||
// ESC \ ends it; ESC anything-else means the OSC was malformed and a new
|
||||
// escape has started, so hand the byte to the ESC state rather than eating
|
||||
// it. Dropping it would lose the sequence that follows.
|
||||
if (c == '\\') { esc_state = E_NORMAL; return; }
|
||||
esc_state = E_ESC;
|
||||
gfx_feed(c);
|
||||
return;
|
||||
|
||||
case E_CSI:
|
||||
if (c == '?') { esc_private = 1; return; }
|
||||
if (c >= '0' && c <= '9') {
|
||||
if (esc_nparm == 0) esc_nparm = 1;
|
||||
if (esc_nparm <= 8)
|
||||
esc_parm[esc_nparm - 1] = esc_parm[esc_nparm - 1] * 10 + (c - '0');
|
||||
return;
|
||||
}
|
||||
if (c == ';') {
|
||||
if (esc_nparm < 8) esc_nparm++;
|
||||
return;
|
||||
}
|
||||
esc_state = E_NORMAL;
|
||||
if (!esc_private)
|
||||
csi_dispatch(c);
|
||||
return;
|
||||
}
|
||||
|
||||
// E_NORMAL
|
||||
if (c == '\x1b') { esc_state = E_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 (u8_need > 0) {
|
||||
if ((c & 0xC0) != 0x80) { // malformed: abandon the sequence
|
||||
u8_need = 0;
|
||||
gfx_putc('?', cons_fg);
|
||||
return;
|
||||
}
|
||||
u8_cp = (u8_cp << 6) | (c & 0x3F);
|
||||
if (--u8_need > 0)
|
||||
return;
|
||||
int g = glyph_for(u8_cp);
|
||||
gfx_putc(g ? g : '?', cons_fg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (c >= 0xF0) { u8_cp = c & 0x07; u8_need = 3; return; }
|
||||
else if (c >= 0xE0) { u8_cp = c & 0x0F; u8_need = 2; return; }
|
||||
else if (c >= 0xC0) { u8_cp = c & 0x1F; u8_need = 1; return; }
|
||||
else if (c >= 0x80) { gfx_putc('?', cons_fg); return; } // stray continuation
|
||||
|
||||
gfx_putc(c, cons_fg);
|
||||
}
|
||||
// Output goes to the terminal layer now. The escape parser, the palette, the
|
||||
// cursor and the UTF-8 decoder all moved to kernel/dev/vt.c, because each of
|
||||
// them is per-terminal and there used to be exactly one of each.
|
||||
|
||||
void
|
||||
consputc(int c)
|
||||
|
|
@ -495,76 +203,92 @@ consputc(int c)
|
|||
if (c == BACKSPACE) {
|
||||
uartputc('\b'); uartputc(' '); uartputc('\b');
|
||||
if (!cons_serial_only)
|
||||
gfx_putc(c, cons_fg);
|
||||
vt_putc(vt_current(), '\b');
|
||||
return;
|
||||
}
|
||||
|
||||
uartputc(c);
|
||||
if (!cons_serial_only)
|
||||
gfx_feed(c & 0xff);
|
||||
vt_feed(vt_current(), c & 0xff);
|
||||
}
|
||||
|
||||
#define INPUT_BUF 256
|
||||
struct {
|
||||
struct spinlock lock;
|
||||
char buf[INPUT_BUF];
|
||||
uint r;
|
||||
uint w;
|
||||
uint e;
|
||||
uint abort_read;
|
||||
// Set by EOF (Ctrl-D) on an empty line. Canonical-mode end of input is not
|
||||
// a character -- it is the *absence* of one -- so it cannot be signalled by
|
||||
// putting something in the buffer. The old code pushed a literal ^D in and
|
||||
// had consoleread() pick it back out, which meant a ^D typed mid-line was
|
||||
// indistinguishable from one typed alone.
|
||||
uint eof;
|
||||
} input;
|
||||
|
||||
#define C(x) ((x)-'@')
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The line discipline
|
||||
// Terminals as devices
|
||||
//
|
||||
// TCGETS used to return a hardcoded set of flags and TCSETS threw its argument
|
||||
// away, so the terminal was permanently in one mode: line-at-a-time, always
|
||||
// echoing. Anything wanting a keypress at a time -- a pager, an editor, a
|
||||
// shell doing its own line editing, which is most of what zsh is -- had no way
|
||||
// to ask for one. This is the real thing: a struct programs can write, that
|
||||
// changes what the kernel actually does.
|
||||
// The line discipline below used to operate on one `struct input` and one
|
||||
// `struct termios`, because there was one terminal. Both live in struct vt
|
||||
// now (kernel/dev/vt.c) and everything here takes the terminal it is acting
|
||||
// on as an argument.
|
||||
//
|
||||
// One lock still guards every terminal's input rather than one lock each.
|
||||
// The rings are only touched from the keyboard interrupt and from read(2),
|
||||
// both of which are short, and a single lock cannot be acquired in the wrong
|
||||
// order against itself. Per-terminal locks would buy nothing until there is a
|
||||
// second input source to contend with it.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static struct termios tio = {
|
||||
.c_iflag = ICRNL,
|
||||
.c_oflag = OPOST | ONLCR,
|
||||
.c_cflag = CS8 | CREAD | CLOCAL,
|
||||
.c_lflag = ISIG | ICANON | ECHO | ECHOE,
|
||||
.c_cc = {
|
||||
[VINTR] = C('C'), [VQUIT] = C('\\'), [VERASE] = '\x7f', [VKILL] = C('U'),
|
||||
[VEOF] = C('D'), [VSUSP] = C('Z'), [VWERASE] = C('W'), [VMIN] = 1,
|
||||
},
|
||||
.c_ispeed = 15, .c_ospeed = 15,
|
||||
};
|
||||
static struct spinlock input_lock;
|
||||
|
||||
void
|
||||
cons_get_termios(struct termios *out)
|
||||
// Which terminal a device minor names.
|
||||
//
|
||||
// 1 /dev/console the active terminal, so kernel messages and a
|
||||
// shell started on the console both follow a switch
|
||||
// 10 /dev/tty the caller's controlling terminal
|
||||
// 11..16 /dev/tty1..6 that terminal, whether or not it is on screen
|
||||
//
|
||||
// Anything else is the console, which is what an unrecognised minor meant
|
||||
// before there was more than one terminal to get wrong.
|
||||
struct vt *
|
||||
vt_for_minor(int minor)
|
||||
{
|
||||
acquire(&input.lock);
|
||||
*out = tio;
|
||||
release(&input.lock);
|
||||
if (minor >= 11 && minor < 11 + NVT)
|
||||
return vt_get(minor - 11);
|
||||
if (minor == 10) {
|
||||
struct proc *p = myproc();
|
||||
if (p && p->ctty > 0 && p->ctty <= NVT)
|
||||
return vt_get(p->ctty - 1);
|
||||
}
|
||||
return vt_current();
|
||||
}
|
||||
|
||||
// One output byte to a terminal.
|
||||
//
|
||||
// The serial line only hears from the terminal that is on screen. Two
|
||||
// terminals interleaving into one serial log would be unreadable, and the
|
||||
// serial console is meant to show what the monitor shows.
|
||||
static void
|
||||
vt_out(struct vt *v, int c)
|
||||
{
|
||||
if (v == vt_current())
|
||||
uartputc(c);
|
||||
if (!cons_serial_only)
|
||||
vt_feed(v, c & 0xff);
|
||||
}
|
||||
|
||||
void
|
||||
cons_set_termios(const struct termios *in)
|
||||
cons_get_termios(int minor, struct termios *out)
|
||||
{
|
||||
acquire(&input.lock);
|
||||
tio = *in;
|
||||
struct vt *v = vt_for_minor(minor);
|
||||
acquire(&input_lock);
|
||||
*out = v->tio;
|
||||
release(&input_lock);
|
||||
}
|
||||
|
||||
void
|
||||
cons_set_termios(int minor, const struct termios *in)
|
||||
{
|
||||
struct vt *v = vt_for_minor(minor);
|
||||
acquire(&input_lock);
|
||||
v->tio = *in;
|
||||
// Switching out of canonical mode has to hand over whatever has been typed
|
||||
// but not yet submitted, or a shell that turns off ICANON mid-line silently
|
||||
// eats the characters already in the edit buffer.
|
||||
if (!(tio.c_lflag & ICANON))
|
||||
input.w = input.e;
|
||||
release(&input.lock);
|
||||
wakeup(&input.r);
|
||||
if (!(v->tio.c_lflag & ICANON))
|
||||
v->in.w = v->in.e;
|
||||
release(&input_lock);
|
||||
wakeup(&v->in.r);
|
||||
}
|
||||
|
||||
// Discard everything typed but not yet consumed: completed lines waiting in
|
||||
|
|
@ -576,42 +300,77 @@ cons_set_termios(const struct termios *in)
|
|||
// A pending EOF goes too: end of input is buffer state like any other, and
|
||||
// leaving it set would end the *next* read instead of the one it was typed at.
|
||||
void
|
||||
cons_flush_input(void)
|
||||
cons_flush_input(int minor)
|
||||
{
|
||||
acquire(&input.lock);
|
||||
input.e = input.w = input.r;
|
||||
input.eof = 0;
|
||||
release(&input.lock);
|
||||
struct vt *v = vt_for_minor(minor);
|
||||
acquire(&input_lock);
|
||||
v->in.e = v->in.w = v->in.r;
|
||||
v->in.eof = 0;
|
||||
release(&input_lock);
|
||||
}
|
||||
|
||||
// Foreground process group, per terminal. This was one global int for the
|
||||
// whole machine; tcsetpgrp on tty2 now says nothing about tty1, which is the
|
||||
// entire point of having more than one.
|
||||
int
|
||||
cons_fg_pgrp(int minor)
|
||||
{
|
||||
return vt_for_minor(minor)->fg_pgrp;
|
||||
}
|
||||
|
||||
void
|
||||
cons_set_fg_pgrp(int minor, int pg)
|
||||
{
|
||||
vt_for_minor(minor)->fg_pgrp = pg;
|
||||
}
|
||||
|
||||
static void
|
||||
echo(int c)
|
||||
echo(struct vt *v, int c)
|
||||
{
|
||||
if (c == '\n' || c == '\r') {
|
||||
if ((tio.c_lflag & ECHO) || (tio.c_lflag & ECHONL))
|
||||
consputc('\n');
|
||||
if ((v->tio.c_lflag & ECHO) || (v->tio.c_lflag & ECHONL))
|
||||
vt_out(v, '\n');
|
||||
return;
|
||||
}
|
||||
if (!(tio.c_lflag & ECHO))
|
||||
if (!(v->tio.c_lflag & ECHO))
|
||||
return;
|
||||
// Control characters echo as ^X, the way every other terminal shows them.
|
||||
if (c < 32 && c != '\t') {
|
||||
consputc('^');
|
||||
consputc(c + '@');
|
||||
vt_out(v, '^');
|
||||
vt_out(v, c + '@');
|
||||
} else {
|
||||
consputc(c);
|
||||
vt_out(v, c);
|
||||
}
|
||||
}
|
||||
|
||||
// Rub out the last character of the current line.
|
||||
static void
|
||||
erase_one(void)
|
||||
erase_one(struct vt *v)
|
||||
{
|
||||
if (input.e == input.w)
|
||||
if (v->in.e == v->in.w)
|
||||
return;
|
||||
input.e--;
|
||||
if (tio.c_lflag & ECHOE)
|
||||
consputc(BACKSPACE);
|
||||
v->in.e--;
|
||||
if (v->tio.c_lflag & ECHOE) {
|
||||
if (v == vt_current()) { uartputc('\b'); uartputc(' '); uartputc('\b'); }
|
||||
if (!cons_serial_only)
|
||||
vt_putc(v, '\b');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Keyboard input
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Alt+F1..F6 switch terminals. kbd.c reports them as KEY_ALT_F1.. so that the
|
||||
// decision of what a chord *means* is made here, where the terminals are,
|
||||
// rather than in a driver that should not know they exist.
|
||||
int
|
||||
console_vt_hotkey(int key)
|
||||
{
|
||||
if (key < KEY_ALT_F1 || key >= KEY_ALT_F1 + NVT)
|
||||
return 0;
|
||||
vt_switch(key - KEY_ALT_F1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -619,17 +378,30 @@ consoleintr(int (*getc)(void))
|
|||
{
|
||||
int c;
|
||||
|
||||
acquire(&input.lock);
|
||||
acquire(&input_lock);
|
||||
while((c = getc()) >= 0){
|
||||
if (c == 0)
|
||||
continue;
|
||||
|
||||
// Terminal switching happens before the line discipline sees anything:
|
||||
// Alt+F2 is not input for the terminal you are leaving.
|
||||
if (c >= KEY_ALT_F1 && c < KEY_ALT_F1 + NVT) {
|
||||
release(&input_lock);
|
||||
console_vt_hotkey(c);
|
||||
acquire(&input_lock);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Input goes to whichever terminal is on screen. That is what makes a
|
||||
// terminal a terminal: the keyboard follows the eyes.
|
||||
struct vt *v = vt_current();
|
||||
|
||||
// Input mapping, before anything looks at the character.
|
||||
if (tio.c_iflag & ISTRIP) c &= 0x7f;
|
||||
if (v->tio.c_iflag & ISTRIP) c &= 0x7f;
|
||||
if (c == '\r') {
|
||||
if (tio.c_iflag & IGNCR) continue;
|
||||
if (tio.c_iflag & ICRNL) c = '\n';
|
||||
} else if (c == '\n' && (tio.c_iflag & INLCR)) {
|
||||
if (v->tio.c_iflag & IGNCR) continue;
|
||||
if (v->tio.c_iflag & ICRNL) c = '\n';
|
||||
} else if (c == '\n' && (v->tio.c_iflag & INLCR)) {
|
||||
c = '\r';
|
||||
}
|
||||
|
||||
|
|
@ -639,22 +411,22 @@ consoleintr(int (*getc)(void))
|
|||
// handler is whatever happened to be running on this CPU when the key was
|
||||
// pressed, which is almost never the program the user is looking at. It
|
||||
// also set a kill flag rather than raising a signal, so nothing could
|
||||
// catch it and the shell never learned the job had died. Both wrong;
|
||||
// both fixed by sending a real signal to the foreground process group,
|
||||
// which the kernel has been tracking in tty_fg_pgrp all along.
|
||||
if (tio.c_lflag & ISIG) {
|
||||
// catch it and the shell never learned the job had died. Both wrong; both
|
||||
// fixed by sending a real signal to the foreground process group -- which
|
||||
// is now this terminal's group rather than the machine's only one.
|
||||
if (v->tio.c_lflag & ISIG) {
|
||||
int sig = 0;
|
||||
if (c == tio.c_cc[VINTR]) sig = SIGINT;
|
||||
else if (c == tio.c_cc[VQUIT]) sig = SIGQUIT;
|
||||
else if (c == tio.c_cc[VSUSP]) sig = SIGTSTP;
|
||||
if (c == v->tio.c_cc[VINTR]) sig = SIGINT;
|
||||
else if (c == v->tio.c_cc[VQUIT]) sig = SIGQUIT;
|
||||
else if (c == v->tio.c_cc[VSUSP]) sig = SIGTSTP;
|
||||
if (sig) {
|
||||
echo(c);
|
||||
input.e = input.w = input.r; // discard the partial line
|
||||
input.abort_read = 1;
|
||||
release(&input.lock);
|
||||
sigsend_pgrp(tty_fg_pgrp, sig);
|
||||
acquire(&input.lock);
|
||||
wakeup(&input.r);
|
||||
echo(v, c);
|
||||
v->in.e = v->in.w = v->in.r; // discard the partial line
|
||||
v->in.abort_read = 1;
|
||||
release(&input_lock);
|
||||
sigsend_pgrp(v->fg_pgrp, sig);
|
||||
acquire(&input_lock);
|
||||
wakeup(&v->in.r);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -666,53 +438,53 @@ consoleintr(int (*getc)(void))
|
|||
continue;
|
||||
}
|
||||
|
||||
if (tio.c_lflag & ICANON) {
|
||||
if (c == tio.c_cc[VERASE] || c == C('H')) {
|
||||
erase_one();
|
||||
if (v->tio.c_lflag & ICANON) {
|
||||
if (c == v->tio.c_cc[VERASE] || c == C('H')) {
|
||||
erase_one(v);
|
||||
continue;
|
||||
}
|
||||
if (c == tio.c_cc[VKILL]) {
|
||||
while (input.e != input.w &&
|
||||
input.buf[(input.e-1) % INPUT_BUF] != '\n')
|
||||
erase_one();
|
||||
if (c == v->tio.c_cc[VKILL]) {
|
||||
while (v->in.e != v->in.w &&
|
||||
v->in.buf[(v->in.e-1) % VT_INPUT_BUF] != '\n')
|
||||
erase_one(v);
|
||||
continue;
|
||||
}
|
||||
if (c == tio.c_cc[VWERASE]) {
|
||||
while (input.e != input.w &&
|
||||
input.buf[(input.e-1) % INPUT_BUF] == ' ')
|
||||
erase_one();
|
||||
while (input.e != input.w &&
|
||||
input.buf[(input.e-1) % INPUT_BUF] != ' ')
|
||||
erase_one();
|
||||
if (c == v->tio.c_cc[VWERASE]) {
|
||||
while (v->in.e != v->in.w &&
|
||||
v->in.buf[(v->in.e-1) % VT_INPUT_BUF] == ' ')
|
||||
erase_one(v);
|
||||
while (v->in.e != v->in.w &&
|
||||
v->in.buf[(v->in.e-1) % VT_INPUT_BUF] != ' ')
|
||||
erase_one(v);
|
||||
continue;
|
||||
}
|
||||
if (c == tio.c_cc[VEOF]) {
|
||||
if (input.e == input.w)
|
||||
input.eof = 1; // empty line: end of input
|
||||
input.w = input.e; // otherwise just submit what is there
|
||||
wakeup(&input.r);
|
||||
if (c == v->tio.c_cc[VEOF]) {
|
||||
if (v->in.e == v->in.w)
|
||||
v->in.eof = 1; // empty line: end of input
|
||||
v->in.w = v->in.e; // otherwise submit what is there
|
||||
wakeup(&v->in.r);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (input.e - input.r >= INPUT_BUF)
|
||||
if (v->in.e - v->in.r >= VT_INPUT_BUF)
|
||||
continue; // buffer full: drop it
|
||||
|
||||
// Everything else goes in, including bytes above 127 -- those are UTF-8
|
||||
// continuation bytes, and the old `c >= 32 && c <= 126` test silently
|
||||
// refused every non-ASCII character anyone tried to type.
|
||||
input.buf[input.e++ % INPUT_BUF] = c;
|
||||
echo(c);
|
||||
v->in.buf[v->in.e++ % VT_INPUT_BUF] = c;
|
||||
echo(v, c);
|
||||
|
||||
// Canonical mode hands over a line at a time; raw mode hands over
|
||||
// everything as it arrives.
|
||||
if (!(tio.c_lflag & ICANON) ||
|
||||
c == '\n' || input.e == input.r + INPUT_BUF) {
|
||||
input.w = input.e;
|
||||
wakeup(&input.r);
|
||||
if (!(v->tio.c_lflag & ICANON) ||
|
||||
c == '\n' || v->in.e == v->in.r + VT_INPUT_BUF) {
|
||||
v->in.w = v->in.e;
|
||||
wakeup(&v->in.r);
|
||||
}
|
||||
}
|
||||
release(&input.lock);
|
||||
release(&input_lock);
|
||||
}
|
||||
|
||||
int
|
||||
|
|
@ -720,11 +492,10 @@ consoleread(struct inode *ip, uint64 off, char *dst, int n)
|
|||
{
|
||||
uint target;
|
||||
int c;
|
||||
static uint32 prng_seed = 0x9A7B3C1D;
|
||||
static uint32 prng_seed = 0x9A7B3C1D;
|
||||
|
||||
if (ip->minor == 2) {
|
||||
if (ip->minor == 2)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ip->minor == 4) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
|
|
@ -736,56 +507,59 @@ consoleread(struct inode *ip, uint64 off, char *dst, int n)
|
|||
return n;
|
||||
}
|
||||
|
||||
struct vt *v = vt_for_minor(ip->minor);
|
||||
|
||||
iunlock(ip);
|
||||
target = n;
|
||||
acquire(&input.lock);
|
||||
acquire(&input_lock);
|
||||
while(n > 0){
|
||||
while(input.r == input.w){
|
||||
if (input.eof) {
|
||||
input.eof = 0;
|
||||
release(&input.lock);
|
||||
while(v->in.r == v->in.w){
|
||||
if (v->in.eof) {
|
||||
v->in.eof = 0;
|
||||
release(&input_lock);
|
||||
ilock(ip);
|
||||
return 0; // end of input
|
||||
}
|
||||
if (myproc()->killed || input.abort_read) {
|
||||
input.abort_read = 0;
|
||||
release(&input.lock);
|
||||
if (myproc()->killed || v->in.abort_read) {
|
||||
v->in.abort_read = 0;
|
||||
release(&input_lock);
|
||||
ilock(ip);
|
||||
return -1;
|
||||
}
|
||||
sleep(&input.r, &input.lock);
|
||||
sleep(&v->in.r, &input_lock);
|
||||
}
|
||||
c = input.buf[input.r++ % INPUT_BUF];
|
||||
c = v->in.buf[v->in.r++ % VT_INPUT_BUF];
|
||||
*dst++ = c;
|
||||
--n;
|
||||
|
||||
// Canonical mode returns a line at a time. Raw mode returns whatever has
|
||||
// arrived: a program that asked for a keypress must not be made to wait
|
||||
// for a newline that is never coming.
|
||||
if (tio.c_lflag & ICANON) {
|
||||
if (v->tio.c_lflag & ICANON) {
|
||||
if (c == '\n')
|
||||
break;
|
||||
} else {
|
||||
if (target - n >= (uint)(tio.c_cc[VMIN] ? tio.c_cc[VMIN] : 1))
|
||||
if (target - n >= (uint)(v->tio.c_cc[VMIN] ? v->tio.c_cc[VMIN] : 1))
|
||||
break;
|
||||
}
|
||||
}
|
||||
release(&input.lock);
|
||||
release(&input_lock);
|
||||
ilock(ip);
|
||||
|
||||
return target - n;
|
||||
}
|
||||
|
||||
// Nonzero if a completed input line is waiting — poll(2)'s POLLIN answer
|
||||
// for the console tty. Mirrors the r/w cursors consoleread() blocks on.
|
||||
// Nonzero if a completed input line is waiting -- poll(2)'s POLLIN answer for
|
||||
// a terminal. Mirrors the r/w cursors consoleread() blocks on.
|
||||
int
|
||||
console_input_ready(void)
|
||||
console_input_ready(int minor)
|
||||
{
|
||||
struct vt *v = vt_for_minor(minor);
|
||||
int ready;
|
||||
|
||||
acquire(&input.lock);
|
||||
ready = input.r != input.w;
|
||||
release(&input.lock);
|
||||
acquire(&input_lock);
|
||||
ready = v->in.r != v->in.w;
|
||||
release(&input_lock);
|
||||
return ready;
|
||||
}
|
||||
|
||||
|
|
@ -794,20 +568,50 @@ consolewrite(struct inode *ip, uint64 off, char *buf, int n)
|
|||
{
|
||||
int i;
|
||||
|
||||
if (ip->minor == 2 || ip->minor == 3 || ip->minor == 4) {
|
||||
return n;
|
||||
}
|
||||
if (ip->minor == 2 || ip->minor == 3 || ip->minor == 4)
|
||||
return n;
|
||||
|
||||
struct vt *v = vt_for_minor(ip->minor);
|
||||
|
||||
iunlock(ip);
|
||||
acquire(&cons.lock);
|
||||
if (panicked) {
|
||||
release(&cons.lock);
|
||||
ilock(ip);
|
||||
cli();
|
||||
for(;;) hlt();
|
||||
}
|
||||
for(i = 0; i < n; i++)
|
||||
consputc(buf[i] & 0xff);
|
||||
vt_out(v, buf[i] & 0xff);
|
||||
release(&cons.lock);
|
||||
ilock(ip);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
// A descriptor has been opened on a terminal, or the last one closed.
|
||||
//
|
||||
// This is the only thing that knows a terminal is in use, which is what
|
||||
// VT_GETSTATE reports through v_state and what VT_OPENQRY looks for when it
|
||||
// is asked for a free one. Without it `chvt` says every terminal is idle even
|
||||
// while two shells are sitting on them.
|
||||
static void
|
||||
consoleopen(struct inode *ip)
|
||||
{
|
||||
if (ip->minor >= 11 && ip->minor < 11 + NVT)
|
||||
vt_get(ip->minor - 11)->opened++;
|
||||
}
|
||||
|
||||
static void
|
||||
consoleclose(struct inode *ip)
|
||||
{
|
||||
if (ip->minor >= 11 && ip->minor < 11 + NVT) {
|
||||
struct vt *v = vt_get(ip->minor - 11);
|
||||
if (v->opened > 0)
|
||||
v->opened--;
|
||||
}
|
||||
}
|
||||
|
||||
// The keyboard handler lives in kbd.c, but the console is what owns the
|
||||
// keyboard as an input source, so this is where the line gets claimed.
|
||||
static void
|
||||
|
|
@ -821,12 +625,12 @@ void
|
|||
consoleinit(void)
|
||||
{
|
||||
initlock(&cons.lock, "console");
|
||||
initlock(&input.lock, "input");
|
||||
|
||||
memset(&input, 0, sizeof(input));
|
||||
initlock(&input_lock, "input");
|
||||
|
||||
devsw[CONSOLE].write = consolewrite;
|
||||
devsw[CONSOLE].read = consoleread;
|
||||
devsw[CONSOLE].read = consoleread;
|
||||
devsw[CONSOLE].open = consoleopen;
|
||||
devsw[CONSOLE].close = consoleclose;
|
||||
cons.locking = 1;
|
||||
|
||||
irq_register(IRQ_KBD, kbd_irq, 0, "ps2-kbd", 0);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "vt.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
|
|
@ -68,6 +69,11 @@ kmain(uint32 fb_addr)
|
|||
// their handlers are registered.
|
||||
inputinit();
|
||||
if (boot_verbose) cprintf("verbose boot: inputinit done\n");
|
||||
// Before consoleinit, because the console writes to a terminal and there
|
||||
// has to be one. vt_init reads the framebuffer geometry, so it also has to
|
||||
// come after whatever attached a scanout.
|
||||
vt_init();
|
||||
if (boot_verbose) cprintf("verbose boot: vt_init done\n");
|
||||
consoleinit();
|
||||
if (boot_verbose) cprintf("verbose boot: consoleinit done\n");
|
||||
uartinit();
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@ memread(struct inode *ip, uint64 off, char *dst, int n)
|
|||
case 1: return consoleread(ip, off, dst, n);
|
||||
case 2: return nullread(ip, off, dst, n);
|
||||
case 3: return consoleread(ip, off, dst, n);
|
||||
/* 10 = /dev/tty, 11..16 = /dev/tty1..tty6. consoleread resolves the
|
||||
minor to a terminal; see vt_for_minor(). */
|
||||
case 10: case 11: case 12: case 13: case 14: case 15: case 16:
|
||||
return consoleread(ip, off, dst, n);
|
||||
case 5: return zeroread(ip, off, dst, n);
|
||||
case 7: return zeroread(ip, off, dst, n); // /dev/full reads as zeros
|
||||
case 8:
|
||||
|
|
@ -77,6 +81,8 @@ memwrite(struct inode *ip, uint64 off, char *dst, int n)
|
|||
{
|
||||
switch(ip->minor) {
|
||||
case 1: return consolewrite(ip, off, dst, n);
|
||||
case 10: case 11: case 12: case 13: case 14: case 15: case 16:
|
||||
return consolewrite(ip, off, dst, n);
|
||||
case 7: return fullwrite(ip, off, dst, n); // /dev/full: always ENOSPC
|
||||
case 2:
|
||||
case 5:
|
||||
|
|
|
|||
|
|
@ -202,6 +202,55 @@ gfx_putc(int c, uint32 color)
|
|||
}
|
||||
}
|
||||
|
||||
// --- primitives for the text layer ----------------------------------------
|
||||
//
|
||||
// kernel/dev/vt.c owns a grid of cells and needs exactly two things the
|
||||
// console-cursor interface above cannot give it: draw one cell at a grid
|
||||
// position with its *own* background, and scroll the whole screen without
|
||||
// consulting a cursor. Everything else a terminal does is bookkeeping on the
|
||||
// grid, which is not this file's business.
|
||||
|
||||
// One cell, addressed in cells rather than pixels, with its own colours.
|
||||
// gfx_draw_char paints its background from the file-static gfx_bg, which is
|
||||
// right for a console that has one background and wrong for a terminal where
|
||||
// every cell carries its own.
|
||||
void
|
||||
gfx_draw_cell(int col, int row, uchar glyph, uint32 fg, uint32 bg)
|
||||
{
|
||||
if (!fb)
|
||||
return;
|
||||
|
||||
int x = col * CHAR_WIDTH, y = row * CHAR_HEIGHT;
|
||||
if (x < 0 || y < 0 ||
|
||||
x + CHAR_WIDTH > (int)scanout.width || y + CHAR_HEIGHT > (int)scanout.height)
|
||||
return;
|
||||
|
||||
for (int r = 0; r < CHAR_HEIGHT; r++) {
|
||||
uchar bits = font_data[glyph][r];
|
||||
for (int c = 0; c < CHAR_WIDTH; c++)
|
||||
gfx_draw_pixel(x + c, y + r, (bits & (0x80 >> c)) ? fg : bg);
|
||||
}
|
||||
}
|
||||
|
||||
// Move the whole screen up one text row and clear the row it exposes. The
|
||||
// cursor is not touched: the caller tracks it in cells and knows where it
|
||||
// wants to be, which is not necessarily where a pixel-scroll would leave it.
|
||||
void
|
||||
gfx_scroll_up(uint32 bg)
|
||||
{
|
||||
if (!fb || scanout.height < CHAR_HEIGHT)
|
||||
return;
|
||||
|
||||
uint32 line_size = scanout.stride * CHAR_HEIGHT;
|
||||
uint32 total_size = scanout.stride * scanout.height;
|
||||
|
||||
memmove((void*)fb, (void*)(fb + line_size), total_size - line_size);
|
||||
|
||||
for (int y = (int)scanout.height - CHAR_HEIGHT; y < (int)scanout.height; y++)
|
||||
for (int x = 0; x < (int)scanout.width; x++)
|
||||
gfx_draw_pixel(x, y, bg);
|
||||
}
|
||||
|
||||
void
|
||||
gfx_screen_reset(void)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "x86.h"
|
||||
#include "defs.h"
|
||||
#include "kbd.h"
|
||||
#include "vt.h" /* NVT -- how many terminals the chords reach */
|
||||
#include "input.h"
|
||||
|
||||
int
|
||||
|
|
@ -58,6 +59,14 @@ kbdgetc(void)
|
|||
|
||||
shift |= shiftcode[data];
|
||||
shift ^= togglecode[data];
|
||||
|
||||
// Alt+F1..F6 before the character tables, because there is no character to
|
||||
// look up: it is a chord that means "show me a different terminal". What it
|
||||
// means is decided in console.c, which is where the terminals are; this only
|
||||
// has to name it. See console_vt_hotkey().
|
||||
if ((shift & ALT) && data >= SCAN_F1 && data < SCAN_F1 + NVT)
|
||||
return KEY_ALT_F1 + (data - SCAN_F1);
|
||||
|
||||
c = charcode[shift & (CTL | SHIFT)][data];
|
||||
if(shift & CAPSLOCK){
|
||||
if('a' <= c && c <= 'z')
|
||||
|
|
|
|||
627
kernel/dev/vt.c
Normal file
627
kernel/dev/vt.c
Normal file
|
|
@ -0,0 +1,627 @@
|
|||
// 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;
|
||||
}
|
||||
|
||||
void
|
||||
vt_init(void)
|
||||
{
|
||||
int cols = gfx_cols(), rows = gfx_rows();
|
||||
|
||||
// No framebuffer (serial-only boot): 80x24 is a stated default rather than
|
||||
// a measurement of nothing. The grids still work and still scroll; nobody
|
||||
// is looking at them.
|
||||
if (cols <= 0) cols = 80;
|
||||
if (rows <= 0) rows = 24;
|
||||
if (cols > VT_MAXCOLS) cols = VT_MAXCOLS;
|
||||
if (rows > VT_MAXROWS) rows = VT_MAXROWS;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void
|
||||
vt_putc(struct vt *v, int glyph)
|
||||
{
|
||||
switch (glyph) {
|
||||
case '\n':
|
||||
v->cx = 0;
|
||||
v->cy++;
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
v->cx = 0;
|
||||
break;
|
||||
|
||||
case '\b':
|
||||
case 0x7f:
|
||||
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.
|
||||
do {
|
||||
put_blank_at(v, v->cx, v->cy);
|
||||
v->cx++;
|
||||
} while (v->cx % 8 != 0 && v->cx < v->cols);
|
||||
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 = '?';
|
||||
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);
|
||||
v->cx++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (v->cx >= v->cols) {
|
||||
v->cx = 0;
|
||||
v->cy++;
|
||||
}
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
|
@ -72,7 +72,17 @@ static const struct devnode devnodes[] = {
|
|||
{ "full", 1, 7, D_ROOT },
|
||||
{ "random", 1, 8, D_ROOT },
|
||||
{ "urandom", 1, 9, D_ROOT },
|
||||
{ "tty", 1, 1, D_ROOT }, /* alias for the console */
|
||||
/* /dev/tty is the caller's *controlling* terminal, which is a different
|
||||
thing from the console and used to be the same thing only because there
|
||||
was one terminal. Minor 10 resolves through proc->ctty; see
|
||||
vt_for_minor() in kernel/core/console.c. */
|
||||
{ "tty", 1, 10, D_ROOT },
|
||||
{ "tty1", 1, 11, D_ROOT },
|
||||
{ "tty2", 1, 12, D_ROOT },
|
||||
{ "tty3", 1, 13, D_ROOT },
|
||||
{ "tty4", 1, 14, D_ROOT },
|
||||
{ "tty5", 1, 15, D_ROOT },
|
||||
{ "tty6", 1, 16, D_ROOT },
|
||||
{ "stdin", 1, 1, D_ROOT },
|
||||
{ "stdout", 1, 1, D_ROOT },
|
||||
{ "stderr", 1, 1, D_ROOT },
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ file_poll(struct file *f, int events)
|
|||
|
||||
if(f->ip && f->ip->type == T_DEV && f->ip->major == CONSOLE){
|
||||
int rev = 0;
|
||||
if((events & POLLIN) && f->readable && console_input_ready())
|
||||
if((events & POLLIN) && f->readable && console_input_ready(f->ip ? f->ip->minor : 1))
|
||||
rev |= POLLIN;
|
||||
if((events & POLLOUT) && f->writable)
|
||||
rev |= POLLOUT;
|
||||
|
|
|
|||
|
|
@ -618,7 +618,7 @@ wakeup(void *chan)
|
|||
// POSIX, and Linux in sig_task_ignored(): a signal whose disposition is the
|
||||
// default action is discarded for init rather than delivered. Without this the
|
||||
// system has a single keystroke that kills it -- Ctrl-C sends SIGINT to the
|
||||
// foreground process group, init is pgid 1, and any moment where tty_fg_pgrp
|
||||
// foreground process group, init is pgid 1, and any moment where a terminal's fg_pgrp
|
||||
// is 1 (before a shell claims the terminal, after one exits, or in the window
|
||||
// between the two while the key is held down) lands SIGINT on init. init has
|
||||
// no handler, so killed is set, exit() runs, and exit() panics outright on
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include "errno.h"
|
||||
#include "schfs.h" /* SCHFS_BLKSIZE, SCHFS_NAME_MAX — statfs reports them */
|
||||
#include "gfx.h" /* gfx_cols/gfx_rows — TIOCGWINSZ reports them */
|
||||
#include "vt.h" /* the terminals TIOC*/VT_* act on */
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Linux-ABI-compatible struct stat (x86_64 "struct stat" from
|
||||
|
|
@ -1048,13 +1049,13 @@ addr_t sys_ioctl(void)
|
|||
struct termios *tp = (struct termios*)user_ptr(argp, sizeof(*tp));
|
||||
if (!tp) return -EFAULT;
|
||||
// The real thing, not a hardcoded snapshot: whatever was last set.
|
||||
cons_get_termios(tp);
|
||||
cons_get_termios(f->ip->minor, tp);
|
||||
return 0;
|
||||
}
|
||||
if (request == TCSETS || request == TCSETSW || request == TCSETSF) {
|
||||
struct termios *tp = (struct termios*)user_ptr(argp, sizeof(*tp));
|
||||
if (!tp) return -EFAULT;
|
||||
cons_set_termios(tp);
|
||||
cons_set_termios(f->ip->minor, tp);
|
||||
return 0;
|
||||
}
|
||||
if (request == TIOCGWINSZ) {
|
||||
|
|
@ -1092,7 +1093,7 @@ addr_t sys_ioctl(void)
|
|||
// during a command survives the flush and lands in the next prompt.
|
||||
if (request == TCFLSH) {
|
||||
if (argp == TCIFLUSH || argp == TCIOFLUSH)
|
||||
cons_flush_input();
|
||||
cons_flush_input(f->ip->minor);
|
||||
return 0;
|
||||
}
|
||||
// Controlling-terminal foreground process group.
|
||||
|
|
@ -1104,7 +1105,8 @@ addr_t sys_ioctl(void)
|
|||
// while (tcgetpgrp(tty) != getpgrp()) kill(0, SIGTTIN);
|
||||
// spun forever. Both ends now read the same variable.
|
||||
if (request == TIOCGPGRP) {
|
||||
put_user(tty_fg_pgrp, argp);
|
||||
int pg = cons_fg_pgrp(f->ip->minor);
|
||||
put_user(pg, argp);
|
||||
return 0;
|
||||
}
|
||||
if (request == TIOCSPGRP) {
|
||||
|
|
@ -1113,10 +1115,70 @@ addr_t sys_ioctl(void)
|
|||
return -EFAULT;
|
||||
if (pg <= 0)
|
||||
return -EINVAL;
|
||||
tty_fg_pgrp = pg;
|
||||
cons_set_fg_pgrp(f->ip->minor, pg);
|
||||
return 0;
|
||||
}
|
||||
if (request == TIOCSCTTY) return 0;
|
||||
// TIOCSCTTY was `return 0` -- it claimed to make this terminal the
|
||||
// caller's controlling terminal and did nothing, because there was one
|
||||
// terminal and nothing to choose. It records the choice now, which is what
|
||||
// /dev/tty resolves through and what a session leader on tty3 needs so
|
||||
// that its /dev/tty is tty3 and not whatever is on screen.
|
||||
if (request == TIOCSCTTY) {
|
||||
struct vt *v = vt_for_minor(f->ip->minor);
|
||||
myproc()->ctty = vt_index(v) + 1;
|
||||
v->sid = myproc()->sid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- virtual terminal control ---------------------------------------
|
||||
//
|
||||
// The argument is the terminal number by value for ACTIVATE and
|
||||
// WAITACTIVE, and a pointer for GETSTATE and OPENQRY. Passing argp to
|
||||
// user_ptr for the first two would fault on a legitimate call, which is
|
||||
// the same shape of bug TCFLSH had.
|
||||
if (request == VT_ACTIVATE)
|
||||
return vt_switch((int)argp - 1) == 0 ? 0 : -EINVAL;
|
||||
|
||||
if (request == VT_WAITACTIVE) {
|
||||
// Nothing to wait for: vt_switch repaints synchronously, so by the
|
||||
// time VT_ACTIVATE has returned the terminal is already active.
|
||||
// Answering success is honest here; blocking would be a lie about
|
||||
// work that has already happened.
|
||||
int want = (int)argp - 1;
|
||||
if (want < 0 || want >= NVT) return -EINVAL;
|
||||
return vt_active == want ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
if (request == VT_GETSTATE) {
|
||||
struct vt_stat *st = (struct vt_stat*)user_ptr(argp, sizeof(*st));
|
||||
if (!st) return -EFAULT;
|
||||
st->v_active = (uint16)(vt_active + 1);
|
||||
st->v_signal = 0;
|
||||
// Bit n set means terminal n is in use. Bit 0 is unused because
|
||||
// terminals are numbered from 1.
|
||||
st->v_state = 0;
|
||||
for (int i = 0; i < NVT; i++)
|
||||
if (vt_get(i)->opened > 0)
|
||||
st->v_state |= (uint16)(1u << (i + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (request == VT_OPENQRY) {
|
||||
int *out = (int*)user_ptr(argp, sizeof(int));
|
||||
if (!out) return -EFAULT;
|
||||
for (int i = 0; i < NVT; i++)
|
||||
if (vt_get(i)->opened == 0) { *out = i + 1; return 0; }
|
||||
*out = -1;
|
||||
return -ENFILE;
|
||||
}
|
||||
|
||||
// VT_GETMODE/VT_SETMODE describe whether the kernel or a process drives
|
||||
// switching. There is only the kernel here, so report the default and
|
||||
// accept the default; refusing outright would stop a program that only
|
||||
// wanted to check.
|
||||
if (request == VT_GETMODE || request == VT_SETMODE)
|
||||
return 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue