tty: the switch chord is a setting, because only the host knows the answer
Third guess at a chord, and the third one would have been wrong too: a display
manager that grabs Ctrl+Alt+F<n> matches loosely, so Ctrl+Alt+Shift+F2 never
reaches the guest either. Extra modifiers do not get past a loose grab.
The real problem is that this is not knowable from inside. A key the host
swallowed is indistinguishable from a key nobody pressed, so the guest cannot
probe for a free combination, and there is no combination that is free
everywhere:
the HOST takes Alt+F4, Alt+F2, Ctrl+Alt+F1..F6 (loosely matched),
Ctrl+Alt+digit (QEMU's own console tabs)
APPLICATIONS want Alt+anything (Meta -- zsh binds Meta-digit as
digit-argument), Ctrl+letter, Ctrl+Shift+digit
(Ctrl+Shift+2 types ^@), bare F-keys
the LAYOUT wants AltGr+letter
So it is a setting. VT_SETCHORD/VT_GETCHORD, and in userspace:
chvt -c ctrl+shift+F Ctrl+Shift+F1..F6 (the new default)
chvt -c ctrl+alt+shift+F
chvt -c alt+1
chvt -c off no chord; chvt N is then the only way
chvt report the active terminal and the chord
`off` is a legitimate setting rather than a failure mode. On a machine whose
host grabs everything worth binding, `chvt N` is the honest answer.
The default moved to Ctrl+Shift+F1..F6: display managers bind Ctrl+Alt+F<n>,
not Ctrl+Shift+F<n>, and Ctrl+Shift with a *function* key is not a character
on any layout -- unlike Ctrl+Shift+digit, which is.
Caught while writing it: VTC_RALT was 0x08 while kbd.h's RALT is (1<<7), since
(1<<3) is CAPSLOCK. A chord set from userspace with AltGr would simply never
have matched, and the only symptom would have been a key that does nothing.
A _Static_assert now holds the two headers together.
Verified: Ctrl+Alt+F2 leaves the screen byte-identical, Ctrl+Shift+F2 switches,
Ctrl+Shift+F1 returns to a pixel-identical tty1, and chvt -c round-trips
through alt+1 and off and back. schfs-check passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
parent
fe2f5e1bf8
commit
7c0f7c7d01
8 changed files with 256 additions and 60 deletions
107
cmd/chvt/main.c
107
cmd/chvt/main.c
|
|
@ -3,12 +3,26 @@
|
|||
// 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
|
||||
// same thing and nobody should have to learn a second one. The chord 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
|
||||
// chvt report the active terminal, those in use, and the chord
|
||||
// chvt 2 switch to /dev/tty2
|
||||
// chvt -c <spec> set the switch chord
|
||||
// chvt -c off unbind it entirely; chvt stays the only way to switch
|
||||
//
|
||||
// The chord is settable because the right value is a property of the *host*,
|
||||
// not of SchOS. A display manager that grabs Ctrl+Alt+F<n> takes it before the
|
||||
// guest sees anything, and it matches loosely -- extra modifiers do not get
|
||||
// past it -- so a chord that works on one machine is dead on the next. The
|
||||
// guest cannot detect this: a key the host swallowed is indistinguishable from
|
||||
// a key nobody pressed. So it is a setting.
|
||||
//
|
||||
// chvt -c ctrl+shift+F Ctrl+Shift+F1..F6 (the default)
|
||||
// chvt -c ctrl+alt+shift+F Ctrl+Alt+Shift+F1..F6
|
||||
// chvt -c alt+1 Alt+1..6
|
||||
// chvt -c off no chord at all
|
||||
|
||||
#include "types.h"
|
||||
#include "user.h"
|
||||
|
|
@ -44,6 +58,73 @@ num(const char *s)
|
|||
return v;
|
||||
}
|
||||
|
||||
static int
|
||||
same(const char *a, const char *b, int n)
|
||||
{
|
||||
for (int i = 0; i < n; i++)
|
||||
if (a[i] != b[i]) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
eq(const char *a, const char *b)
|
||||
{
|
||||
while (*a && *a == *b) { a++; b++; }
|
||||
return *a == 0 && *b == 0;
|
||||
}
|
||||
|
||||
// "ctrl+shift+F", "alt+1", "off". Modifiers by name, then a base: F for the
|
||||
// function keys, 1 for the number row.
|
||||
static int
|
||||
parse_chord(const char *spec, struct vt_chord *out)
|
||||
{
|
||||
out->mods = 0;
|
||||
out->base = 0;
|
||||
|
||||
if (eq(spec, "off") || eq(spec, "none"))
|
||||
return 1; // no modifiers: the chord is disabled
|
||||
|
||||
const char *p = spec;
|
||||
while (*p) {
|
||||
const char *start = p;
|
||||
while (*p && *p != '+') p++;
|
||||
int n = (int)(p - start);
|
||||
|
||||
if (n == 4 && same(start, "ctrl", 4)) out->mods |= VTC_CTRL;
|
||||
else if (n == 5 && same(start, "shift", 5)) out->mods |= VTC_SHIFT;
|
||||
else if (n == 3 && same(start, "alt", 3)) out->mods |= VTC_ALT;
|
||||
else if (n == 5 && same(start, "altgr", 5)) out->mods |= VTC_RALT;
|
||||
else if (n == 1 && (*start == 'F' || *start == 'f')) out->base = VTC_BASE_F1;
|
||||
else if (n == 1 && *start == '1') out->base = VTC_BASE_1;
|
||||
else return 0;
|
||||
|
||||
if (*p == '+') p++;
|
||||
}
|
||||
// A base with no modifiers would bind a bare function key, which every
|
||||
// full-screen program wants. Refuse it here rather than in the kernel, so
|
||||
// the message can say why.
|
||||
if (out->base && !out->mods)
|
||||
return 0;
|
||||
if (out->mods && !out->base)
|
||||
out->base = VTC_BASE_F1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
show_chord(const struct vt_chord *c)
|
||||
{
|
||||
if (c->mods == 0) {
|
||||
puts("off (use chvt N)\n");
|
||||
return;
|
||||
}
|
||||
if (c->mods & VTC_CTRL) puts("Ctrl+");
|
||||
if (c->mods & VTC_ALT) puts("Alt+");
|
||||
if (c->mods & VTC_SHIFT) puts("Shift+");
|
||||
if (c->mods & VTC_RALT) puts("AltGr+");
|
||||
if (c->base == VTC_BASE_F1) puts("F1..F6\n");
|
||||
else puts("1..6\n");
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
|
|
@ -56,6 +137,21 @@ main(int argc, char *argv[])
|
|||
exit();
|
||||
}
|
||||
|
||||
if (argc == 3 && argv[1][0] == '-' && argv[1][1] == 'c') {
|
||||
struct vt_chord c;
|
||||
if (!parse_chord(argv[2], &c)) {
|
||||
puts("chvt: bad chord -- try ctrl+shift+F, alt+1, or off\n");
|
||||
exit();
|
||||
}
|
||||
if (ioctl(fd, VT_SETCHORD, &c) < 0) {
|
||||
puts("chvt: kernel refused that chord\n");
|
||||
exit();
|
||||
}
|
||||
puts("chord set to ");
|
||||
show_chord(&c);
|
||||
exit();
|
||||
}
|
||||
|
||||
if (argc == 1) {
|
||||
struct vt_stat st;
|
||||
if (ioctl(fd, VT_GETSTATE, &st) < 0) {
|
||||
|
|
@ -71,6 +167,11 @@ main(int argc, char *argv[])
|
|||
putn(i);
|
||||
}
|
||||
puts("\n");
|
||||
struct vt_chord c;
|
||||
if (ioctl(fd, VT_GETCHORD, &c) == 0) {
|
||||
puts("chord: ");
|
||||
show_chord(&c);
|
||||
}
|
||||
exit();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue