docs(agent): #137 re-diagnosed from plymouth's source — my first read was wrong
All checks were successful
Check / eval (push) Successful in 3m49s
All checks were successful
Check / eval (push) Successful in 3m49s
The entry claimed Window.GetWidth() returns head 0 and that the fix was per-head sprites. Both false, and per-head sprites would have REGRESSED it into duplicate logos. From plymouth.src (the build nixpkgs ships): - Window.GetWidth() with no index returns data->max_width — the widest head (script-lib-sprite.c:227), not head 0; - the plugin builds a max_width x max_height canvas and CENTRES every display inside it (display->x = (max_width - width)/2, :536), drawing sprites per display at sprite->x - display->x (:452); - so a canvas-centred sprite IS centred on every head, and today's math is correct at the instant it runs. (The DRM renderer's area.x = 0 for every head is a red herring — the offsets that matter are the plugin's.) The real bug is TIME: nomarchy.script computes every position as a top-level statement at parse time, against the canvas that existed then. A display arriving or leaving mid-splash — exactly "boot/shutdown with an external" — changes max_*, the plugin re-centres each display, and the frozen sprite coords are then off by (new_max - old_max)/2 on every head. A single monitor never changes the canvas, which is why it was never seen undocked. Fix shape recorded (reposition from the existing refresh callback on canvas change). Not shipped: the same script draws the LUKS passphrase box on an encrypted machine, and the render rig — plymouth ships x11.so plus a built-in PLY_CREATE_FAKE_MULTI_HEAD_SETUP — needs root. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -180,23 +180,54 @@ proven. Delete together with the shim + the `nomarchy-theme-sync` alias.
|
||||
### 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. Cause is in the theme script, not the
|
||||
hardware: `modules/nixos/plymouth/nomarchy.script` calls `Window.GetWidth()`
|
||||
/ `Window.GetHeight()` with **no head index**, which returns head 0, while
|
||||
sprite coordinates live in a device-wide space spanning every head. So every
|
||||
element is placed at "head 0's centre" measured from the union origin — right
|
||||
on a single monitor, wrong the moment a second one exists, and wrong in a
|
||||
different direction depending on which head is 0 and how they are arranged.
|
||||
centred on the external monitor.
|
||||
|
||||
It is the whole splash, not just the logo: the same unindexed call places the
|
||||
logo (`:9`, `:15–16` — note the 15%-of-height *scale* also tracks the wrong
|
||||
head), the password entry (`:129`), the lock, the bullets, and the progress
|
||||
box/bar (`:210`, `:219`). Fix shape is the standard Plymouth one: loop
|
||||
`Window.GetX(i)` / `GetY(i)` / `GetWidth(i)` / `GetHeight(i)` over the heads
|
||||
and give each its own sprite set, so each monitor gets a centred, correctly
|
||||
scaled splash — the LUKS prompt especially, since a passphrase box half off
|
||||
the panel is the failure that matters. Pass = docked boot + shutdown centred
|
||||
on both heads; undocked unchanged.
|
||||
**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.
|
||||
|
||||
**Testing — the rig exists, and it needs root.** nixpkgs' plymouth ships
|
||||
`x11.so`, and plymouth has a built-in fake dual-head mode:
|
||||
`PLY_CREATE_FAKE_MULTI_HEAD_SETUP=1` (`renderers/x11/plugin.c:294`) creates
|
||||
800×600 + 640×480 heads, so the splash can be rendered in a window with no
|
||||
DRM and no reboot. Caveat: that fake path builds both heads up front, so it
|
||||
proves *rendering and centring*, not the canvas-change bug itself — for that,
|
||||
plug/unplug while the splash is up, or compare a boot with and without the
|
||||
external. `plymouthd` wants root and its theme comes from
|
||||
`/etc/plymouth/plymouthd.conf`, so this needs a sudo session.
|
||||
|
||||
**Do not ship this blind:** the same script draws the **LUKS passphrase box**,
|
||||
this machine is encrypted, and a script that fails to parse leaves a prompt you
|
||||
cannot see. Pass = with the rig, a two-head render centred on both; 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user