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
105
cmd/chvt/main.c
105
cmd/chvt/main.c
|
|
@ -3,12 +3,26 @@
|
||||||
// chvt N -- show virtual terminal N.
|
// chvt N -- show virtual terminal N.
|
||||||
//
|
//
|
||||||
// The same name and the same argument as the Linux tool, because it does the
|
// 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
|
// this from the keyboard; this does it from a script, and from a serial
|
||||||
// console where there are no function keys to press.
|
// console where there are no function keys to press.
|
||||||
//
|
//
|
||||||
// chvt report which terminal is active and which are in use
|
// chvt report the active terminal, those in use, and the chord
|
||||||
// chvt 2 switch to /dev/tty2
|
// 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 "types.h"
|
||||||
#include "user.h"
|
#include "user.h"
|
||||||
|
|
@ -44,6 +58,73 @@ num(const char *s)
|
||||||
return v;
|
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
|
int
|
||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
|
@ -56,6 +137,21 @@ main(int argc, char *argv[])
|
||||||
exit();
|
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) {
|
if (argc == 1) {
|
||||||
struct vt_stat st;
|
struct vt_stat st;
|
||||||
if (ioctl(fd, VT_GETSTATE, &st) < 0) {
|
if (ioctl(fd, VT_GETSTATE, &st) < 0) {
|
||||||
|
|
@ -71,6 +167,11 @@ main(int argc, char *argv[])
|
||||||
putn(i);
|
putn(i);
|
||||||
}
|
}
|
||||||
puts("\n");
|
puts("\n");
|
||||||
|
struct vt_chord c;
|
||||||
|
if (ioctl(fd, VT_GETCHORD, &c) == 0) {
|
||||||
|
puts("chord: ");
|
||||||
|
show_chord(&c);
|
||||||
|
}
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ its distribution.
|
||||||
| API | **SchAPI** | The native programming interface — *designed, not built* |
|
| API | **SchAPI** | The native programming interface — *designed, not built* |
|
||||||
| Compositor | **Schcom** | Owns the display; v0 runs |
|
| Compositor | **Schcom** | Owns the display; v0 runs |
|
||||||
|
|
||||||
Six virtual terminals sit under all of it -- `/dev/tty1`..`tty6`,
|
Six virtual terminals sit under all of it -- `/dev/tty1`..`tty6`, `chvt N`
|
||||||
Ctrl+Alt+Shift+F1..F6 --
|
or a settable chord --
|
||||||
each with its own screen, line discipline and foreground process group. See
|
each with its own screen, line discipline and foreground process group. See
|
||||||
[terminals.md](terminals.md).
|
[terminals.md](terminals.md).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
||||||
# Virtual terminals
|
# Virtual terminals
|
||||||
|
|
||||||
Six of them, `/dev/tty1` through `/dev/tty6`, reached with
|
Six of them, `/dev/tty1` through `/dev/tty6`, reached with `chvt N` or with
|
||||||
**Ctrl+Alt+Shift+F1 .. F6** or with `chvt N`. Each one is an independent terminal: its own screen, its own
|
a keyboard chord — **Ctrl+Shift+F1 .. F6** by default, and settable, for
|
||||||
|
reasons below. Each one is an independent terminal: its own screen, its own
|
||||||
cursor, its own line discipline, its own foreground process group and 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
|
session. Two shells on two terminals are two unrelated processes that happen to
|
||||||
share a keyboard and a screen.
|
share a keyboard and a screen.
|
||||||
|
|
@ -107,49 +108,67 @@ into the terminal instead of switching away from it.
|
||||||
What the chord *means* is decided in `console.c`, where the terminals are. The
|
What the chord *means* is decided in `console.c`, where the terminals are. The
|
||||||
keyboard driver names it and stops.
|
keyboard driver names it and stops.
|
||||||
|
|
||||||
### Why this chord
|
### The chord is a setting, and that is the point
|
||||||
|
|
||||||
**Anything the kernel swallows is taken from every program that will ever run
|
**There is no combination that is free for everyone on every host**, and a
|
||||||
here, permanently.** That is the whole difficulty, and it took two wrong
|
guest cannot find out what its host takes: a key the host swallowed is
|
||||||
answers to see it. The obvious candidates are all spoken for on one side or
|
indistinguishable from a key nobody pressed. Three parties have a claim on
|
||||||
the other:
|
every candidate.
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **The host takes it** | |
|
| **The host takes it, and the guest never sees it** | |
|
||||||
| `Alt+F4` | the window manager closes the QEMU window |
|
| `Alt+F4`, `Alt+F2` | window manager |
|
||||||
| `Alt+F2` | GNOME and KDE open a run dialog |
|
| `Ctrl+Alt+F1..F6` | display manager — and it matches **loosely**, so adding Shift does not get past it |
|
||||||
| `Ctrl+Alt+F1..F6` | the display manager switches its **own** terminals |
|
| `Ctrl+Alt+`digit | QEMU's own console tabs |
|
||||||
| **Applications want it** | |
|
| **Applications want it, and lose it permanently if the kernel eats it** | |
|
||||||
| `Alt+`anything | Meta. zsh's own line editor binds Meta-digit as `digit-argument`; so does every readline editor |
|
| `Alt+`anything | Meta. zsh binds Meta-digit as `digit-argument`; so does every readline editor |
|
||||||
| `Ctrl+`letter | control characters |
|
| `Ctrl+`letter | control characters |
|
||||||
| `Ctrl+Shift+`digit | types a character — `Ctrl+Shift+2` is `^@` |
|
| `Ctrl+Shift+`digit | types a character — `Ctrl+Shift+2` is `^@` |
|
||||||
| `F1..F6` alone | help, and whatever a full-screen program wants |
|
| `F1..F6` alone | help, and whatever a full-screen program wants |
|
||||||
| **The layout wants it** | |
|
| **The keyboard layout wants it** | |
|
||||||
| `AltGr+`letter | Polish diacritics — ą ć ę ł ń ó ś ź ż — and on some setups AltGr *is* Ctrl+Alt |
|
| `AltGr+`letter | Polish diacritics — ą ć ę ł ń ó ś ź ż — and on some setups AltGr arrives as Ctrl+Alt |
|
||||||
|
|
||||||
`Ctrl+Alt+Shift+F1..F6` is in none of those sets. Display managers bind the
|
So the kernel ships a default and lets it be moved:
|
||||||
same chord *without* Shift, so adding it steps out of the host's way, and no
|
|
||||||
terminal application binds a triple-modified function key. Four keys is more
|
|
||||||
than one wants to press, which is right for something done rarely and on
|
|
||||||
purpose — `chvt N` covers everything else.
|
|
||||||
|
|
||||||
Which chords actually reach a guest was **measured, not assumed**: a temporary
|
```sh
|
||||||
|
chvt # report the active terminal and the current chord
|
||||||
|
chvt -c ctrl+shift+F # Ctrl+Shift+F1..F6 (the default)
|
||||||
|
chvt -c ctrl+alt+shift+F # if your host leaves that alone
|
||||||
|
chvt -c alt+1 # Alt+1..6
|
||||||
|
chvt -c off # no chord; chvt N is then the only way
|
||||||
|
```
|
||||||
|
|
||||||
|
`off` is a legitimate setting, not a failure mode. On a machine whose host
|
||||||
|
grabs everything worth binding, `chvt N` from a shell is the honest answer.
|
||||||
|
|
||||||
|
The default is `Ctrl+Shift+F1..F6` because display managers bind `Ctrl+Alt+F<n>`
|
||||||
|
rather than `Ctrl+Shift+F<n>`, and because `Ctrl+Shift` with a *function key*
|
||||||
|
is not a character on any layout — unlike `Ctrl+Shift+`digit, which is.
|
||||||
|
|
||||||
|
Which chords reach a guest at all was **measured, not assumed**: a temporary
|
||||||
probe in `kbdgetc` logged every modified keypress, and QEMU's `sendkey` fired
|
probe in `kbdgetc` logged every modified keypress, and QEMU's `sendkey` fired
|
||||||
each candidate at it. Everything reached the guest, including `Ctrl+Alt+F2` —
|
each candidate at it. Everything arrived, `Ctrl+Alt+F2` included — so that
|
||||||
so the host conflict is the host's display manager, not QEMU.
|
conflict is the host's display manager, not QEMU, and no amount of extra
|
||||||
|
modifiers gets around a grab that matches loosely.
|
||||||
|
|
||||||
The modifier test is an **equality**, not a mask:
|
The modifier test is an **equality**, not a mask:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
if ((shift & (CTL | SHIFT | ALT | RALT)) == (CTL | ALT | SHIFT) && ...)
|
if (vt_chord_mods != 0 &&
|
||||||
|
(shift & (CTL | SHIFT | ALT | RALT)) == vt_chord_mods && ...)
|
||||||
```
|
```
|
||||||
|
|
||||||
A mask would fire on any superset, so a chord aimed at the host would switch
|
A mask fires on any superset, so a chord aimed at the host would switch
|
||||||
terminals behind your back. Right Alt is tracked as its own bit (`RALT`) for
|
terminals behind your back. Right Alt is tracked as its own bit (`RALT`) for
|
||||||
the same reason; it used to share `ALT`, which is harmless for typing — neither
|
the same reason; it used to share `ALT`, which is harmless for typing — neither
|
||||||
is used to look up a character — and wrong for chords.
|
is used to look up a character — and wrong for chords.
|
||||||
|
|
||||||
|
`include/posix.h`'s `VTC_*` bits mirror `include/kbd.h`'s modifier bits, and a
|
||||||
|
`_Static_assert` in `kbd.c` holds them together. 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.
|
||||||
|
|
||||||
### Taking the chord back
|
### Taking the chord back
|
||||||
|
|
||||||
Three layers, in increasing order of how much a program is claiming:
|
Three layers, in increasing order of how much a program is claiming:
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ void cons_flush_input(int minor);
|
||||||
int cons_fg_pgrp(int minor);
|
int cons_fg_pgrp(int minor);
|
||||||
void cons_set_fg_pgrp(int minor, int pg);
|
void cons_set_fg_pgrp(int minor, int pg);
|
||||||
int console_vt_hotkey(int key);
|
int console_vt_hotkey(int key);
|
||||||
|
extern uint vt_chord_mods; // kernel/dev/kbd.c -- the switch chord
|
||||||
|
extern uint vt_chord_base;
|
||||||
struct vt *vt_for_minor(int minor);
|
struct vt *vt_for_minor(int minor);
|
||||||
extern uint lapic_timer_hz; // measured, kernel/arch/x86_64/lapic.c
|
extern uint lapic_timer_hz; // measured, kernel/arch/x86_64/lapic.c
|
||||||
extern uint64 tsc_hz;
|
extern uint64 tsc_hz;
|
||||||
|
|
|
||||||
|
|
@ -36,40 +36,43 @@
|
||||||
#define KEY_INS 0xE8
|
#define KEY_INS 0xE8
|
||||||
#define KEY_DEL 0xE9
|
#define KEY_DEL 0xE9
|
||||||
|
|
||||||
// Ctrl+Alt+Shift+F1..F6 -- switch virtual terminal.
|
// Switch virtual terminal.
|
||||||
//
|
//
|
||||||
// Above 0xFF deliberately. kbdgetc returns an int and consoleintr takes one,
|
// 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
|
// 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 would be typed into
|
// UTF-8 continuation byte. A chord that collided with one would be typed into
|
||||||
// the terminal instead of switching away from it.
|
// the terminal instead of switching away from it.
|
||||||
//
|
//
|
||||||
// Choosing this took two wrong answers. Anything the kernel swallows is taken
|
// *Which* chord is a runtime setting, not a constant here, and that is the
|
||||||
// from every program that will ever run here, permanently, and the obvious
|
// interesting part. Three separate parties have a claim on every candidate:
|
||||||
// candidates are all spoken for on one side or the other:
|
|
||||||
//
|
//
|
||||||
// HOST takes it, before the guest ever sees it
|
// the HOST, which sees the keystroke first and may never pass it on
|
||||||
// Alt+F4 the window manager closes the QEMU window
|
// Alt+F4, Alt+F2 window manager
|
||||||
// Alt+F2 GNOME and KDE open a run dialog
|
// Ctrl+Alt+F1..F6 display manager, and it matches loosely --
|
||||||
// Ctrl+Alt+F1..F6 the display manager switches its *own* terminals
|
// adding Shift does not get past it
|
||||||
|
// Ctrl+Alt+<digit> QEMU's own console tabs
|
||||||
//
|
//
|
||||||
// APPLICATIONS want it, and would never get it again
|
// APPLICATIONS, which lose the key permanently if the kernel eats it
|
||||||
// Alt+<anything> Meta. zsh's own line editor binds Meta-digit as
|
// Alt+<anything> Meta. zsh binds Meta-digit as digit-argument
|
||||||
// digit-argument; every readline editor does too
|
|
||||||
// Ctrl+<letter> control characters
|
// Ctrl+<letter> control characters
|
||||||
// Ctrl+Shift+<digit> types a character -- Ctrl+Shift+2 is ^@
|
// Ctrl+Shift+<digit> types a character -- Ctrl+Shift+2 is ^@
|
||||||
// F1..F6 alone help, and whatever a full-screen program wants
|
// F1..F6 alone help, and full-screen programs
|
||||||
//
|
//
|
||||||
// THE KEYBOARD LAYOUT wants it
|
// the KEYBOARD LAYOUT
|
||||||
// AltGr+<letter> Polish diacritics, and on some setups AltGr is
|
// AltGr+<letter> Polish diacritics, and on some setups AltGr
|
||||||
// delivered as Ctrl+Alt
|
// arrives as Ctrl+Alt
|
||||||
//
|
//
|
||||||
// Ctrl+Alt+Shift+F1..F6 is in none of those sets. Display managers bind the
|
// There is no combination that is free for everyone on every host, and the
|
||||||
// same chord *without* Shift, so adding it steps out of the host's way, and
|
// guest cannot find out what its host grabs -- the keystroke simply never
|
||||||
// no terminal application binds a triple-modified function key. Four keys is
|
// arrives, which is indistinguishable from a chord nobody pressed. So the
|
||||||
// more than one wants to press, which is correct for something done rarely
|
// kernel ships a default and lets it be changed: VT_SETCHORD, and `chvt
|
||||||
// and deliberately -- and `chvt N` is there for everything else.
|
// --chord` in userspace. Setting it to no modifiers disables the chord
|
||||||
|
// entirely and leaves `chvt N` as the only way to switch, which is a
|
||||||
|
// legitimate choice on a machine whose host takes everything.
|
||||||
#define KEY_VT_SWITCH 0x120
|
#define KEY_VT_SWITCH 0x120
|
||||||
|
|
||||||
#define SCAN_F1 0x3B // F1..F6 are scancodes 0x3B..0x40
|
#define SCAN_F1 0x3B // F1..F6 are scancodes 0x3B..0x40
|
||||||
|
#define SCAN_1 0x02 // 1..6 are scancodes 0x02..0x07
|
||||||
|
|
||||||
// C('A') == Control-A
|
// C('A') == Control-A
|
||||||
#define C(x) (x - '@')
|
#define C(x) (x - '@')
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,32 @@ struct rlimit {
|
||||||
#define VT_PROCESS 0x01
|
#define VT_PROCESS 0x01
|
||||||
#define VT_ACKACQ 0x02
|
#define VT_ACKACQ 0x02
|
||||||
|
|
||||||
|
// VT_SETCHORD / VT_GETCHORD -- which key combination the kernel switches on.
|
||||||
|
// SchOS's own, not Linux's: Linux hardcodes Ctrl+Alt+F<n> and nobody has ever
|
||||||
|
// needed to move it, because Linux is the host. A guest does need to move it,
|
||||||
|
// because the right answer depends entirely on what the host grabs first --
|
||||||
|
// and that is not something the guest can discover.
|
||||||
|
#define VT_GETCHORD 0x5620
|
||||||
|
#define VT_SETCHORD 0x5621
|
||||||
|
|
||||||
|
// Modifier bits, matched exactly. These mirror kernel/dev/kbd.h's values so
|
||||||
|
// that userspace and the keyboard driver cannot drift apart.
|
||||||
|
#define VTC_SHIFT 0x01 // kbd.h SHIFT (1<<0)
|
||||||
|
#define VTC_CTRL 0x02 // kbd.h CTL (1<<1)
|
||||||
|
#define VTC_ALT 0x04 // kbd.h ALT (1<<2)
|
||||||
|
#define VTC_RALT 0x80 // kbd.h RALT (1<<7) -- NOT (1<<3); that is
|
||||||
|
// CAPSLOCK, and bits 3..6 are the other locks
|
||||||
|
// and the E0 prefix flag
|
||||||
|
|
||||||
|
// Well-known base scancodes for the `base` field below.
|
||||||
|
#define VTC_BASE_F1 0x3B // F1..F6
|
||||||
|
#define VTC_BASE_1 0x02 // the number row, 1..6
|
||||||
|
|
||||||
|
struct vt_chord {
|
||||||
|
uint16 mods; // exactly these modifiers held, and no others
|
||||||
|
uint16 base; // scancode of the key meaning terminal 1
|
||||||
|
};
|
||||||
|
|
||||||
struct vt_mode {
|
struct vt_mode {
|
||||||
char mode; // VT_AUTO or VT_PROCESS
|
char mode; // VT_AUTO or VT_PROCESS
|
||||||
char waitv; // unused here
|
char waitv; // unused here
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,20 @@
|
||||||
#include "x86.h"
|
#include "x86.h"
|
||||||
#include "defs.h"
|
#include "defs.h"
|
||||||
#include "kbd.h"
|
#include "kbd.h"
|
||||||
|
#include "posix.h"
|
||||||
#include "vt.h" /* NVT -- how many terminals the chords reach */
|
#include "vt.h" /* NVT -- how many terminals the chords reach */
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
|
|
||||||
|
// The chord, settable at run time through VT_SETCHORD. Defaults to
|
||||||
|
// Ctrl+Shift+F1..F6: the host's display manager binds Ctrl+Alt+F<n> and
|
||||||
|
// matches it loosely, so anything containing Ctrl+Alt never reaches a guest,
|
||||||
|
// whatever else is held with it. Ctrl+Shift is not a VT chord on any host and
|
||||||
|
// is not a character on any layout.
|
||||||
|
//
|
||||||
|
// Zero modifiers disables the chord entirely; `chvt N` still works.
|
||||||
|
uint vt_chord_mods = CTL | SHIFT;
|
||||||
|
uint vt_chord_base = SCAN_F1;
|
||||||
|
|
||||||
int
|
int
|
||||||
kbdgetc(void)
|
kbdgetc(void)
|
||||||
{
|
{
|
||||||
|
|
@ -60,19 +71,22 @@ kbdgetc(void)
|
||||||
shift |= shiftcode[data];
|
shift |= shiftcode[data];
|
||||||
shift ^= togglecode[data];
|
shift ^= togglecode[data];
|
||||||
|
|
||||||
// Ctrl+Alt+Shift+F1..F6 before the character tables, because there is no
|
// The switch chord, before the character tables, because there is no
|
||||||
// character to
|
// character to look up: it is a combination that means "show me a different
|
||||||
// look up: it is a chord that means "show me a different terminal". What it
|
// terminal". What it means is decided in console.c, which is where the
|
||||||
// means is decided in console.c, which is where the terminals are; this only
|
// terminals are; this only has to name it. See console_vt_hotkey().
|
||||||
// has to name it. See console_vt_hotkey().
|
//
|
||||||
|
// The modifier test is an equality, not a mask. A mask fires on any superset,
|
||||||
|
// so a chord aimed at the host would switch terminals behind your back.
|
||||||
//
|
//
|
||||||
// The modifier test is an equality, not a mask. `shift & ALT` would fire on
|
// 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
|
// 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 left
|
// Polish keystroke -- would switch terminals behind your back. Exactly left
|
||||||
// Alt, and nothing else held.
|
// Alt, and nothing else held.
|
||||||
if ((shift & (CTL | SHIFT | ALT | RALT)) == (CTL | ALT | SHIFT) &&
|
if (vt_chord_mods != 0 &&
|
||||||
data >= SCAN_F1 && data < SCAN_F1 + NVT)
|
(shift & (CTL | SHIFT | ALT | RALT)) == vt_chord_mods &&
|
||||||
return KEY_VT_SWITCH + (data - SCAN_F1);
|
data >= vt_chord_base && data < vt_chord_base + NVT)
|
||||||
|
return KEY_VT_SWITCH + (data - vt_chord_base);
|
||||||
|
|
||||||
c = charcode[shift & (CTL | SHIFT)][data];
|
c = charcode[shift & (CTL | SHIFT)][data];
|
||||||
if(shift & CAPSLOCK){
|
if(shift & CAPSLOCK){
|
||||||
|
|
@ -89,3 +103,10 @@ kbdintr(void)
|
||||||
{
|
{
|
||||||
consoleintr(kbdgetc);
|
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/posix.h VTC_* must mirror include/kbd.h modifiers");
|
||||||
|
|
|
||||||
|
|
@ -1187,6 +1187,30 @@ addr_t sys_ioctl(void)
|
||||||
vt_for_minor(f->ip->minor)->mode = m->mode;
|
vt_for_minor(f->ip->minor)->mode = m->mode;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
// Which chord the kernel switches on. SchOS's own ioctl -- see the long
|
||||||
|
// comment in include/kbd.h for why this has to be settable at all.
|
||||||
|
if (request == VT_SETCHORD) {
|
||||||
|
struct vt_chord *c = (struct vt_chord*)user_ptr(argp, sizeof(*c));
|
||||||
|
if (!c) return -EFAULT;
|
||||||
|
// Only real modifier bits, and a base that leaves room for NVT keys
|
||||||
|
// below the release flag. A base of 0 with no modifiers is the
|
||||||
|
// documented way to turn the chord off, so it is not an error.
|
||||||
|
if (c->mods & ~(VTC_SHIFT | VTC_CTRL | VTC_ALT | VTC_RALT))
|
||||||
|
return -EINVAL;
|
||||||
|
if (c->mods && (c->base == 0 || c->base + NVT > 0x80))
|
||||||
|
return -EINVAL;
|
||||||
|
vt_chord_mods = c->mods;
|
||||||
|
vt_chord_base = c->base;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (request == VT_GETCHORD) {
|
||||||
|
struct vt_chord *c = (struct vt_chord*)user_ptr(argp, sizeof(*c));
|
||||||
|
if (!c) return -EFAULT;
|
||||||
|
c->mods = (uint16)vt_chord_mods;
|
||||||
|
c->base = (uint16)vt_chord_base;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (request == VT_GETMODE) {
|
if (request == VT_GETMODE) {
|
||||||
struct vt_mode *m = (struct vt_mode*)user_ptr(argp, sizeof(*m));
|
struct vt_mode *m = (struct vt_mode*)user_ptr(argp, sizeof(*m));
|
||||||
if (!m) return -EFAULT;
|
if (!m) return -EFAULT;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue