// SPDX-License-Identifier: GPL-2.0-or-later // // 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 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 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(); }