SchOS/kernel/dev/kbd.c
Nexoniarz 4a7165c951 X gets a VT: keyboard, terminal switching, and a way out
Xorg uses VTs only when stat("/dev/tty0") reports Linux's tty major 4.
SchOS's terminals sit under major 1, so X ran on no VT at all: the console
kept drawing over it, Ctrl+Alt+F-keys did nothing, and nothing ended the
session. stat now reports terminals under Linux's numbers.

startx picks the VT only when uname says Linux, so root's xserverrc passes
the current one itself. An xorg.conf.d file names X's input devices, since
Alpine's Xorg looks them up through udev and SchOS has none yet.

Alt+F1..F6 and Ctrl+Alt+F1..F6 now switch terminals as on Linux, alongside
the settable chord. USB keyboards gain arrows, Home/End/PgUp/PgDn/Ins/Del
and the switch keys, which the PS/2 path always had.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-24 21:45:50 +02:00

185 lines
6 KiB
C

#include "core/types.h"
#include "arch/x86.h"
#include "core/defs.h"
#include "dev/kbd.h"
#include "uapi/posix.h"
#include "dev/vt.h" /* NVT -- how many terminals the chords reach */
#include "dev/input.h"
#include "dev/keymap.h"
// The chord, settable at run time through VT_SETCHORD.
//
// AltGr+F1..F6 by default. Every other candidate is taken by the host, by
// applications, or by the keyboard layout -- see include/dev/kbd.h -- and AltGr
// with a function key is taken by none of the three: a Polish layout wants
// AltGr+*letter*, which still reaches the shell untouched, and no window
// manager binds AltGr with an F-key.
//
// AltGr+minus and AltGr+equals step to the previous and next terminal on the
// same modifiers, for when you have two and do not care which number they are.
//
// Zero modifiers disables all of it; `chvt N` still works.
uint vt_chord_mods = RALT;
uint vt_chord_base = SCAN_F1;
// Which scancodes are modifiers, and which are locks. Hardware facts rather
// than layout ones -- every layout agrees that 0x2A is a Shift key -- so they
// stay here while the character tables live in keymap.c.
static const uchar shiftcode[256] = {
[0x1D] = CTL,
[0x2A] = SHIFT,
[0x36] = SHIFT,
[0x38] = ALT,
[0x9D] = CTL,
[0xB8] = RALT // right Alt / AltGr -- see RALT in kbd.h
};
static const uchar togglecode[256] = {
[0x3A] = CAPSLOCK,
[0x45] = NUMLOCK,
[0x46] = SCROLLLOCK
};
// A key can produce more than one byte.
//
// consoleintr's contract with getc() is bytes: the serial line hands it raw
// UTF-8 already, and it would have no way to tell an encoded codepoint apart
// from a byte that happened to have the same value. So the keyboard encodes
// here and hands the bytes over one call at a time -- kbdintr loops until
// getc() returns negative, so a queue of at most four drains on its own
// before the next scancode is read.
static uchar pend[4];
static int pend_n, pend_i;
static int
queue_utf8(uint32 cp)
{
pend_i = 0;
if (cp < 0x80) {
pend_n = 0;
return (int)cp; // the common case: no queue at all
}
if (cp < 0x800) {
pend[0] = 0xC0 | (cp >> 6);
pend[1] = 0x80 | (cp & 0x3F);
pend_n = 2;
} else if (cp < 0x10000) {
pend[0] = 0xE0 | (cp >> 12);
pend[1] = 0x80 | ((cp >> 6) & 0x3F);
pend[2] = 0x80 | (cp & 0x3F);
pend_n = 3;
} else {
pend[0] = 0xF0 | (cp >> 18);
pend[1] = 0x80 | ((cp >> 12) & 0x3F);
pend[2] = 0x80 | ((cp >> 6) & 0x3F);
pend[3] = 0x80 | (cp & 0x3F);
pend_n = 4;
}
return pend[pend_i++];
}
int
kbdgetc(void)
{
static uint shift;
uint st, data;
uint32 cp;
if (pend_i < pend_n)
return pend[pend_i++]; // finish the last key first
pend_i = pend_n = 0;
st = inb(KBSTATP);
if((st & KBS_DIB) == 0)
return -1;
if(st & 0x20)
return -1;
data = inb(KBDATAP);
if(data == 0xE0){
shift |= E0ESC;
return 0;
}
// Feed /dev/input/event0 before the byte is turned into a character. This
// has to happen here rather than in a second handler because the byte can
// only be read from the port once -- whoever reads it owes it to everyone
// else. The console path below is untouched: both consumers see every key.
input_ps2_scancode(data, (shift & E0ESC) != 0);
// While something holds an exclusive grab on the keyboard, the console gets
// nothing. The shift/caps state below is still tracked, so releasing the
// grab does not leave the line discipline believing a modifier is held.
if(input_is_grabbed(INPUT_KBD)){
if(data & 0x80){
data = (shift & E0ESC ? data : data & 0x7F);
shift &= ~(shiftcode[data] | E0ESC);
} else {
shift |= shiftcode[data];
shift ^= togglecode[data];
}
return 0;
}
if(data & 0x80){
data = (shift & E0ESC ? data : data & 0x7F);
shift &= ~(shiftcode[data] | E0ESC);
return 0;
} else if(shift & E0ESC){
data |= 0x80;
shift &= ~E0ESC;
}
shift |= shiftcode[data];
shift ^= togglecode[data];
// The switch chord, before the character tables, because there is no
// character to look up: it is a combination 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().
//
// The modifier test is an equality, not a mask. `shift & ALT` would fire on
// Ctrl+Alt+1 and on AltGr+1 as well, so a chord aimed at the host -- or a
// Polish keystroke -- would switch terminals behind your back. Exactly the
// modifiers named, and nothing else held.
if (vt_chord_mods != 0 &&
(shift & (CTL | SHIFT | ALT | RALT)) == vt_chord_mods) {
if (data >= vt_chord_base && data < vt_chord_base + NVT)
return KBD_VT_SWITCH + (data - vt_chord_base);
if (data == SCAN_MINUS) return KBD_VT_PREV;
if (data == SCAN_EQUALS) return KBD_VT_NEXT;
}
// And Linux's own: Alt+F1..F6 and Ctrl+Alt+F1..F6, whatever the chord is set
// to. These are what anyone who has used Linux presses, on a real machine,
// and nothing reached them: SchOS answered only AltGr+F-keys, which no one
// thinks to try. Under X the terminal is in VT_PROCESS, console_vt_hotkey()
// lets the keys through, and X switches with them itself -- as on Linux.
if (data >= SCAN_F1 && data < SCAN_F1 + NVT &&
((shift & (CTL | SHIFT | ALT | RALT)) == ALT ||
(shift & (CTL | SHIFT | ALT | RALT)) == (CTL | ALT)))
return KBD_VT_SWITCH + (data - SCAN_F1);
cp = keymap_lookup(data, shift);
if (cp == 0)
return 0;
if (cp >= KBD_SPECIAL)
return (int)cp; // arrows and the rest, not characters
return queue_utf8(cp);
}
void
kbdintr(void)
{
consoleintr(kbdgetc);
}
// The two headers describe the same bits to two different audiences, and a
// mismatch would be silent: a chord set from userspace would simply never
// match, and the only symptom would be a key that does nothing.
_Static_assert(VTC_SHIFT == SHIFT && VTC_CTRL == CTL &&
VTC_ALT == ALT && VTC_RALT == RALT,
"include/uapi/posix.h VTC_* must mirror include/dev/kbd.h modifiers");