The sweet spot was available the whole time and I ruled it out by writing
"AltGr+anything" when the true statement is "AltGr+*letter*".
A Polish layout wants AltGr with a letter -- ą ć ę ł ń ó ś ź ż. AltGr with a
function key produces nothing on any layout, no window manager binds it, and
no application asks for it. It is the one combination the host, applications
and the keyboard layout all leave alone, and the previous three commits talked
themselves out of it on a technicality that was mine, not the hardware's.
Verified rather than argued, with QEMU sendkey and screendump hashes:
AltGr+F2 -> tty2
AltGr+F1 -> back, pixel-identical to the start
AltGr+a -> reaches zsh, so ą still types
That distinction is only expressible because Right Alt became its own modifier
bit two commits ago. Sharing ALT is harmless for typing -- neither is used to
look up a character -- and made this impossible to say.
Also added, since it was asked for in the same breath: minus and equals step
to the previous and next terminal, on whatever the chord's modifiers are. They
skip terminals that are not in use -- the same `opened` count VT_GETSTATE
reports -- because six terminals with two shells on them means four blank
screens in between, and cycling past those is worse than not having the key.
AltGr+= -> tty2, again -> wraps to tty1 (only two in use)
AltGr+- -> tty2
chvt -c still moves all of it, and `chvt` reports both halves.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
203 lines
6 KiB
C
203 lines
6 KiB
C
// 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. 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 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 altgr+F AltGr+F1..F6 (the default)
|
|
// chvt -c ctrl+shift+F Ctrl+Shift+F1..F6
|
|
// chvt -c alt+1 Alt+1..6
|
|
// chvt -c off no chord at all
|
|
//
|
|
// Whatever the modifiers are, they also bind minus and equals to the previous
|
|
// and next terminal in use -- AltGr+- and AltGr+= by default.
|
|
|
|
#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;
|
|
}
|
|
|
|
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");
|
|
else puts("1..6");
|
|
puts(", and ");
|
|
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+");
|
|
puts("- / + to step\n");
|
|
}
|
|
|
|
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 == 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) {
|
|
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");
|
|
struct vt_chord c;
|
|
if (ioctl(fd, VT_GETCHORD, &c) == 0) {
|
|
puts("chord: ");
|
|
show_chord(&c);
|
|
}
|
|
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();
|
|
}
|