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
364 lines
10 KiB
C
364 lines
10 KiB
C
// SPDX-License-Identifier: Apache-2.0
|
|
// SchOS — POSIX-compatibility types used by kernel syscall handlers.
|
|
#pragma once
|
|
#include "types.h"
|
|
#include "date.h"
|
|
|
|
struct iovec {
|
|
void *iov_base;
|
|
uint64 iov_len;
|
|
};
|
|
|
|
// Linux's IOV_MAX. readv/writev used to cap iovcnt at 16 -- which is legal,
|
|
// since POSIX only requires 16 -- and returned EINVAL above it. But programs
|
|
// are written against the platform they run on, and on Linux that limit is
|
|
// 1024: toybox's `yes` builds a 256-entry iovec and writev()s it in a loop,
|
|
// so `yes | head -2` failed with "Invalid argument" before producing a single
|
|
// byte. Nothing here costs per-entry memory; the count is just a loop bound.
|
|
#define UIO_MAXIOV 1024
|
|
|
|
// Simplified termios (matches Linux x86_64 layout enough for basic use).
|
|
struct termios {
|
|
uint32 c_iflag;
|
|
uint32 c_oflag;
|
|
uint32 c_cflag;
|
|
uint32 c_lflag;
|
|
uint8 c_line;
|
|
uint8 c_cc[19];
|
|
uint32 c_ispeed;
|
|
uint32 c_ospeed;
|
|
};
|
|
|
|
// Terminal flags, with Linux's values. The kernel honours the ones marked
|
|
// below; the rest are accepted and stored so a program that sets them and
|
|
// reads them back sees what it wrote, which is what cfmakeraw() and every
|
|
// line-editing library actually check.
|
|
//
|
|
// c_iflag
|
|
#define IGNBRK 0000001
|
|
#define BRKINT 0000002
|
|
#define IGNPAR 0000004
|
|
#define PARMRK 0000010
|
|
#define INPCK 0000020
|
|
#define ISTRIP 0000040 // honoured: strip the eighth bit
|
|
#define INLCR 0000100 // honoured: NL -> CR on input
|
|
#define IGNCR 0000200 // honoured: drop CR
|
|
#define ICRNL 0000400 // honoured: CR -> NL on input
|
|
#define IUCLC 0001000
|
|
#define IXON 0002000
|
|
#define IXANY 0004000
|
|
#define IXOFF 0010000
|
|
#define IMAXBEL 0020000
|
|
#define IUTF8 0040000
|
|
|
|
// c_oflag
|
|
#define OPOST 0000001 // honoured: enable output processing
|
|
#define OLCUC 0000002
|
|
#define ONLCR 0000004 // honoured: NL -> CR NL on output
|
|
#define OCRNL 0000010
|
|
#define ONOCR 0000020
|
|
#define ONLRET 0000040
|
|
|
|
// c_cflag
|
|
#define CS8 0000060
|
|
#define CREAD 0000200
|
|
#define HUPCL 0002000
|
|
#define CLOCAL 0004000
|
|
|
|
// c_lflag
|
|
#define ISIG 0000001 // honoured: INTR/QUIT/SUSP generate signals
|
|
#define ICANON 0000002 // honoured: line-at-a-time with editing
|
|
#define ECHO 0000010 // honoured
|
|
#define ECHOE 0000020 // honoured: ERASE rubs the character out
|
|
#define ECHOK 0000040
|
|
#define ECHONL 0000100 // honoured: echo NL even with ECHO off
|
|
#define NOFLSH 0000200
|
|
#define TOSTOP 0000400
|
|
#define IEXTEN 0100000
|
|
|
|
// c_cc indices (Linux order). VMIN/VTIME share slots with VEOF/VEOL, which is
|
|
// why they are only meaningful in the mode the other pair is not.
|
|
#define VINTR 0
|
|
#define VQUIT 1
|
|
#define VERASE 2
|
|
#define VKILL 3
|
|
#define VEOF 4
|
|
#define VTIME 5
|
|
#define VMIN 6
|
|
#define VSWTC 7
|
|
#define VSTART 8
|
|
#define VSTOP 9
|
|
#define VSUSP 10
|
|
#define VEOL 11
|
|
#define VREPRINT 12
|
|
#define VDISCARD 13
|
|
#define VWERASE 14
|
|
#define VLNEXT 15
|
|
#define VEOL2 16
|
|
|
|
struct winsize {
|
|
uint16 ws_row;
|
|
uint16 ws_col;
|
|
uint16 ws_xpixel;
|
|
uint16 ws_ypixel;
|
|
};
|
|
|
|
struct rlimit {
|
|
uint64 rlim_cur;
|
|
uint64 rlim_max;
|
|
};
|
|
|
|
// Linux ioctl codes we handle.
|
|
#define TCGETS 0x5401
|
|
#define TCSETS 0x5402
|
|
#define TCSETSW 0x5403
|
|
#define TCSETSF 0x5404
|
|
#define TCFLSH 0x540B
|
|
// TCFLSH queue selectors. Not a pointer -- these are the argument itself.
|
|
#define TCIFLUSH 0
|
|
#define TCOFLUSH 1
|
|
#define TCIOFLUSH 2
|
|
#define TIOCGPGRP 0x540F
|
|
#define TIOCSPGRP 0x5410
|
|
#define TIOCGWINSZ 0x5413
|
|
#define TIOCSWINSZ 0x5414
|
|
#define TIOCSCTTY 0x540E
|
|
|
|
// Virtual terminal control, Linux's numbers. The argument to VT_ACTIVATE and
|
|
// VT_WAITACTIVE is a 1-based terminal number passed by value, not a pointer.
|
|
#define VT_OPENQRY 0x5600 // first free terminal, out through a pointer
|
|
#define VT_GETMODE 0x5601
|
|
#define VT_SETMODE 0x5602
|
|
#define VT_GETSTATE 0x5603 // struct vt_stat
|
|
#define VT_ACTIVATE 0x5606
|
|
#define VT_WAITACTIVE 0x5607
|
|
|
|
// VT_SETMODE modes. VT_AUTO is the kernel switching on its own; VT_PROCESS
|
|
// means a program has taken switching over and the kernel must keep its hands
|
|
// off the keyboard.
|
|
#define VT_AUTO 0x00
|
|
#define VT_PROCESS 0x01
|
|
#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 {
|
|
char mode; // VT_AUTO or VT_PROCESS
|
|
char waitv; // unused here
|
|
short relsig; // signal on release -- not delivered yet
|
|
short acqsig; // signal on acquire -- not delivered yet
|
|
short frsig; // unused
|
|
};
|
|
|
|
struct vt_stat {
|
|
uint16 v_active; // active terminal, 1-based
|
|
uint16 v_signal; // unused; Linux keeps it for VT_SETMODE
|
|
uint16 v_state; // bitmask of terminals in use, bit 1 = tty1
|
|
};
|
|
#define TIOCGSERIAL 0x541E
|
|
#define FIONREAD 0x541B
|
|
// FICLONE: share another file's data blocks with this one. The argument is a
|
|
// file descriptor, not a pointer. Linux's value, so `cp --reflink` works.
|
|
#define FICLONE 0x40049409
|
|
#define TIOCEXCL 0x540C
|
|
#define TIOCNXCL 0x540D
|
|
|
|
// arch_prctl commands
|
|
#define ARCH_SET_GS 0x1001
|
|
#define ARCH_SET_FS 0x1002
|
|
#define ARCH_GET_FS 0x1003
|
|
#define ARCH_GET_GS 0x1004
|
|
|
|
// clone flags (subset)
|
|
#define CLONE_VM 0x00000100
|
|
#define CLONE_THREAD 0x00010000
|
|
#define CLONE_SIGHAND 0x00000800
|
|
|
|
// futex ops
|
|
#define FUTEX_WAIT 0
|
|
#define FUTEX_WAKE 1
|
|
#define FUTEX_WAIT_PRIVATE 128
|
|
#define FUTEX_WAKE_PRIVATE 129
|
|
|
|
// Resource limit codes (POSIX)
|
|
#define RLIMIT_AS 9
|
|
#define RLIMIT_STACK 3
|
|
#define RLIMIT_NOFILE 7
|
|
|
|
// clock IDs
|
|
#define CLOCK_REALTIME 0
|
|
#define CLOCK_MONOTONIC 1
|
|
|
|
struct timespec {
|
|
long tv_sec;
|
|
long tv_nsec;
|
|
};
|
|
|
|
struct itimerval {
|
|
struct timeval it_interval;
|
|
struct timeval it_value;
|
|
};
|
|
|
|
struct rusage {
|
|
struct timeval ru_utime;
|
|
struct timeval ru_stime;
|
|
long ru_maxrss;
|
|
long ru_ixrss;
|
|
long ru_idrss;
|
|
long ru_isrss;
|
|
long ru_minflt;
|
|
long ru_majflt;
|
|
long ru_nswap;
|
|
long ru_inblock;
|
|
long ru_oublock;
|
|
long ru_msgsnd;
|
|
long ru_msgrcv;
|
|
long ru_nsignals;
|
|
long ru_nvcsw;
|
|
long ru_nivcsw;
|
|
};
|
|
|
|
struct tms {
|
|
long tms_utime;
|
|
long tms_stime;
|
|
long tms_cutime;
|
|
long tms_cstime;
|
|
};
|
|
|
|
struct sysinfo {
|
|
long uptime;
|
|
uint64 loads[3];
|
|
uint64 totalram;
|
|
uint64 freeram;
|
|
uint64 sharedram;
|
|
uint64 bufferram;
|
|
uint64 totalswap;
|
|
uint64 freeswap;
|
|
uint16 procs;
|
|
uint16 pad;
|
|
uint32 pad2;
|
|
uint64 totalhigh;
|
|
uint64 freehigh;
|
|
uint32 mem_unit;
|
|
char _f[8];
|
|
};
|
|
|
|
// Must match Linux's x86-64 struct statfs field for field, because the programs
|
|
// reading it were compiled against Linux's.
|
|
//
|
|
// f_fsid is the trap. Linux's fsid_t is `struct { int val[2]; }` -- eight bytes
|
|
// in total, not two eight-byte words -- and declaring it as int64[2] made this
|
|
// struct one slot too long from f_fsid onwards. Everything after it landed in
|
|
// the wrong place: userland read f_frsize out of f_namelen and got 255, so df
|
|
// computed its block counts in units of 255 bytes and reported a 32 MB root
|
|
// filesystem as 1992 KB. It went unnoticed for as long as it did because every
|
|
// field here used to be an invented constant, so the numbers were meaningless
|
|
// whichever slot they came from.
|
|
struct statfs {
|
|
int64 f_type;
|
|
int64 f_bsize;
|
|
int64 f_blocks;
|
|
int64 f_bfree;
|
|
int64 f_bavail;
|
|
int64 f_files;
|
|
int64 f_ffree;
|
|
int32 f_fsid[2];
|
|
int64 f_namelen;
|
|
int64 f_frsize;
|
|
int64 f_flags;
|
|
int64 f_spare[4];
|
|
};
|
|
|
|
// at-syscall dirfd sentinel
|
|
#define AT_FDCWD (-100)
|
|
#define AT_REMOVEDIR 0x200
|
|
#define AT_SYMLINK_NOFOLLOW 0x100
|
|
#define AT_EMPTY_PATH 0x1000
|
|
#define AT_NO_AUTOMOUNT 0x800
|
|
|
|
// prctl options
|
|
#define PR_SET_NAME 15
|
|
#define PR_GET_NAME 16
|
|
#define PR_SET_DUMPABLE 4
|
|
#define PR_GET_DUMPABLE 3
|
|
|
|
// signal numbers (Linux x86_64 values), used by kill dispatch and
|
|
// default-action classification in sigsend()
|
|
#define SIGHUP 1
|
|
#define SIGINT 2
|
|
#define SIGQUIT 3
|
|
#define SIGILL 4
|
|
#define SIGABRT 6
|
|
#define SIGBUS 7
|
|
#define SIGFPE 8
|
|
#define SIGKILL 9
|
|
#define SIGUSR1 10
|
|
#define SIGSEGV 11
|
|
#define SIGUSR2 12
|
|
#define SIGPIPE 13
|
|
#define SIGALRM 14
|
|
#define SIGTERM 15
|
|
#define SIGCHLD 17
|
|
#define SIGCONT 18
|
|
#define SIGSTOP 19
|
|
// Job-control and the remaining standard signals. A shell needs the TSTP/
|
|
// TTIN/TTOU trio to implement ^Z and background-job terminal access at all;
|
|
// they were simply absent, so any reference to them failed to compile.
|
|
#define SIGTRAP 5
|
|
#define SIGTSTP 20
|
|
#define SIGTTIN 21
|
|
#define SIGTTOU 22
|
|
#define SIGURG 23
|
|
#define SIGXCPU 24
|
|
#define SIGXFSZ 25
|
|
#define SIGVTALRM 26
|
|
#define SIGPROF 27
|
|
#define SIGWINCH 28
|
|
#define SIGIO 29
|
|
#define SIGPWR 30
|
|
#define SIGSYS 31
|
|
|
|
// getrusage who
|
|
#define RUSAGE_SELF 0
|
|
#define RUSAGE_CHILDREN (-1)
|
|
|
|
// fallocate mode
|
|
#define FALLOC_FL_KEEP_SIZE 1
|
|
|
|
// poll(2) event bits (Linux values)
|
|
#define POLLIN 0x001
|
|
#define POLLPRI 0x002
|
|
#define POLLOUT 0x004
|
|
#define POLLERR 0x008
|
|
#define POLLHUP 0x010
|
|
#define POLLNVAL 0x020
|
|
|
|
struct pollfd {
|
|
int fd;
|
|
short events;
|
|
short revents;
|
|
};
|