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
120 lines
3.9 KiB
C
120 lines
3.9 KiB
C
#include "types.h"
|
|
#include "x86.h"
|
|
#include "defs.h"
|
|
#include "kbd.h"
|
|
#include "posix.h"
|
|
#include "vt.h" /* NVT -- how many terminals the chords reach */
|
|
#include "input.h"
|
|
|
|
// The chord, settable at run time through VT_SETCHORD.
|
|
//
|
|
// AltGr+F1..F6 by default. Every other candidate is taken by the host, by
|
|
// applications, or by the keyboard layout -- see include/kbd.h -- and AltGr
|
|
// with a function key is taken by none of the three: a Polish layout wants
|
|
// AltGr+*letter*, which still reaches the shell untouched, and no window
|
|
// manager binds AltGr with an F-key.
|
|
//
|
|
// AltGr+minus and AltGr+equals step to the previous and next terminal on the
|
|
// same modifiers, for when you have two and do not care which number they are.
|
|
//
|
|
// Zero modifiers disables all of it; `chvt N` still works.
|
|
uint vt_chord_mods = RALT;
|
|
uint vt_chord_base = SCAN_F1;
|
|
|
|
int
|
|
kbdgetc(void)
|
|
{
|
|
static uint shift;
|
|
static uchar *charcode[4] = {
|
|
normalmap, shiftmap, ctlmap, ctlmap
|
|
};
|
|
uint st, data, c;
|
|
|
|
st = inb(KBSTATP);
|
|
if((st & KBS_DIB) == 0)
|
|
return -1;
|
|
|
|
if(st & 0x20)
|
|
return -1;
|
|
|
|
data = inb(KBDATAP);
|
|
|
|
if(data == 0xE0){
|
|
shift |= E0ESC;
|
|
return 0;
|
|
}
|
|
|
|
// Feed /dev/input/event0 before the byte is turned into a character. This
|
|
// has to happen here rather than in a second handler because the byte can
|
|
// only be read from the port once -- whoever reads it owes it to everyone
|
|
// else. The console path below is untouched: both consumers see every key.
|
|
input_ps2_scancode(data, (shift & E0ESC) != 0);
|
|
|
|
// While something holds an exclusive grab on the keyboard, the console gets
|
|
// nothing. The shift/caps state below is still tracked, so releasing the
|
|
// grab does not leave the line discipline believing a modifier is held.
|
|
if(input_is_grabbed(INPUT_KBD)){
|
|
if(data & 0x80){
|
|
data = (shift & E0ESC ? data : data & 0x7F);
|
|
shift &= ~(shiftcode[data] | E0ESC);
|
|
} else {
|
|
shift |= shiftcode[data];
|
|
shift ^= togglecode[data];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if(data & 0x80){
|
|
data = (shift & E0ESC ? data : data & 0x7F);
|
|
shift &= ~(shiftcode[data] | E0ESC);
|
|
return 0;
|
|
} else if(shift & E0ESC){
|
|
data |= 0x80;
|
|
shift &= ~E0ESC;
|
|
}
|
|
|
|
shift |= shiftcode[data];
|
|
shift ^= togglecode[data];
|
|
|
|
// The switch chord, before the character tables, because there is no
|
|
// character to look up: it is a combination that means "show me a different
|
|
// terminal". What it means is decided in console.c, which is where the
|
|
// terminals are; this only 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
|
|
// 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
|
|
// Alt, and nothing else held.
|
|
if (vt_chord_mods != 0 &&
|
|
(shift & (CTL | SHIFT | ALT | RALT)) == vt_chord_mods) {
|
|
if (data >= vt_chord_base && data < vt_chord_base + NVT)
|
|
return KEY_VT_SWITCH + (data - vt_chord_base);
|
|
if (data == SCAN_MINUS) return KEY_VT_PREV;
|
|
if (data == SCAN_EQUALS) return KEY_VT_NEXT;
|
|
}
|
|
|
|
c = charcode[shift & (CTL | SHIFT)][data];
|
|
if(shift & CAPSLOCK){
|
|
if('a' <= c && c <= 'z')
|
|
c += 'A' - 'a';
|
|
else if('A' <= c && c <= 'Z')
|
|
c += 'a' - 'A';
|
|
}
|
|
return c;
|
|
}
|
|
|
|
void
|
|
kbdintr(void)
|
|
{
|
|
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");
|