Twelve login terminals, root only, console fixes, a faster build, a Niveus splash, and X back on evdev

- tty1-tty12 all run login; the image has only root. /dev/tty7..12 could not
  be written (cdev.c knew minors 10-16 only), which sent their jobs into a
  restart storm that took the supervisor down; fixed, and stat reports them as
  Linux's tty7..12.
- Erasing text at a prompt did nothing: erase_one sent a bare backspace, which
  only moves; it and the echo path now send back-blank-back through vt_feed.
- A white cursor block was left at the end of every line of a verbose boot:
  a line feed at the bottom scrolls the pixels, cursor included, and the
  cursor was erased after the scroll. It comes off first now. A blank cell is
  also marked dirty, which it was not.
- ldlsd died with a general protection fault on real hardware in its variadic
  log function: Linux module code calls with an 8-byte-aligned stack and an
  unset %al, and the prologue's movaps faults. LDLS guest code is built with
  -mstackrealign.
- make iso took over a minute for any change, because staging r8169 rewrote
  the same files and every mtime moved; it now replaces files only if their
  contents differ (about 10 s).
- The loader's splash is redrawn in the boot manager's style, and the boot
  manager shows its cursor whenever a pointer device exists.
- xf86-input-libinput is out of the image again: with it Xorg took both
  keyboard and pointer from libinput and, on the user's laptop, neither worked.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
This commit is contained in:
Nexoniarz 2026-10-26 17:43:44 +01:00
commit 2aef583aad
23 changed files with 290 additions and 155 deletions

View file

@ -265,40 +265,57 @@ ui_logo(int y, int w)
/* Where the bar goes. Derived, not stored, so nothing can set it and then have
* the screen change underneath it.
*
* The bar is exactly the mark's width and sits directly beneath it: the two are
* one object, and a bar wider than the mark it belongs to reads as a separate
* thing that happens to be nearby.
* The track is wider than the mark and sits beneath it: a mark with a recessed
* track and one line of type, centred, on the same graphite the boot manager
* uses. Laid out from the middle outwards so it reads the same at 800x600 and
* at 4K. S() is defined further down with the rest of the Niveus code, so the
* scale is applied through SC directly.
*/
static void
bar_geometry(void)
{
PROG_W = ui_mark_width();
PROG_H = BAR_H;
PROG_W = 220 * SC;
PROG_H = 6 * SC;
PROG_X = ((int)FB.width - PROG_W) / 2;
int mark_h = (LOGO_H * PROG_W) / LOGO_W;
int group_h = mark_h + MARK_GAP + PROG_H;
int mw = ui_mark_width() * SC;
if (mw > LOGO_W) mw = LOGO_W;
int mark_h = (LOGO_H * mw) / LOGO_W;
int gap = 34 * SC;
int group_h = mark_h + gap + PROG_H;
int top = ((int)FB.height - group_h) / 2;
PROG_Y = top + mark_h + MARK_GAP;
PROG_Y = top + mark_h + gap;
}
static ui_u32 bg_at(int y);
static void nv_backdrop(void);
static int nv_width(const struct nvfont *f, const char *s);
static void nv_text(const struct nvfont *f, int x, int base, const char *s,
ui_u32 fg, ui_u32 bgtop, ui_u32 bgbot, int y0, int h);
static void rrect(int x, int y, int w, int h, int r, ui_u32 top, ui_u32 bot,
ui_u32 bgtop, ui_u32 bgbot, int bgy, int bgh);
void
ui_splash(const char *subtitle)
{
if (!ACTIVE)
return;
ui_clear();
nv_backdrop();
bar_geometry();
int w = PROG_W;
int w = ui_mark_width() * SC;
if (w > LOGO_W) w = LOGO_W;
int mark_h = (LOGO_H * w) / LOGO_W;
int top = PROG_Y - MARK_GAP - mark_h;
int top = PROG_Y - 34 * SC - mark_h;
ui_u32 saved = LOGO_BG;
LOGO_BG = bg_at(top + mark_h / 2);
ui_logo(top, w);
LOGO_BG = saved;
if (subtitle)
ui_text_centered(PROG_Y + PROG_H + SUB_GAP, subtitle, SCH_DIM);
ui_progress_label(subtitle);
}
/* --- Niveus -----------------------------------------------------------------
@ -912,6 +929,25 @@ ui_notice(const char *title, const char *const *lines, int n,
box(title, lines, 0, n, button, hint);
}
/* The track is a recess, the fill is the accent, lit from above -- the same two
* materials as a well and a selected key in the boot manager. The fill is
* redrawn from the left edge on every tick rather than extended, because its
* rounded end moves; the bar is small enough that this costs nothing. */
static void
progress_paint(int px)
{
int r = PROG_H / 2;
ui_u32 bg = bg_at(PROG_Y + PROG_H / 2);
rrect(PROG_X, PROG_Y, PROG_W, PROG_H, r, 0x0A0A0Bu, 0x151517u, bg, bg, 0, 1);
if (px >= PROG_H) {
ui_u32 top = mix(SCH_ACCENT, 0xFFFFFFu, 28, 100);
ui_u32 bot = mix(SCH_ACCENT, 0x000000u, 24, 100);
rrect(PROG_X + 1, PROG_Y + 1, px - 2, PROG_H - 2, r - 1, top, bot,
0x0A0A0Bu, 0x151517u, PROG_Y, PROG_H);
}
}
void
ui_progress_begin(const char *label, ui_u32 total)
{
@ -924,10 +960,7 @@ ui_progress_begin(const char *label, ui_u32 total)
PROG_LAST_PX = 0;
PROG_ACTIVE = 1;
/* The unfilled track: a recess, not a colour. Drawn once -- repainting it
* on every tick is what makes a progress bar flicker. */
ui_fill_rect(PROG_X, PROG_Y, PROG_W, PROG_H, 0x1B1F28u);
ui_fill_rect(PROG_X, PROG_Y, PROG_W, 1, 0x101319u);
progress_paint(0);
if (label)
ui_progress_label(label);
}
@ -938,11 +971,16 @@ ui_progress_label(const char *label)
if (!ACTIVE || !label)
return;
/* Clear the line first. A shorter label after a longer one would otherwise
* leave the tail of the old one behind. */
int y = PROG_Y + PROG_H + SUB_GAP;
ui_fill_rect(0, y, (int)FB.width, GLYPH_H, SCH_BG);
ui_text_centered(y, label, SCH_DIM);
/* Repaint the backdrop under the line first. A shorter label after a longer
* one would otherwise leave the tail of the old one behind. */
int h = nv_height(F_BODY) + 4 * SC;
int y = PROG_Y + PROG_H + 18 * SC;
for (int r = 0; r < h; r++)
ui_fill_rect(0, y + r, (int)FB.width, 1, bg_at(y + r));
int tw = nv_width(F_BODY, label);
nv_text(F_BODY, ((int)FB.width - tw) / 2, y + F_BODY->ascent, label, NV_TXT3,
bg_at(y), bg_at(y + h), y, h);
}
void
@ -956,16 +994,8 @@ ui_progress_tick(ui_u32 done)
PROG_DONE = done;
int px = (int)(((ui_u64)PROG_DONE * (ui_u64)PROG_W) / PROG_TOTAL);
/* Only the newly filled part. Filling from zero each time is O(width) per
* tick and visibly slower than the thing it is reporting on. */
if (px > PROG_LAST_PX) {
/* The fill is metal too, shaded across the bar's height, so it looks
* like the same material as the mark rather than a coloured rectangle
* that happens to be underneath it. */
for (int r = 0; r < PROG_H; r++)
ui_fill_rect(PROG_X + PROG_LAST_PX, PROG_Y + r,
px - PROG_LAST_PX, 1, metal(r, PROG_H));
progress_paint(px);
PROG_LAST_PX = px;
}
}

View file

@ -1063,15 +1063,15 @@ pointer_poll(void)
}
/* Wait for a key, a click on a key, or the pointer moving onto one.
*
* The cursor is shown whenever a pointer device is present: hiding it until
* something moved left a machine whose pointer sends no events to the
* loader with nothing on screen to say a pointer was even looked for.
*
* `budget` is a countdown in milliseconds that survives across calls: a
* caller that returns here after a hover continues the same ten seconds
* instead of being given a fresh ten. NULL waits for ever.
*
* The cursor stays hidden until the pointer has actually done something. The
* firmware's console layer installs a pointer protocol whether or not a mouse
* is attached, so "a protocol exists" proves nothing -- and a cursor parked in
* the middle of the menu of a laptop with no mouse is worse than none. */
*/
static int
wait_event(EFI_INPUT_KEY *key, int *item, UINTN *budget)
{
@ -1082,6 +1082,8 @@ wait_event(EFI_INPUT_KEY *key, int *item, UINTN *budget)
PTR_X = (int)ui_width() / 2;
PTR_Y = (int)ui_height() / 2;
}
if (have)
PTR_ACTIVE = 1;
ui_cursor(PTR_X, PTR_Y, PTR_ACTIVE);
for (;;) {
if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, key)))
@ -1094,6 +1096,10 @@ wait_event(EFI_INPUT_KEY *key, int *item, UINTN *budget)
PTR_RESCANS++;
pointers_find();
have = PTR_NREL + PTR_NABS;
if (have && !PTR_ACTIVE) {
PTR_ACTIVE = 1;
ui_cursor(PTR_X, PTR_Y, 1);
}
}
if (have) {
int e = pointer_poll();