60597ae154
Record the two concrete prerequisites for compositor input wiring, found by reading the compositor source: (1) an event-loop restructure — it uses a blocking accept loop, drains keys only reactively, and ClientState stores no UnixStream, so no input source can push wl_keyboard to a client; (2) the desktop cannot run without DRM/Mesa (no /scheme/drm/card0 on mini), so the wiring is unvalidatable until the Mesa EGL/DRI port lands. Defines the exact wiring for when those clear (read /scheme/evdev, set_vt_mode, real XKB keymap fd, keycode = evdev - 8).
511 lines
35 KiB
Markdown
511 lines
35 KiB
Markdown
# 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: BOOT-VALIDATED on redbear-mini (2026-07-24).** P0 went green
|
||
(acpid compiles) and a fresh `redbear-mini` boot confirmed both critical checks:
|
||
- **No console regression** — reaches `Red Bear login:`, `root` logs in, and
|
||
`ls -la /usr/bin` executes at the shell (console input + command execution
|
||
intact after the keymap-on-console-path-only refactor).
|
||
- **Raw tap live** — `evdevd: consuming raw orbclient::Event from
|
||
/scheme/input/consumer_raw` with no open error; `inputd: scheme:input
|
||
registered`; `seatd: skipped on live-mini` (recipe flip didn't disturb mini).
|
||
|
||
All changes remain **compile-checked** against `x86_64-unknown-redox`. The raw tap,
|
||
VT-mode op, and grab are **purely additive** and leave the working console
|
||
fan-out byte-for-byte unchanged (graphics_vts/grab empty by default) — confirmed
|
||
by the clean login above. Boot-log errors seen (`ls '..'` EINVAL, shell `No child
|
||
processes`, `acpid pci_fd not registered`, upower signal exit) are **pre-existing
|
||
and unrelated** to the input stack (filesystem/relibc/acpi ordering), not
|
||
regressions from this work.
|
||
|
||
---
|
||
|
||
## 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 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`).**
|
||
|
||
> **Blocker analysis (2026-07-24, from reading `redbear-compositor/source/src/main.rs`):**
|
||
> Two concrete prerequisites, neither runtime-testable today:
|
||
> 1. **Compositor event-loop restructure.** The compositor uses a *blocking*
|
||
> `for stream in self.listener.incoming()` accept loop (`:1409`) and drains key
|
||
> events only *reactively* when a client poke arrives (`:2555`). Worse,
|
||
> `ClientState` (`:1145`) **does not store the client's `UnixStream`**, so no
|
||
> input source can push `wl_keyboard` events to the focused client. Real input
|
||
> requires: store per-client stream fds; multiplex the input fd + client
|
||
> sockets (poll loop or a dedicated input-reader thread); proactively send
|
||
> `wl_keyboard.enter/key/modifiers` to the focused client on input arrival.
|
||
> 2. **The desktop cannot run without DRM/Mesa.** The compositor renders via
|
||
> `/scheme/drm/card0`; on mini/QEMU there is no DRM card
|
||
> ([[full-wayland-build-cascade-2026-07]]), so the compositor never starts and
|
||
> nothing here can be validated. This is desktop bring-up, gated on the Mesa
|
||
> EGL/DRI port — **not** a continuation of the input-core work.
|
||
>
|
||
> When those clear, the wiring is: compositor opens `/scheme/evdev` (evdevd's
|
||
> Linux-evdev output — reuses evdevd's orbclient→evdev keycode translation, no
|
||
> re-implementation), calls inputd `set_vt_mode(vt, graphics=true)` to suppress
|
||
> the text console, ships a real XKB keymap fd (replacing the `/dev/null` stub at
|
||
> `:3380`), and dispatches `wl_keyboard` to the focused surface (keycode = evdev
|
||
> code − 8).
|
||
|
||
- **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.**
|