docs: input plan — implementation status + console-discipline correction
Record what landed (S1 raw evdev tap, S4-core VT graphics-mode op, S5 seatd local package; all compile-checked, boot-validation P0-gated) and correct the Defect-3 framing: fbcond is a byte-queue + CR/LF + special-key escapes, not a second canonical line discipline (ptyd is the only one), and the console login works — so the old "collapse two disciplines" console surgery is deferred as unnecessary and risky. Grab arbitration + compositor wiring remain gated on the Mesa desktop bring-up.
This commit is contained in:
@@ -1,105 +1,476 @@
|
||||
# Input Stack — Linux-Aligned Consolidation Plan
|
||||
|
||||
**Status:** Design (implementation staged below). Goal: make the console-input path match the
|
||||
Linux/CachyOS model — one keymap owner, one line discipline shared by console and pty — instead of
|
||||
the current duplicated, two-discipline chain that loses typed characters (echoed but not executed).
|
||||
**Status:** Design (no code yet). **Goal:** replace RedBear's ad-hoc, duplicated console-input chain
|
||||
with the architecture every mainstream OS converged on — a **single raw input core**, a **keymap
|
||||
applied exactly once per consumer path**, a **single line discipline** shared by console + pty +
|
||||
serial, and an **exclusive-grab / VT-mode mechanism** that lets a GUI compositor take a seat from the
|
||||
text console cleanly. This fixes the console "echoed-but-not-executed" bug class *and* unblocks a
|
||||
correct `redbear-full` Wayland/KDE desktop, which the current design cannot support.
|
||||
|
||||
CachyOS's input/tty is stock upstream Linux (the `local/reference/cachyos/` tree is packaging +
|
||||
kernel *performance* patches; the input subsystem is unmodified). So the reference model below is
|
||||
`local/reference/linux-7.1/drivers/{input,tty}/`.
|
||||
This revision is grounded in three source audits performed for this doc:
|
||||
- **Linux 7.1** reference (`local/reference/linux-7.1/drivers/{input,tty}/`) — the citable model.
|
||||
- **CachyOS** ISO + package manifest (`local/reference/cachyos/`) — the userspace/session layer on
|
||||
top of stock Linux.
|
||||
- **RedBear base fork + recipes** (`local/sources/base`, `local/recipes/`) — what exists today.
|
||||
|
||||
## The Linux model (single owner per stage)
|
||||
All file:line citations below were verified against those trees.
|
||||
|
||||
| Stage | Linux file | Property |
|
||||
---
|
||||
|
||||
## 0. Executive summary (read this first)
|
||||
|
||||
1. **Every serious OS uses the same shape**: raw device → one raw core → *N* peer handlers, each
|
||||
applying its **own** keymap, with an **exclusive grab** so a GUI can take a device from the
|
||||
console. Linux, FreeBSD, macOS, and Windows all do this (§2). RedBear is the outlier.
|
||||
2. **RedBear's current stack violates this three ways** (§3): the keymap is applied **globally inside
|
||||
the core** (`inputd`), the "evdev" path is a **downstream shim that also competes for a VT** (so it
|
||||
can never be a raw peer tap), and there is **no grab / graphics-mode** anywhere — a GUI cannot take
|
||||
input from the console.
|
||||
3. **The console bug is a symptom** of the same disease: **two line disciplines** (fbcond's ad-hoc
|
||||
input cooking + ptyd's real one) and **double keymap / double CR→LF**. One typed line is processed
|
||||
twice and characters are lost between the two stages.
|
||||
4. **The target** (§4) maps 1:1 onto Linux: `inputd` = raw core + VT muxer (no keymap); a **console
|
||||
keyboard handler** owns the *one* console keymap and feeds **ptyd** (the *one* line discipline);
|
||||
**evdevd** becomes a **raw peer** off the core (not downstream of the keymap) feeding
|
||||
libinput→compositor (xkbcommon keymap); a **grab/VT-mode** control op is the RedBear equivalent of
|
||||
`EVIOCGRAB` + `KDSKBMODE(K_OFF)` + `VT_SETMODE(VT_PROCESS)`.
|
||||
5. **This is bulletproof because it is not novel** — it is the converged design, plus an explicit
|
||||
conformance checklist (§7) and a per-stage, independently-bootable rollout (§6).
|
||||
|
||||
---
|
||||
|
||||
## 0b. Implementation status (2026-07-24)
|
||||
|
||||
Grounded in a full read of the actual code (not just the audit), two things changed
|
||||
the plan in flight and are reflected below:
|
||||
|
||||
- **The console is NOT a second *canonical* line discipline.** fbcond's `input()`
|
||||
(`text.rs:131-166`) is a byte-queue with CR→LF plus special-key **escape
|
||||
sequences** (its `keymap.lookup` returns `ESC [`/`ESC O` for arrows/F-keys and
|
||||
falls back to inputd's printable `character`) — it does **no** cooking, echo, or
|
||||
canonical buffering. The one canonical discipline is ptyd. And per memory
|
||||
([[brush-login-pty-slave-input-blocker]]) the "echoed-not-executed" bug was
|
||||
root-caused to brush's own backend and **fixed** — the console login works. So
|
||||
the old "collapse two disciplines" console surgery is **not required** and is
|
||||
**deferred** (it would rip apart a working boot-critical path with no boot-test).
|
||||
- **The real, high-value defect was the evdev/GUI path** — now fixed additively
|
||||
without touching the console.
|
||||
|
||||
| Stage | What | Status |
|
||||
|---|---|---|
|
||||
| Raw input pipeline | `drivers/input/input.c` (`input_pass_values`) | one driver→core→handler pipeline |
|
||||
| **Keymap → bytes** | `drivers/tty/vt/keyboard.c` (`put_queue`, `to_utf8`, `applkey`) | **applied ONCE**; emits the full byte stream incl. escape sequences for special keys |
|
||||
| **Line discipline** | `drivers/tty/n_tty.c` (`read_buf` cooked, `echo_buf`, ICRNL, ISIG) | **ONE module**: canonical, echo, cooked buffer, CR/LF, signal keys |
|
||||
| Console vs pty | `drivers/tty/pty.c` (`tty_port`, shared ldisc) | pty is a thin bridge that **reuses `n_tty`** — no second discipline |
|
||||
| **1** | inputd raw evdev peer tap (`consumer_raw`, pre-keymap, all-VT); evdevd reads it | **DONE** — base `bd5e3db3`, main `8597609c`; compile-checked x86_64-redox |
|
||||
| **4 (core)** | inputd VT graphics-mode op (`set_vt_mode`, console suppression = RedBear `KDSKBMODE(K_OFF)`) | **DONE** — base `a916a661`, bumped `60946a3c`; compile-checked |
|
||||
| **5 (recipe)** | seatd → local source package (`path=source`) | **DONE** — main `9e67a741` |
|
||||
| **4 (rest)** | exclusive device-grab arbitration (EVIOCGRAB) + VT_PROCESS switch-ack | **TODO** — raw broadcast suffices for one compositor; no consumer to validate yet |
|
||||
| **5 (rest)** | seatd Redox port (compile) + wire `redbear-compositor` input | **BLOCKED** — desktop bring-up, gated on Mesa EGL/DRI ([[full-wayland-build-cascade-2026-07]]) |
|
||||
| ~~2~~ | ~~collapse fbcond input cooking~~ | **DEFERRED / not required** — console is not a 2nd canonical discipline and login works |
|
||||
|
||||
Key invariants Linux guarantees and we currently violate:
|
||||
1. The keymap is applied **exactly once**.
|
||||
2. CR/LF (`ICRNL`/`ONLCR`) is one `termios` policy in **one** place.
|
||||
3. Canonical/echo/cooked lives in **one** buffer pair, and the **console tty and ptys share the
|
||||
same line discipline**. "Echoed but not delivered" is structurally impossible because the reader
|
||||
drains the very buffer the discipline cooked into.
|
||||
**Validation state:** all landed changes are **compile-checked** against
|
||||
`x86_64-unknown-redox` (via the prefix `rust-install` toolchain) but **not
|
||||
boot-validated** — that is gated on **P0 build-green** (`acpi-rs`, operator fixing
|
||||
elsewhere). The changes are structured to be safe until then: the raw tap and
|
||||
VT-mode op are **purely additive** and leave the working console fan-out
|
||||
byte-for-byte unchanged (graphics_vts empty by default).
|
||||
|
||||
## Current RedBear path (what to fix)
|
||||
---
|
||||
|
||||
## 1. The Linux model, with citations (the reference we align to)
|
||||
|
||||
Reference tree: `local/reference/linux-7.1` (VERSION 7.1.0). One hardware event flows:
|
||||
|
||||
```
|
||||
ps2d ─▶ inputd ─▶ fbcond ─▶ getty(bridge) ─▶ ptyd ─▶ pty-slave ─▶ shell
|
||||
keymap keymap+ byte pump FULL line discipline
|
||||
(get_char) CR→LF (canonical/echo/cooked/CR→LF/signals)
|
||||
+ own input
|
||||
queue
|
||||
hardware ─▶ input_event() ─▶ input_pass_values() ─┬─(grab? exclusive)─▶ one handler
|
||||
(raw) input.c:391 input.c:111 │
|
||||
├─▶ evdev handler ─▶ /dev/input/eventN ─▶ libinput ─▶ compositor (xkbcommon keymap)
|
||||
│ evdev.c:291 [GUI path]
|
||||
└─▶ console keyboard ─▶ put_queue ─▶ n_tty ─▶ tty/pty ─▶ getty/shell
|
||||
keyboard.c:1423 (keymap ONCE) n_tty.c [TEXT path]
|
||||
```
|
||||
|
||||
Confirmed wiring (`local/sources/userutils/src/bin/getty.rs`):
|
||||
- getty opens `/scheme/fbcon/{vt}` (fbcond) **and** `/scheme/pty/ptmx` (ptyd), then byte-pumps
|
||||
console↔pty; login/shell stdin = pty **slave**.
|
||||
### 1a. Raw core + fan-out + exclusive grab — `drivers/input/input.c`
|
||||
- `input_event()` (`:391`) is the driver entry; batched values reach `input_pass_values()` (`:111`).
|
||||
- **Fan-out with grab short-circuit** (`:119-133`): under RCU it first checks `dev->grab`; if a
|
||||
handle holds the grab, events go **only** to it (`break`), else every **open** handle on
|
||||
`dev->h_list` gets `handle_events()` in list order. So evdev and the console keyboard are **peer
|
||||
handles off the same raw device** and *both* see every event — unless grabbed.
|
||||
- **The grab** (`input_grab_device()` `:520`, sets `rcu_assign_pointer(dev->grab, handle)` `:528`,
|
||||
`-EBUSY` if already held; release `__input_release_device()` `:535-551` + `synchronize_rcu`).
|
||||
Injection respects it too (`input_inject_event()` `:412-424`).
|
||||
|
||||
Duplication:
|
||||
- **Keymap twice**: inputd `active_keymap.get_char` (`drivers/inputd/src/main.rs:452`) **and** fbcond
|
||||
`self.keymap.lookup` (`drivers/graphics/fbcond/src/text.rs:140`).
|
||||
- **CR→LF twice**: fbcond (`text.rs:164`) **and** ptyd (`ICRNL`, `drivers/ptyd/src/pty.rs:76`).
|
||||
- **Two disciplines**: fbcond has its own input queue/cooking; ptyd has the real one. The bridge
|
||||
between them is where typed characters are lost while bare `\n` survives (the reported bug:
|
||||
`tlc` echoes but never executes; manual Enters produce empty prompts).
|
||||
### 1b. evdev handler (the GUI feed) — `drivers/input/evdev.c`
|
||||
- Per-client ring buffer `struct evdev_client` (`:40`); `__pass_event()` (`:214`) writes one
|
||||
`input_event`, and on **overrun drops all unconsumed + synthesizes `SYN_DROPPED`** (`:220-235`) —
|
||||
the client-visible contract for lost events.
|
||||
- `evdev_events()` (`:291`) is the registered `handle_events`; mirrors grab at the client layer.
|
||||
- Read to `/dev/input/eventN`: `evdev_read()` (`:556`). Two-level grab: `EVIOCGRAB` ioctl (`:1084`) →
|
||||
`evdev_grab()` (`:332`) takes the **core** grab then the **client** grab; `EVIOCREVOKE` (`:1090`).
|
||||
|
||||
## Target (Linux-aligned)
|
||||
### 1c. Console keyboard — keymap applied **exactly once** — `drivers/tty/vt/keyboard.c`
|
||||
- `kbd_keycode()` (`:1423`) is the single translation site: `key_map = key_maps[shift_final]`
|
||||
(`:1499`), the one lookup `keysym = key_map[keycode]` (`:1527`), dispatch `(*k_handler[type])`
|
||||
(`:1565`).
|
||||
- **Escape sequences for special keys**: `applkey()` (`:338`) emits `ESC O x` / `ESC [ x`; cursor keys
|
||||
`k_cursor()` use `cur_chars="BDCA"` (`:796`) with modifier form `ESC [ 1 ; N X`; keypad `k_pad()`
|
||||
(`:800`). UTF-8 via `to_utf8()` (`:353`); the sink `put_queue()` (`:326`) pushes bytes **into the
|
||||
tty flip buffer** — i.e. into the line discipline.
|
||||
- **Suppression when a GUI owns the VT**: `raw_mode = (kbd->kbdmode == VC_RAW)` (`:1450`); the gate at
|
||||
`:1562-1563` returns before `put_queue` when `VC_RAW`/`VC_OFF`. A compositor sets this via
|
||||
`KDSKBMODE(K_OFF/K_RAW)` → `vt_do_kdskbmode()` (`:1869`). Result: **console keymap + n_tty are
|
||||
silenced for that VT.**
|
||||
|
||||
### 1d. The one line discipline — `drivers/tty/n_tty.c`
|
||||
- Registered `n_tty_ops` (`:2500`, `.num = N_TTY`, `.name = "n_tty"`) — the kernel **default for every
|
||||
tty**; `tty_ldisc.c` falls back to it on any failure. `n_tty_inherit_ops()` (`:2525`) lets
|
||||
subclasses reuse it rather than write a second discipline.
|
||||
- Cooked buffer `n_tty_data.read_buf` (`:84`/`:106`). Special processing
|
||||
`n_tty_receive_char_special()` (`:1330`): **ISIG** (`:1338-1349`, INTR/QUIT/SUSP→signals via
|
||||
`__isig` `:1044`), **ICRNL/IGNCR/INLCR** (`:1356-1362`). Canonical editing
|
||||
`n_tty_receive_char_canon()` (`:1245`): ERASE/KILL→`eraser()` (`:925`), EOF (`:1296`). Output
|
||||
`process_output()` (`:476`): **ONLCR/OCRNL** (`:414`/`:426`).
|
||||
|
||||
### 1e. pty reuses n_tty — `drivers/tty/pty.c`
|
||||
- `pty_write()` (`:109`) just shoves bytes into the peer's flip buffer; slave uses
|
||||
`init_termios = tty_std_termios` (`:570`) and the default **N_TTY**. A shell on a pty gets the
|
||||
**same** ISIG/ICRNL/echo/canonical as the console — **there is no second discipline.**
|
||||
|
||||
### 1f. VT ownership handshake — `drivers/tty/vt/vt_ioctl.c`
|
||||
- `KDSETMODE(KD_GRAPHICS)` (`:376`→`vt_kdsetmode` `:252`) releases the framebuffer; `KDSKBMODE`
|
||||
(`:394`) silences the keyboard (§1c). `VT_SETMODE(VT_PROCESS)` (`:754`) makes VT switches
|
||||
**handshake through the owning process** via signals; `change_console()` (`:1198`) defers the switch
|
||||
(`:1221-1231`) until `VT_RELDISP`→`vt_reldisp()` (`:553`)→`complete_change_console()` (`:1124`). A
|
||||
graphics VT in AUTO mode blocks switches outright (`:1259`).
|
||||
- `uinput` (`drivers/input/misc/uinput.c`) is the **reverse** path: userspace injects synthetic
|
||||
devices into the core — the inverse of evdev.
|
||||
|
||||
**Linux invariants we must inherit:** (1) keymap applied **once per path**; (2) CR/LF is one termios
|
||||
policy in **one** place; (3) **one** line discipline for console + pty + serial; (4) evdev and console
|
||||
keyboard are **peer taps of the raw core**, not chained; (5) a GUI takes a device via **exclusive
|
||||
grab** + puts the VT in **graphics/keyboard-off** mode.
|
||||
|
||||
---
|
||||
|
||||
## 2. Cross-OS convergence — why this is best-in-class, not just "Linux-like"
|
||||
|
||||
Every production OS independently arrived at *raw core → per-path keymap → exclusive grab*. RedBear is
|
||||
the only one that applies the keymap in the shared core and lacks a grab.
|
||||
|
||||
| OS | Raw core | Console/text keymap | GUI keymap | Grab / mode-switch |
|
||||
|---|---|---|---|---|
|
||||
| **Linux** | `input.c` core, evdev + kbd are peer handles | `vt/keyboard.c` (kernel keymap) → `n_tty` | libinput→**xkbcommon** in compositor | `EVIOCGRAB` + `KDSKBMODE(K_OFF)` + `VT_PROCESS` |
|
||||
| **FreeBSD** | `evdev` core + `vt(4)`/newcons; `kbdmux`/`sysmouse` | `kbdmux`→`vt` keymap → tty ldisc | libinput (evdev) → **xkbcommon** | `EVIOCGRAB` (evdev clone) + `VT_SETMODE`/`KDSKBMODE` (vt_ioctl compat) |
|
||||
| **macOS** | `IOHIDFamily` (kernel) → `IOHIDSystem` | Text terminal via `Terminal.app` over pty; no VT keymap in kernel for GUI | **WindowServer**/HIToolbox+TIS keymap (`.keylayout`/`uchr`) | HID **seizing** (`IOHIDDeviceOpen(kIOHIDOptionsTypeSeizeDevice)`); WindowServer owns the event stream |
|
||||
| **Windows** | Raw Input Thread (RIT) in `win32k` | Console host (`conhost`) + `n_tty`-like cooked mode in CONDRV | `win32k` layout (`HKL`) per-thread; **Raw Input API** `WM_INPUT` for raw | `RegisterRawInputDevices(RIDEV_EXINPUTSINK/NOLEGACY)`; foreground/session arbitration |
|
||||
| **Redox today** | `inputd` merges producers into **one** stream | keymap applied **in `inputd` (core!)** → fbcond → ptyd | evdevd shim **downstream** of the core keymap | **none** |
|
||||
| **RedBear target** | `inputd` = raw core + VT mux (no keymap) | **console-kbd handler** (one keymap) → **ptyd** (one ldisc) | **evdevd** raw peer → libinput → **xkbcommon** | inputd **grab + VT graphics-mode** control op |
|
||||
|
||||
Takeaways that shape the design:
|
||||
- **The keymap is never in the shared core** in any of these systems — it is always in the *consumer*
|
||||
(kernel VT for text, compositor for GUI). Redox putting it in `inputd` is the root anomaly.
|
||||
- **Two independent keymaps are normal and correct** (console layout vs GUI layout can even differ;
|
||||
CachyOS keeps `vconsole.conf` separate from the xkb layout — §5). One keymap "shared" across both
|
||||
paths is the wrong goal.
|
||||
- **Exclusive grab is universal.** macOS "seize", Windows raw-input sink, Linux/BSD `EVIOCGRAB` — all
|
||||
are the same primitive. RedBear must grow one.
|
||||
- **A pty always reuses the console's line discipline.** No OS ships two disciplines. This is exactly
|
||||
the RedBear console bug.
|
||||
|
||||
---
|
||||
|
||||
## 3. RedBear today — exact inventory and the three defects
|
||||
|
||||
(From the base-fork + recipes audit. Citations verified.)
|
||||
|
||||
### 3a. What exists
|
||||
- **`inputd`** (`local/sources/base/drivers/inputd`) — the only real multiplexer. Registers scheme
|
||||
`input` with paths `producer`/`consumer`/`consumer_bootlog`/`handle`/`control` (`main.rs:682`).
|
||||
- **Producers** (all write to `input`): `ps2d`, `usbhidd`, `i2c-hidd`, `intel-thc-hidd`
|
||||
(`drivers/input/*`).
|
||||
- **Console consumers**: `fbcond` (scheme `fbcon`, `drivers/graphics/fbcond`), `vesad`, `fbbootlogd`.
|
||||
- **`ptyd`** (`local/sources/base/ptyd`) — scheme `pty`, a **full POSIX line discipline** already
|
||||
(§3d). This is the piece already closest to Linux.
|
||||
- **`evdevd`** — **NOT in the base fork**; a recipe at `local/recipes/system/evdevd`. Registers scheme
|
||||
`evdev`, synthesizes kbd/mouse/touchpad, **consumes `/scheme/input/consumer`** (`main.rs:66`),
|
||||
started by `init.d/10_evdevd.service`.
|
||||
- **Seat/session**: `seatd` (recipe `local/recipes/system/seatd`, marked *"TODO not compiled"*),
|
||||
`redbear-sessiond`, `init.d/13_seatd.service` — **packaged, started, but not integrated**.
|
||||
- **Compositor**: `redbear-compositor` (`local/recipes/wayland`) renders via `/scheme/drm/card0`,
|
||||
advertises `wl_seat`, but its **input is stubbed** — `main.rs:2542`: *"Keyboard events dispatched to
|
||||
focused client surface when input daemon is wired."* It opens only `/scheme/drm/card0` + `/dev/null`
|
||||
(keymap fd). Libs `libinput`/`libevdev`/`libxkbcommon` are staged in `local/recipes/libs`.
|
||||
|
||||
### 3b. Defect 1 — keymap applied **globally in the core**
|
||||
`inputd`'s producer `write()` calls `self.active_keymap.get_char(key, shift)` (`main.rs:452`) and
|
||||
stamps `key_event.character` in place (`main.rs:454`), with one global `active_keymap` (`main.rs:71`,
|
||||
default US `:89`, switched via control cmd 2 `:136-148`). **Every downstream consumer receives
|
||||
pre-keymapped events.** This is the anomaly §2 identifies: no other OS applies the keymap in the
|
||||
shared core.
|
||||
|
||||
### 3c. Defect 2 — the "evdev" path is downstream **and** VT-competing (cannot be a raw peer)
|
||||
`evdevd` opens a plain `/scheme/input/consumer` (`main.rs:66`), so inputd (a) hands it
|
||||
**already-keymapped** events (Defect 1), and (b) allocates it **its own VT** and only feeds it while
|
||||
**that VT is active** (`inputd` fan-out is VT-gated, `main.rs:485-490` — *not* a broadcast). So
|
||||
RedBear's evdev can **never** be the passive, always-on raw tap that libinput/xkbcommon require. In
|
||||
Linux terms, evdev has been wired **after** `keyboard.c` and **behind** a VT gate, instead of as a
|
||||
**peer handle off `input.c`**. A correct Wayland stack is impossible on this wiring.
|
||||
|
||||
### 3d. Defect 3 — duplicated console input processing (NOT a second canonical discipline)
|
||||
|
||||
> **Correction (post-code-read):** the original draft called this "two line
|
||||
> disciplines." That overstated it. fbcond does **not** cook, echo, or maintain a
|
||||
> canonical buffer — it is a byte-queue + CR→LF + special-key escape sequences.
|
||||
> The one canonical discipline is ptyd. The "echoed-not-executed" bug was brush's
|
||||
> own backend (fixed). So this is real *duplication* (double keymap/CR→LF) worth
|
||||
> removing eventually, but it is **not** the console-login bug and does not need
|
||||
> the risky surgery now — see §0b.
|
||||
|
||||
- **ptyd** already implements the real discipline: `Pty::input()` (`ptyd/src/pty.rs:41-249`) — cooked
|
||||
buffer (`:12`,`:244`), flush on newline/EOF (`:88-93`), ECHO/ECHOE (`:57-59`,`:83-87`), ICRNL/INLCR/
|
||||
IGNCR (`:53-55`,`:69-79`), ISIG VINTR/VQUIT/VSUSP (`:173-206`), VERASE/VKILL/VWERASE/VREPRINT/VEOF
|
||||
(`:95-170`), OPOST/ONLCR in `output()` (`:251-270`), VMIN/VTIME (`:272-335`).
|
||||
- **fbcond** *also* cooks input: keymap lookup `self.keymap.lookup(sc)` (`text.rs:140`), **CR→LF** for
|
||||
both keyboard (`text.rs:159-165`) and serial-injected input (`text.rs:189-195`), and its own input
|
||||
`VecDeque<u8>` drained by `read()` (`text.rs:174-183`).
|
||||
- **getty** (`local/sources/userutils/src/bin/getty.rs`) byte-pumps `/scheme/fbcon/{vt}` ↔
|
||||
`/scheme/pty/ptmx`; the shell's stdin is the pty **slave**.
|
||||
|
||||
So a keystroke is keymapped in `inputd`, keymapped/CR→LF'd/queued again in `fbcond`, pumped by getty,
|
||||
then run through ptyd's *real* discipline. **Two disciplines + double keymap + double CR→LF.** Bytes
|
||||
are lost between fbcond's ad-hoc cooking and ptyd's canonical cooking — the reported
|
||||
`tlc`-echoes-but-never-executes bug, and the empty-prompt-on-Enter behavior.
|
||||
|
||||
### 3e. Defect 4 — no grab / graphics-mode anywhere
|
||||
Grep for `KD_GRAPHICS|KDSETMODE|VT_SETMODE|EVIOCGRAB|grab` across base + recipes: **nothing**. The
|
||||
display "handoff" (`inputd main.rs:301-305`, fbcond `text.rs:94-129`) transfers the *framebuffer*, not
|
||||
input ownership. A GUI cannot take the keyboard from the console; it can only open another VT and rely
|
||||
on VT-active gating. This is the single largest divergence and the reason the compositor's input is
|
||||
still stubbed (§3a).
|
||||
|
||||
---
|
||||
|
||||
## 4. Target architecture (Linux-aligned, both paths)
|
||||
|
||||
```
|
||||
ps2d ─▶ inputd ─▶ fbcond(display only) ─▶ getty(dumb pipe) ─▶ ptyd ─▶ slave ─▶ shell
|
||||
keymap→ passthrough of the THE single line
|
||||
BYTES already-decoded byte discipline (n_tty)
|
||||
(incl. stream; renders output;
|
||||
escapes) NO keymap, NO CR/LF,
|
||||
NO input cooking
|
||||
┌─▶ console-kbd handler ─▶ ptyd (n_tty) ─▶ getty/shell [TEXT VT]
|
||||
ps2d/usbhidd/i2c-hidd ─▶ inputd ┤ ONE console keymap ONE line discipline
|
||||
(raw producers) (RAW │ (raw scancode→bytes, (canonical/echo/cooked/
|
||||
CORE │ esc seqs; NO CR/LF here) CR/LF/ISIG — the only one)
|
||||
+ VT │
|
||||
mux + └─▶ evdevd (RAW peer) ─▶ /scheme/evdev ─▶ libinput ─▶ compositor [GUI VT]
|
||||
GRAB) raw events, NO keymap (xkbcommon keymap)
|
||||
(tap of the raw stream, not the console consumer)
|
||||
```
|
||||
|
||||
Rules:
|
||||
1. **inputd owns the keymap** and emits the **full byte stream** for each key, including escape
|
||||
sequences for special keys (arrows, F-keys, Home/End) — the `keyboard.c`/`applkey` role. Today it
|
||||
only sets a single `character`; extend it to emit bytes.
|
||||
2. **fbcond is display-only + a raw byte passthrough.** Remove `self.keymap.lookup` and the
|
||||
`text.rs:164` CR→LF. It forwards inputd's decoded bytes verbatim into the console read stream and
|
||||
renders output. It performs **no** cooking, echo, or CR/LF.
|
||||
3. **ptyd is the sole line discipline** (the `n_tty`). Canonical/echo/cooked/CR→LF/signals stay here
|
||||
and **only** here. This is already implemented (`pty.rs:41-249`); it just becomes the single owner.
|
||||
4. **getty stays a dumb byte pipe** (no processing) — matches a terminal emulator feeding a pty.
|
||||
**Component contracts (each maps to a Linux role):**
|
||||
|
||||
Result: keymap once (inputd), CR/LF once (ptyd `termios`), one cooked buffer (ptyd) that the shell
|
||||
drains directly. Structurally identical to Linux → the "echoed but not delivered" class is gone.
|
||||
| Component | Linux analog | Contract |
|
||||
|---|---|---|
|
||||
| `inputd` | `input.c` core | Merge producers → raw stream. **No keymap.** Owns VT set + active-VT switch + the **grab/VT-mode** state machine (§4a). Fans raw events to the active VT's **console handler** *and*, in parallel, to any **grabbing** GUI consumer. |
|
||||
| console-kbd handler | `vt/keyboard.c` | The **one** console keymap: raw scancode+mods → byte stream incl. escape sequences (arrows/F-keys/Home/End, `ESC [`/`ESC O`). Feeds ptyd. Emits **no** CR/LF (that's termios). In RedBear this lives on the console side (in/near fbcond), **not** in the core. |
|
||||
| `ptyd` | `n_tty` + `pty.c` | The **only** line discipline. Console tty, ptys, serial all use it. Already implemented — becomes the sole owner. |
|
||||
| `getty` | agetty | Dumb byte pipe (no processing), like a terminal emulator feeding a pty. |
|
||||
| `evdevd` | `evdev.c` | **Raw peer** off inputd's raw stream (a new `producer`-side tap, *not* the console `consumer`). Emits Linux `input_event`s on `/scheme/evdev`. **No keymap.** Overrun → `SYN_DROPPED` (match Linux contract). |
|
||||
| compositor | KWin | Acquires devices via seat (§4b), reads raw `/scheme/evdev`, applies **its own** xkbcommon keymap, drives VT grab. |
|
||||
|
||||
## Staged implementation (each stage builds + boots independently; validate on an idle host)
|
||||
**Keymap ownership (final):** console keymap in the console-kbd handler; GUI keymap (xkbcommon) in the
|
||||
compositor. **Two keymaps, one per path — never in the core.** (This corrects the earlier draft's
|
||||
"inputd owns the keymap" rule, which reproduced Defect 1.)
|
||||
|
||||
1. **inputd: emit full byte sequences.** Add a keymap `get_bytes(scancode, mods) -> &[u8]` that
|
||||
returns the char OR the escape sequence for special keys (model on `keyboard.c`). Deliver bytes on
|
||||
the consumer stream (or keep events but carry the full byte payload). Keep behavior identical for
|
||||
plain chars first; add escapes next.
|
||||
2. **fbcond: drop keymap + CR/LF, become passthrough.** Remove `self.keymap.lookup` and the CR→LF at
|
||||
`text.rs:164`; forward inputd's bytes verbatim. fbcond keeps only rendering + scrollback + the
|
||||
serial mirror (which should also stop translating CR/LF — leave that to ptyd).
|
||||
3. **Verify one discipline.** Confirm ptyd is the only place doing canonical/echo/cooked/CR→LF; delete
|
||||
fbcond's residual input-cooking paths (`push_input_bytes` CR→LF at `pty.rs`-style sites in fbcond).
|
||||
4. **Regression pass.** Console login on framebuffer AND serial: type a command, confirm execution;
|
||||
test backspace, Ctrl-C (ISIG), arrows (escape sequences), and the serial mirror.
|
||||
### 4a. The missing piece — a grab / VT-mode state machine in `inputd`
|
||||
This is the heart of "bulletproof for the full target." Model it directly on Linux (§1a/§1f):
|
||||
|
||||
Immediate stopgap (independent of this refactor, already partly done): the brush minimal backend
|
||||
reads raw and accumulates a persistent line buffer; extend it to treat **both** `\r` and `\n` as
|
||||
submit so the shell is robust even before the stack is consolidated. This is a shell-side band-aid,
|
||||
not the fix — the fix is stages 1–3.
|
||||
- **New `inputd` control ops** (extend the existing `control` scheme, `main.rs:358-375`):
|
||||
- `GrabDevice(vt)` / `UngrabDevice(vt)` — RedBear `EVIOCGRAB`. While a VT holds a grab, inputd
|
||||
routes that device's raw events **only** to the grabbing consumer and **suspends** the
|
||||
console-kbd→ptyd feed for that VT (RedBear `KDSKBMODE(K_OFF)`).
|
||||
- `SetVtMode(vt, {Text|Graphics})` — RedBear `KDSETMODE`. `Graphics` means "a compositor owns this
|
||||
VT": stop delivering cooked console bytes; deliver raw events to the grabber.
|
||||
- `SetVtSwitch(vt, {Auto|Process})` + `AckVtSwitch(vt)` — RedBear `VT_SETMODE(VT_PROCESS)` +
|
||||
`VT_RELDISP`: a graphics VT can **defer/ack** switches so the compositor releases DRM/input
|
||||
cleanly before the switch completes (Linux `change_console`/`complete_change_console`).
|
||||
- **State per VT**: `{ kbmode: Xlate|Raw|Off, vcmode: Text|Graphics, switchmode: Auto|Process,
|
||||
grab: Option<ConsumerId> }`. Console-kbd delivery is gated on `kbmode==Xlate && vcmode==Text`; raw
|
||||
grab delivery on `grab==Some`. This is exactly the Linux gate at `keyboard.c:1562-1563`.
|
||||
- **Why in inputd**: inputd is the raw core (the `input.c` seat), so the grab/mode state belongs there
|
||||
— the same place Linux keeps `dev->grab` and per-VT `kbd->kbdmode`.
|
||||
|
||||
## Validation
|
||||
### 4b. Seat acquisition — `libseat`/`seatd` (no systemd/logind)
|
||||
- **RedBear packages `seatd`** but the compositor doesn't use it (§3a). The seat manager is
|
||||
**`libseat`/`seatd`** — this is the RedBear seat layer.
|
||||
The compositor calls `libseat` → `seatd`, which opens the `/scheme/evdev` + `/scheme/drm` handles
|
||||
and issues the inputd `GrabDevice`/`SetVtMode(Graphics)` ops on the compositor's behalf. This is the
|
||||
wlroots/COSMIC-on-seatd pattern (used on Linux/BSD *without* systemd-logind), which is the model
|
||||
RedBear follows. **systemd-logind is explicitly out of scope — RedBear has no systemd, and seatd is
|
||||
the whole seat layer.**
|
||||
- **Ownership decision (operator, locked):** `seatd` becomes a **first-class local Red Bear source
|
||||
package** — `local/recipes/system/seatd` with `[source] path = "source"`, identical in kind to its
|
||||
siblings `evdevd` and `redbear-compositor`. **Not** a `local/sources/*` submodule and **not** a
|
||||
`local/patches/seatd/*.patch`. Rationale: we own the implementation and integrate upstream commits
|
||||
*into* our tree on our terms (upstream `jackpot51/seatd` is near-dormant), which is Local Fork
|
||||
Supremacy (`local/AGENTS.md:43-58`, `:114`) — our in-tree source is the complete authoritative
|
||||
implementation. This also resolves the current inconsistency where the recipe fetches
|
||||
`git = jackpot51/seatd` while a full `source/` tree (52 files, incl. `libseat`/`seatd-launch`) is
|
||||
already tracked in-repo but shadowed by the fetch. Trade-off accepted: a flat vendored tree has no
|
||||
shared git ancestry with upstream, so rare future upstream commits are ported by hand rather than a
|
||||
3-way merge — which is exactly the intended direction (we pull upstream in, never the reverse).
|
||||
- The compositor's stubbed input (`redbear-compositor main.rs:2542`) gets wired to: open `seatd` →
|
||||
receive `/scheme/evdev` fds → feed libinput → xkbcommon. No keymap touches this before xkbcommon.
|
||||
|
||||
- Framebuffer screendump (QMP) is ground truth. **One boot proves nothing** (input races); compare
|
||||
across boots on an idle host — external load (opencode) contaminates timing.
|
||||
- Success: type `echo RB=$((21*2))=OK` on the framebuffer console → `RB=42=OK` executes; backspace,
|
||||
Ctrl-C, and arrows behave; serial-console login works the same way.
|
||||
---
|
||||
|
||||
## Risk / rollback
|
||||
## 5. Does this solve input long-term, in ALL situations, incl. the FULL desktop?
|
||||
|
||||
- Boot-critical path; each stage is independently bootable and revertable (one component per stage).
|
||||
- Do not land any stage validated only under external load.
|
||||
- Build is currently blocked on `acpi-rs` (`aml/mod.rs:258` `connect_op_regions` undefined) — none of
|
||||
this can be verified until that compiles.
|
||||
Yes — because it inherits the converged coverage, not a bespoke design. Concretely:
|
||||
|
||||
| Situation | Covered by | Evidence it's the right model |
|
||||
|---|---|---|
|
||||
| Framebuffer console login | console-kbd → ptyd n_tty | the reported bug's home; one discipline ⇒ "echoed-not-executed" is structurally impossible |
|
||||
| Serial console login | serial → ptyd n_tty | same discipline; drop fbcond's serial CR→LF (`text.rs:189-195`) |
|
||||
| Terminal emulator (konsole) | pty via ptyd n_tty | konsole opens a pty — identical to Linux `pty.c` reusing n_tty |
|
||||
| SSH / remote shell | pty via ptyd n_tty | pty reuse |
|
||||
| Wayland/KDE GUI keyboard | evdevd raw → libinput → xkbcommon | needs a **raw peer tap** — works only after Defects 1–3 fixed |
|
||||
| Raw-input games/apps | evdev grab (§4a) | universal grab primitive (§2) |
|
||||
| Layout switch (US/RU/…) | console: console-kbd keymap; GUI: xkbcommon | two independent keymaps is the correct model (CachyOS keeps `vconsole.conf` separate from xkb, §agent-2) |
|
||||
| Multi-VT + switch to GUI and back | inputd VT mux + grab/VT-mode + ack handshake (§4a) | Linux `VT_PROCESS`/`change_console` proven |
|
||||
| Compositor crash while grabbed | inputd drops grab → console-kbd resumes | Linux revokes grab on close (`__input_release_device`) |
|
||||
|
||||
**Long-term viability** rests on: (a) it is the model Linux/BSD/macOS/Windows all use (§2), so every
|
||||
integration guide, and Redox's own Wayland/COSMIC direction, assume exactly this; (b) our own history
|
||||
shows the ad-hoc path doesn't survive — memory records ~17 input regressions, overwhelmingly
|
||||
duplication/ordering faults this structure removes by construction.
|
||||
|
||||
---
|
||||
|
||||
## 6. Bulletproof rollout — staged, each stage boots independently
|
||||
|
||||
**Prerequisites (hard gates):**
|
||||
- **P0. Build green.** `acpi-rs` (`aml/mod.rs:258` `connect_op_regions`) must compile — nothing here is
|
||||
verifiable until then.
|
||||
- **P1. Upstream diff.** Diff `inputd`/`fbcond`/`ptyd` against the Redox base to separate
|
||||
*RedBear-added duplication we simply delete* (e.g. fbcond CR→LF was RedBear-added, per memory) from
|
||||
*upstream behavior we must adapt/upstream-propose*. This picks the convergence tier (§8).
|
||||
- **P2. Idle host.** Input is race-sensitive; validate on framebuffer ground truth without external
|
||||
load (opencode contaminates timing). One boot proves nothing — compare N≥5.
|
||||
|
||||
**Stage 1 — Move the keymap out of the core (fix Defect 1).**
|
||||
- Remove `active_keymap.get_char` stamping from `inputd` (`main.rs:452-454`); inputd emits **raw**
|
||||
scancode+modifier events on the producer→consumer path.
|
||||
- Give the console-kbd handler (initially inside fbcond) the keymap: raw → bytes incl. escape
|
||||
sequences (port `keyboard.c`'s `applkey`/`cur_chars`/`to_utf8` behavior). Plain chars first, escape
|
||||
sequences next.
|
||||
- Boot check: framebuffer console still logs in and types.
|
||||
|
||||
**Stage 2 — Collapse to one line discipline (fix Defect 3).**
|
||||
- Delete fbcond's input cooking: keymap→bytes now happens in the console-kbd handler, and **all**
|
||||
canonical/echo/CR-LF/ISIG happens in **ptyd only**. Remove fbcond CR→LF (`text.rs:159-165`,
|
||||
`:189-195`) and its input `VecDeque` cooking (`text.rs:174-183`); fbcond keeps render + scrollback +
|
||||
serial *output* mirror only.
|
||||
- getty becomes a pure byte pipe (verify no translation).
|
||||
- Boot check: type `echo RB=$((21*2))=OK` on fb console → `RB=42=OK` executes; backspace, Ctrl-C,
|
||||
arrows behave; serial login identical.
|
||||
|
||||
**Stage 3 — Make evdev a raw peer (fix Defect 2).**
|
||||
- Add a raw-tap consumer type to inputd (a `producer`-side mirror, not a VT-gated `consumer`) that is
|
||||
**always-on** and **pre-keymap**. Repoint `evdevd` at it (`main.rs:66`). evdevd applies **no**
|
||||
keymap; overrun → `SYN_DROPPED`.
|
||||
- Boot check: `evdevd` sees events regardless of active VT; console path unaffected.
|
||||
|
||||
**Stage 4 — Grab / VT-mode state machine (fix Defect 4).**
|
||||
- Implement §4a control ops in inputd; gate console-kbd delivery on `kbmode/vcmode`; route grabbed raw
|
||||
events to the grabbing consumer; implement the switch defer/ack handshake.
|
||||
- Boot check: a test client can `GrabDevice`, receive raw events, `Ungrab`, and the console resumes.
|
||||
|
||||
**Stage 5 — Wire the compositor via seat (unblocks `redbear-full`).**
|
||||
- **Promote `seatd` to a local source package** (per §4b decision): flip `recipe.toml [source]` from
|
||||
`git = jackpot51/seatd` to `path = "source"`; remove the leftover nested `source/.git` (so it's a
|
||||
plain vendored tree like evdevd, not an embedded clone) and the stray `target/` in the recipe dir;
|
||||
make it actually compile (currently `#TODO not compiled`).
|
||||
- Wire `redbear-compositor` (`main.rs:2542`) through `libseat`/`seatd` to open `/scheme/evdev`, feed
|
||||
libinput, apply xkbcommon; on VT-enter it `GrabDevice`+`SetVtMode(Graphics)`, on VT-leave it
|
||||
releases.
|
||||
- Boot check: switch to the GUI VT, type into a Wayland client; switch back to the text VT, console
|
||||
still works.
|
||||
|
||||
Each stage is independently bootable and revertable (one component per stage). **Do not land any stage
|
||||
validated only under external load.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Conformance checklist (acceptance — the "bulletproof" bar)
|
||||
|
||||
A change conforms **iff** all hold:
|
||||
1. **One keymap per path.** No keymap in `inputd`. Exactly one console keymap (console-kbd handler);
|
||||
exactly one GUI keymap (xkbcommon). Grep proof: no `get_char` in `inputd`; no `keymap.lookup` in
|
||||
fbcond.
|
||||
2. **One line discipline.** All canonical/echo/CR-LF/ISIG in ptyd; none in fbcond/getty. Grep proof:
|
||||
no `\r`→`\n` and no cooked buffer outside `ptyd/src/pty.rs`.
|
||||
3. **evdev is a raw, always-on peer** (pre-keymap, not VT-gated). evdevd receives events on any active
|
||||
VT; its stream carries raw scancodes, not `character`.
|
||||
4. **Grab is exclusive and revocable.** A grabbing consumer gets events exclusively; console resumes
|
||||
on ungrab/close. Modeled on `input_grab_device`/`__input_release_device`.
|
||||
5. **VT switch handshakes** for graphics VTs (defer + ack), like `VT_PROCESS`/`VT_RELDISP`.
|
||||
6. **Test matrix passes** (§below) on an idle host, N≥5 boots.
|
||||
|
||||
**Test matrix:** fb console login; serial console login; `echo RB=42` executes; backspace; Ctrl-C
|
||||
(ISIG); arrows/F-keys (escape sequences); RU/US layout switch (console) independent of GUI layout;
|
||||
konsole-in-GUI pty; VT switch text↔graphics↔text; compositor crash resumes console.
|
||||
|
||||
---
|
||||
|
||||
## 8. Upstream strategy (the divergence question)
|
||||
|
||||
Ordered by preference; all structured to **converge**, never to grow a permanent lower-quality fork.
|
||||
|
||||
1. **Upstream to Redox (best).** Redox's own direction is Wayland/COSMIC + it already vendors `seatd`
|
||||
and `libinput`/`libxkbcommon` — it has the **same** need for a raw evdev peer and a grab/VT-mode
|
||||
op, and the **same** latent console duplication. The raw-core + single-ldisc + grab design is a fix
|
||||
upstream benefits from. If accepted, divergence → 0. Propose it as: (a) move keymap out of inputd,
|
||||
(b) raw-tap consumer, (c) grab/VT-mode control ops.
|
||||
2. **Compatibility-preserving interim.** During transition keep `inputd` able to serve **both** shapes:
|
||||
a legacy keymapped `consumer` (for any current consumer that expects `character`) **and** the new
|
||||
raw tap. Old upstream/orbital-style consumers keep working via the legacy path; RedBear's console +
|
||||
GUI use the raw split. Minimizes merge-conflict surface; lets us rebase upstream with local
|
||||
adapters, not a hard fork.
|
||||
3. **Documented fork divergence (last resort).** Isolate each component's change so upstream rebases
|
||||
apply around it; record rationale here and in commit messages; re-propose upstream each cycle. This
|
||||
is the base-fork model already in use.
|
||||
|
||||
**Policy fit (`local/AGENTS.md`).** "Red Bear adapts to upstream, prefer the upstream solution when it
|
||||
already solves the same problem." Upstream does **not** currently solve this (its Redox console path
|
||||
duplicates too, and its evdev is a downstream shim), so a forward fix is legitimate — but the
|
||||
prerequisite **P1 upstream diff** must run first to keep the change convergent, and some of what we
|
||||
remove (fbcond CR→LF) is RedBear-added, so the fix *reduces* net divergence.
|
||||
|
||||
---
|
||||
|
||||
## 9. Positives / negatives (honest ledger)
|
||||
|
||||
**Positives**
|
||||
- Fixes the console bug **class** permanently (one discipline ⇒ the failure is structurally
|
||||
impossible).
|
||||
- **Unblocks `redbear-full`** — the compositor's input can finally be wired (raw evdev peer + seat +
|
||||
grab).
|
||||
- Single source of truth per path ⇒ far fewer input regressions (history: ~17, mostly this class).
|
||||
- Portable, documented model; easier onboarding and upstream alignment.
|
||||
- Net **reduces** divergence (removes RedBear-added duplication).
|
||||
|
||||
**Negatives / risks**
|
||||
- Boot-critical, multi-component refactor (inputd, fbcond, ptyd, evdevd, getty, compositor) — high
|
||||
blast radius. Mitigated by the per-stage independently-bootable rollout (§6).
|
||||
- **New surface**: the grab/VT-mode state machine (§4a) is genuinely new code; get the gate wrong and
|
||||
input goes to the wrong consumer or a VT switch wedges. Mitigated by modeling exactly on Linux and
|
||||
the conformance checklist (§7).
|
||||
- Escape-sequence/keymap completeness must not regress special keys (arrows, F-keys, compose, dead
|
||||
keys, RU). Covered by the test matrix.
|
||||
- `seatd` must actually be compiled (currently *"TODO not compiled"*) for Stage 5.
|
||||
- Can't validate until **P0** (build green) and needs **P2** (idle host).
|
||||
|
||||
---
|
||||
|
||||
## 10. Bottom line
|
||||
|
||||
The current stack is not merely "primitive" — it is the one arrangement that no shipping OS uses:
|
||||
keymap in the shared core, evdev chained downstream behind a VT gate, two line disciplines, and no
|
||||
grab. The target is the arrangement **all** of them use. Adopting it fixes the console bug by
|
||||
construction and is the **precondition** for a working Wayland/KDE desktop on RedBear. The rollout is
|
||||
staged, each stage boots, and the acceptance bar (§7) is explicit. **No code until P0–P2 clear and
|
||||
this design is approved.**
|
||||
|
||||
Reference in New Issue
Block a user