tty: Alt+1..6 switches terminals, and AltGr is left alone

Alt+F1..F6 was wrong in three separate ways, and the third is the one that
matters most here.

  Alt+F4            the host window manager closes the QEMU window
  Alt+F2            GNOME and KDE open a run dialog
  Ctrl+Alt+F1..F6   the host switches its *own* virtual terminals
  AltGr+anything    Polish diacritics -- a c e l n o s x z with their marks --
                    and on some setups AltGr is delivered as Ctrl+Alt
  Ctrl+Shift+key    widely bound inside applications
  Scroll Lock       not present on many keyboards

Alt+1..6 survives all of it: the number row, so no function key is involved,
and left Alt, so AltGr is untouched.

Right Alt is tracked as its own modifier bit now. It used to share ALT, which
is harmless for typing -- neither is used to look up a character -- and wrong
for chords, because it made AltGr indistinguishable from Alt.

And the test is an equality rather than a mask:

    if ((shift & (CTL | SHIFT | ALT | RALT)) == ALT && ...)

`shift & ALT` fired on Ctrl+Alt+1 and AltGr+1 as well, so a chord aimed at the
host -- or an ordinary Polish keystroke -- would have switched terminals
behind your back. That was a real bug in what shipped yesterday.

Verified with QEMU's sendkey, comparing screendump hashes:

    start (tty1)   86cee2284878
    Ctrl+Alt+2     86cee2284878   identical -- did not fire
    AltGr+2        86cee2284878   identical -- did not fire
    Alt+2          114562804a78   switched
    Alt+1          86cee2284878   back, pixel-identical to the start

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 12:56:34 +02:00
commit 91a6d440d6
6 changed files with 71 additions and 12 deletions

View file

@ -18,7 +18,7 @@ its distribution.
| API | **SchAPI** | The native programming interface — *designed, not built* |
| Compositor | **Schcom** | Owns the display; v0 runs |
Six virtual terminals sit under all of it -- `/dev/tty1`..`tty6`, Alt+F1..F6 --
Six virtual terminals sit under all of it -- `/dev/tty1`..`tty6`, Alt+1..6 --
each with its own screen, line discipline and foreground process group. See
[terminals.md](terminals.md).

View file

@ -1,13 +1,13 @@
<!-- SPDX-License-Identifier: Apache-2.0 -->
# Virtual terminals
Six of them, `/dev/tty1` through `/dev/tty6`, reached with **Alt+F1..Alt+F6**
Six of them, `/dev/tty1` through `/dev/tty6`, reached with **Alt+1 .. Alt+6**
or with `chvt N`. Each one is an independent terminal: its own screen, 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
share a keyboard and a screen.
Alt+F7 is deliberately unused. That is where a display server goes.
A seventh is deliberately not bound. That is where a display server goes.
---
@ -98,7 +98,7 @@ Linux's numbers, because they do Linux's job.
## The keyboard
`kbd.c` recognises Alt+F1..F6 and returns `KEY_ALT_F1 + n`, which is above
`kbd.c` recognises Alt+1..6 and returns `KEY_ALT_F1 + n`, which is above
0xFF on purpose: every value that fits in a byte is already either a character
or a UTF-8 continuation byte, and a chord that collided with one would be typed
into the terminal instead of switching away from it.
@ -106,6 +106,33 @@ into the terminal instead of switching away from it.
What the chord *means* is decided in `console.c`, where the terminals are. The
keyboard driver names it and stops.
### Why the number row, and why left Alt
Everything else was already spoken for:
| | |
|---|---|
| `Alt+F4` | the host window manager closes the QEMU window |
| `Alt+F2` | GNOME and KDE open a run dialog |
| `Ctrl+Alt+F1..F6` | the host switches its **own** virtual terminals |
| `AltGr+`anything | Polish diacritics — ą ć ę ł ń ó ś ź ż — and on some setups AltGr *is* Ctrl+Alt |
| `Ctrl+Shift+`key | widely bound inside applications |
| Scroll Lock | not present on many keyboards |
`Alt+1..6` survives all of it.
The modifier test is an **equality**, not a mask:
```c
if ((shift & (CTL | SHIFT | ALT | RALT)) == ALT && ...)
```
`shift & ALT` would fire on `Ctrl+Alt+1` and on `AltGr+1` too, so a chord aimed
at the host — or an ordinary Polish keystroke — would switch terminals behind
your back. Right Alt is tracked as its own bit (`RALT`) for exactly this
reason; it used to share `ALT`, which is harmless for typing, because neither
is used to look up a character, and wrong for chords.
Input goes to whichever terminal is on screen. That is what makes a terminal a
terminal: the keyboard follows the eyes.