Files
RedBear-OS/local/docs/INPUT-STACK-LINUX-ALIGNMENT-PLAN.md
T
vasilito fdf377456f 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.
2026-07-24 12:04:17 +09:00

477 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Input Stack — Linux-Aligned Consolidation Plan
**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.
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.
All file:line citations below were verified against those trees.
---
## 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 |
|---|---|---|
| **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 |
**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).
---
## 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:
```
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]
```
### 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`).
### 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`).
### 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)
```
┌─▶ 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)
```
**Component contracts (each maps to a Linux role):**
| 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. |
**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.)
### 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):
- **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`.
### 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.
---
## 5. Does this solve input long-term, in ALL situations, incl. the FULL desktop?
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 13 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 P0P2 clear and
this design is approved.**