fix(plymouth): #137 splash follows the canvas; #145 keyboard hint on the LUKS prompt
All checks were successful
Check / eval (push) Successful in 3m59s
All checks were successful
Check / eval (push) Successful in 3m59s
The logo was off-centre on the external when booting/shutting down docked. Both earlier diagnoses in this repo were WRONG, and that is the part worth keeping: Window.GetWidth() returns max_width (not head 0), and per-head sprites would have DUPLICATED the logo — the script plugin already builds one max_width x max_height canvas, centres every display inside it (script-lib-sprite.c:536) and draws sprites at (sprite.x - display.x), so a canvas-centred sprite is centred on every head. The arithmetic was right. The bug was TIME: every position was a top-level statement evaluated once at parse time. A head arriving/leaving mid-splash resizes the canvas, the plugin re-centres the displays, and frozen sprites end up off by (new_max - old_max)/2 on every head. One monitor never resizes the canvas — hence "only when docked". Fix: one layout(), re-run from the existing refresh callback on canvas change. The trap that cost two attempts (now a comment): in plymouth script a bare assignment inside a function writes the GLOBAL if that name already exists globally. So `canvas_width = Window.GetWidth()` updated global.canvas_width BEFORE the guard compared against it — always false, body never ran, splash rendered as a bare background, NO error logged. Isolated with a 20-line probe. Hence cw/ch. #145 rides along: a VT loads one keymap and knows nothing of per-device layouts, so the passphrase box types with the console layout — worth saying before three wrong tries on a disk you cannot read (same gap as #114, one step earlier). Fedora's mechanism does not port: ply_keymap_icon is a C widget in the two-step plugin (the script plugin has no keyboard API), fed from XKBLAYOUT in /etc/vconsole.conf, which NixOS never writes (plymouth's trace says `XKBLAYOUT: (null)`). So @LAYOUT@ is baked from services.xserver.xkb.layout like the palette — the FIRST of a comma list, since that is what the VT loads. Icon is plymouth's own keyboard.png (the same glyph Fedora shows), copied at build time so no GPL bytes enter the repo, recoloured to subtext. chmod +w after that copy: store files are 444 and recolor rewrites in place. Without it magick fails, the phase aborts, and EVERY LATER SED SILENTLY DOES NOT RUN — the theme shipped with @BG_R@ literals and I only caught it by reading the build log. Verified by render (tools/plymouth-preview.sh, real built theme): both heads centred, password dialog showing padlock + entry + keyboard icon and "us". V3 queued — only a real docked boot can resize a canvas. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
123
agent/BACKLOG.md
123
agent/BACKLOG.md
@@ -177,129 +177,6 @@ migration (state.json created + git-staged, legacy `git rm`'d). Proven by
|
||||
hand 2026-07-15 (drvPaths identical across the rename) — this just keeps it
|
||||
proven. Delete together with the shim + the `nomarchy-theme-sync` alias.
|
||||
|
||||
### 145. Show the keyboard layout on the LUKS prompt (Fedora does this)
|
||||
|
||||
Bernardo 2026-07-16: the splash's password screen should say which layout is
|
||||
loaded. It is the right ask for this machine specifically — a VT has **one**
|
||||
keymap and no concept of per-device layouts, so `settings.keyboard.devices`
|
||||
(his external board remembered as `us`) does **not** apply at the passphrase
|
||||
box; it follows the session layout via `services.xserver.xkb.layout` →
|
||||
`console.useXkbConfig` (`modules/nixos/default.nix:96-102`). That is the same
|
||||
structural gap as **#114** (tuigreet), one step earlier in the boot, and with
|
||||
worse stakes: three wrong tries on a disk you cannot read yet, with no
|
||||
indication that the keyboard is not the one you think.
|
||||
|
||||
**Fedora's mechanism does not port, and the reason matters.** Its label is
|
||||
`ply_keymap_icon` — a **C widget in the two-step plugin**
|
||||
(`plugins/splash/two-step/plugin.c:239`, `libply-splash-graphics/ply-keymap-icon.c`);
|
||||
the **script** plugin has no keyboard API at all (nothing in
|
||||
`script-lib-plymouth.c`). And it is fed from **`XKBLAYOUT` in
|
||||
`/etc/vconsole.conf`**, which **NixOS does not write** — this box has exactly
|
||||
`KEYMAP=/nix/store/…-xkb-console-keymap` and nothing else, so plymouth's own
|
||||
trace says `XKBLAYOUT: (null)`. Switching to a C-plugin theme *and* teaching
|
||||
NixOS to emit XKBLAYOUT to get a stock feature is a bad trade for a text label.
|
||||
|
||||
**So bake it, the way the palette already is.** The theme derivation seds
|
||||
`@BG_R@/@BG_G@/@BG_B@` into `nomarchy.script` at build time
|
||||
(`modules/nixos/plymouth.nix`); add `@LAYOUT@` from
|
||||
`config.nomarchy.keyboard.layout` and draw it under the entry with
|
||||
`Image.Text` (already used by `display_message_callback`), in the muted/subtext
|
||||
colour, only inside `display_password_callback`. It stays honest because the
|
||||
label and the console keymap are baked from the same option in the same
|
||||
rebuild.
|
||||
|
||||
To settle while building: (a) `keyboard.layout` may be a comma list (`us,gb`) —
|
||||
the VT loads the **first**, so print that one, not the raw string, or the label
|
||||
lies in exactly the situation it exists for; (b) wording — Fedora says
|
||||
"keyboard layout: us"; (c) whether to render it at all when there is only one
|
||||
layout and no remembered devices (an argument for always: the point is to tell
|
||||
you what the *console* does, which is not what your session does).
|
||||
|
||||
**Do #137 first, and land this with it:** the label is one more sprite that has
|
||||
to move when the canvas resizes, so it belongs in that item's `layout()`, and
|
||||
both are proven by the same `tools/plymouth-preview.sh` run — which renders
|
||||
`ask-for-password`, i.e. exactly this screen.
|
||||
|
||||
### 137. Plymouth splash draws on head 0's geometry, so it is off-centre when docked
|
||||
|
||||
Bernardo 2026-07-16 (T14s, docked): the boot **and** shutdown logo is not
|
||||
centred on the external monitor.
|
||||
|
||||
**The first diagnosis here was wrong — do not act on it.** It claimed
|
||||
`Window.GetWidth()` returns head 0 and that the fix was per-head sprites. Both
|
||||
are false, and per-head sprites would have been a *regression* (duplicate
|
||||
logos). Read from plymouth's own source (`plymouth.src`, the build nixpkgs
|
||||
ships) rather than from a summary:
|
||||
|
||||
- `Window.GetWidth()` **without an index returns `data->max_width`**
|
||||
(`script-lib-sprite.c:227`) — the widest head, not head 0.
|
||||
- The script plugin builds a virtual canvas of `max_width × max_height` and
|
||||
**centres every display inside it**: `display->x = (max_width - width) / 2`
|
||||
(`:536`). Sprites are drawn per display at `sprite->x - display->x` (`:452`).
|
||||
- Therefore a canvas-centred sprite **is** centred on every head, and today's
|
||||
`Window.GetWidth()/2 - w/2` is *correct at the instant it runs*. Note the DRM
|
||||
renderer sets every head's `area.x = 0` (`renderers/drm/plugin.c:612`) — the
|
||||
origins that matter are the plugin's computed ones, not the crtc's.
|
||||
|
||||
**The real bug is time, not geometry.** `nomarchy.script` computes every
|
||||
position as a **top-level statement at parse time** (`:9`, `:15–16`, `:129`,
|
||||
`:210`, `:219`), i.e. once, against whatever canvas existed at that moment.
|
||||
When a display arrives or leaves *during* the splash — which is exactly
|
||||
"booting up / shutting down **with** an external" — `max_width/max_height`
|
||||
change, the plugin re-centres each display (`:532-537`), but the sprite's frozen
|
||||
canvas coordinate does not move with it. Every element then sits off by
|
||||
`(new_max - old_max) / 2` **on every head**. A single monitor never changes the
|
||||
canvas, which is precisely why this has never been seen undocked.
|
||||
|
||||
**Fix shape:** hoist the position math into a `reposition()` and call it from
|
||||
the existing `refresh_callback` (already running at 50 fps) whenever
|
||||
`Window.GetWidth()/GetHeight()` differ from the last canvas — logo (scale
|
||||
included), entry, lock, any live bullets, progress box/bar. Keep it a
|
||||
canvas-change check, not an every-frame recompute.
|
||||
|
||||
**The rig works, needs no root, and is in `scratchpad/ply-rig.sh`** (recipe
|
||||
below — rebuild it, it is ~20 lines). Rendered on the dev box 2026-07-16:
|
||||
|
||||
- `unshare -rm` gives a user + mount namespace: root *in there* only, every
|
||||
mount invisible to the real system and gone when it exits.
|
||||
- `/run/plymouth` is BOTH the daemon's socket dir **and** its compiled-in
|
||||
plugin/theme path (`strings plymouthd` → `/run/plymouth/plugins/`). Bind a
|
||||
writable dir over it **and recreate its three symlinks** (`plugins`,
|
||||
`themes`, `plymouthd.defaults` → `/etc/plymouth/…`) or plymouth cannot find
|
||||
`x11.so`, silently skips X11 and reaches for a tty.
|
||||
- **Do not pass `--tty`**: `main.c:2133` only takes the X11 path while
|
||||
`default_tty` is unset. Bind an empty dir over `/dev/dri` too — with X11
|
||||
first in the renderer order (`ply-renderer.c:283`) DRM is never used, and the
|
||||
mask makes that a guarantee rather than a hope.
|
||||
- `PLY_CREATE_FAKE_MULTI_HEAD_SETUP=1` → 800×600 + 640×480 windows.
|
||||
`plymouth show-splash`, then `plymouth ask-for-password` renders the LUKS
|
||||
dialog. Debug: `--kernel-command-line="plymouth.debug=stream:/path splash"`
|
||||
(**`stream:`**, not `file:` — `file:` buffers and you get nothing).
|
||||
- Fast loop: copy the theme into the rig's `themes/` and sed its `.plymouth`
|
||||
`ScriptFile=`/`ImageDir=` to the copy — then editing the script is a 10s
|
||||
render instead of a 3min `nixos-rebuild`.
|
||||
|
||||
**What the rig already proved (2026-07-16):** with a *stable* two-head canvas
|
||||
the **current** theme centres correctly on both heads — logo and progress bar
|
||||
each in their own head's middle. So the geometry is not the bug, which is the
|
||||
evidence for the timing diagnosis above. The fake path builds both heads up
|
||||
front, so it cannot reproduce a canvas *change*: for that, plug/unplug while
|
||||
the splash is up, or compare a boot with and without the external.
|
||||
|
||||
**Attempt 1 failed, and the rig caught it — that is the point.** Hoisting the
|
||||
positions into a `layout()` called from `refresh_callback` rendered the
|
||||
background on both heads and **nothing else**: no logo, no progress bar. The
|
||||
script plugin logs `starting boot animation` and **no parse error**, so it
|
||||
fails *silently* — reverted rather than shipped (that script draws the LUKS box
|
||||
on an encrypted machine). Next session: bisect with the fast loop. Prime
|
||||
suspect is scope — plymouth script assigns into `local` unless the name already
|
||||
exists, so a `logo.image = …` inside a function may not be the `logo` the rest
|
||||
of the script sees, even though the same pattern works in `update_progress_bar`.
|
||||
Test that hypothesis *first*, in isolation, before rewriting anything.
|
||||
|
||||
Pass = with the rig, a two-head render centred on both **plus the password
|
||||
dialog visible**; then a real docked boot + shutdown, undocked unchanged.
|
||||
|
||||
### 143. Rofi keeps the selected *row number* when the search changes, so the highlight lands on an unrelated entry
|
||||
|
||||
Bernardo 2026-07-16, and his repro is exact: open the app menu, type `ca`, move
|
||||
|
||||
@@ -509,6 +509,17 @@ Everything else below stays open; order is convenience, not a gate.
|
||||
active and PPD switching governors; opt-ins: ROCm (`rocminfo`, a GPU
|
||||
PyTorch/Ollama smoke) and the XDNA NPU driver loading.
|
||||
- [ ] **Fingerprint** — `fprintd-enroll` + (opt-in PAM) login/sudo.
|
||||
- [ ] **#137/#145 splash on a real docked boot** — the one thing the render rig
|
||||
cannot do: its fake heads exist from the start, so the canvas never
|
||||
resizes, which *is* the bug. **Docked**, reboot: pass = the logo is centred
|
||||
on **both** panels at boot **and** at shutdown (it was off-centre on the
|
||||
external), and the LUKS prompt shows the padlock, the entry, and beneath it
|
||||
a keyboard icon + your layout (`us`). Then reboot **undocked** — unchanged.
|
||||
Rendering is already proven (`tools/plymouth-preview.sh`, both heads +
|
||||
`ask-for-password`), so what is unproven is only a canvas that changes
|
||||
under a real DRM boot. If the splash is ever blank, the passphrase prompt
|
||||
is invisible but still live — type it blind, or pick the previous
|
||||
generation in the boot menu, which carries the old theme.
|
||||
- [ ] **#142 dock mode survives a rebuild** — **relogin first** (the watcher is
|
||||
`exec-once`; `nomarchy-home` alone leaves the old one running and you
|
||||
would be testing nothing — round 6's lesson). Then, docked: run
|
||||
|
||||
@@ -19,6 +19,35 @@ Template:
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 — #137 + #145: the splash follows the canvas, and says your layout
|
||||
- **Task:** #137 (splash off-centre docked) + #145 (keyboard layout on the LUKS
|
||||
prompt, with Fedora's icon).
|
||||
- **Did:** one `layout()`, re-run from the existing refresh callback on canvas
|
||||
change; `@LAYOUT@` + `@FG_*@` baked by plymouth.nix like the palette already
|
||||
is; plymouth's own `keyboard.png` copied at build time and recoloured.
|
||||
- **The lesson, and it is the whole entry:** I was wrong twice, and only the
|
||||
render rig caught it. Not head-0 geometry (`Window.GetWidth()` = `max_width`);
|
||||
not per-head sprites (would have duplicated the logo — the plugin already
|
||||
centres each display in the canvas). The bug was **time**: positions computed
|
||||
once at parse time vs a canvas that resizes when a head arrives. And attempt 1
|
||||
of the fix died on a plymouth-script trap: **a bare assignment inside a
|
||||
function writes the global if that name exists globally**, so
|
||||
`canvas_width = Window.GetWidth()` updated the very global its guard compared
|
||||
against — always false, nothing drawn, **no error logged**. Isolated with a
|
||||
20-line probe (`[after assign: global.canvas_width=800][guard SKIPPED]`).
|
||||
Both facts are now comments in the script; do not re-derive them.
|
||||
- **Verified:** V0 + V2 by render — `tools/plymouth-preview.sh` with the real
|
||||
built theme: both heads centred, and the password dialog showing padlock,
|
||||
entry, and ⌨ + `us`. Also caught, only because I read the build log: copying a
|
||||
file from the store keeps it **444**, so `recolor` failed, the phase aborted,
|
||||
and **every later sed silently did not run** — the theme shipped with `@BG_R@`
|
||||
literals. `chmod +w`. Check build logs, not just exit codes.
|
||||
- **Pending:** V3 queued — a real docked boot/shutdown is the only way to resize
|
||||
a canvas (the rig's fake heads exist from the start). Recovery if the splash
|
||||
is ever blank: the prompt is invisible but live (type blind), or boot the
|
||||
previous generation.
|
||||
- **Next suggestion:** #129/#130 (GTK contrast) or #133 (the #107 shim check).
|
||||
|
||||
## 2026-07-16 — "fix the rest": #140, #142, #144 shipped; #137 re-diagnosed
|
||||
- **Task:** the T14s round's remainder. Bernardo picked configurable-provider +
|
||||
drop-the-CLI for #140 (and asked for chatgpt/grok in the list).
|
||||
|
||||
Reference in New Issue
Block a user