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:
Nexoniarz 2026-09-13 13:13:38 +02:00
commit 7c0f7c7d01
8 changed files with 256 additions and 60 deletions

View file

@ -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();
} }

View file

@ -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).

View file

@ -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:

View file

@ -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;

View file

@ -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 full-screen programs
// F1..F6 alone help, and whatever a full-screen program wants
// //
// 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 - '@')

View file

@ -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

View file

@ -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");

View file

@ -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;