# 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). 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}/`. ## The Linux model (single owner per stage) | Stage | Linux file | Property | |---|---|---| | 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 | 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. ## Current RedBear path (what to fix) ``` 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 ``` 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**. 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). ## Target (Linux-aligned) ``` 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 ``` 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. 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. ## Staged implementation (each stage builds + boots independently; validate on an idle host) 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. 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. ## Validation - 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 - 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.