Boot manager: Niveus window, keys and wells, Instrument Sans, pointer, other systems, working power entries
The F8 menu is redrawn in the Niveus language: a window with three lights, bevelled keys with a lit edge, wells for panels, anti-aliased corners and a soft shadow, set in Instrument Sans rasterised into include/gfx/nvfont.h (tools/python/mknvfont.py, make nvfont). Scale doubles on screens 1400 px tall or more. Lists taller than the screen scroll. The keyboard still drives everything, and a mouse, touchpad or tablet does too: both UEFI pointer protocols are polled, hover moves the highlight, a click activates. The cursor stays hidden until the pointer has really done something, since the firmware installs a pointer protocol with or without a mouse. The 'keep this resolution?' countdown is one ten-second budget however many times the pointer moves. Loaders found at well-known paths on any volume (Windows, Ubuntu, Debian, Fedora, Arch, openSUSE, systemd-boot, rEFInd, other BOOTX64.EFI) are listed and chain-loaded with LoadImage/StartImage. 'Reboot to firmware' used to halt; it now sets OsIndications and restarts, and Restart and Shut Down call ResetSystem. Checked under QEMU+OVMF: menu, resolution list (incl. 2560x1600 at double scale), system information, shut down, firmware setup, chain-loading a copy of the loader from a second disk, and a full boot of the ISO to the login prompt. Pointer hover and click were checked only with synthetic events: in this setup a USB mouse or tablet delivered no event to the loader (one USB I/O handle appeared but no pointer protocol came from it), and why was not established. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
This commit is contained in:
parent
020aa6113c
commit
dee3affa3b
11 changed files with 9304 additions and 208 deletions
11
Makefile
11
Makefile
|
|
@ -653,7 +653,7 @@ UEFI_CFLAGS = $(UEFI_DEBUG_FLAGS) --target=x86_64-unknown-windows -ffreestanding
|
|||
# Hence the check and the message. It is worth the four lines.
|
||||
$(BUILD)/uefi/BOOTX64.EFI: boot/uefi/main.c boot/uefi/efi.h include/arch/schboot.h \
|
||||
boot/common/ui.c boot/common/ui.h \
|
||||
include/gfx/logo.h include/gfx/theme.h include/gfx/font.h
|
||||
include/gfx/logo.h include/gfx/theme.h include/gfx/font.h include/gfx/nvfont.h
|
||||
@mkdir -p $(BUILD)/uefi
|
||||
@command -v clang >/dev/null 2>&1 || { \
|
||||
echo ""; \
|
||||
|
|
@ -1775,6 +1775,13 @@ LOGO_PX = 128
|
|||
logo:
|
||||
$(PYTHON) tools/python/mklogo.py "$(LOGO_SRC)" include/gfx/logo.h $(LOGO_PX)
|
||||
|
||||
# include/gfx/nvfont.h: the boot manager's typeface (Instrument Sans, from
|
||||
# docs/Website/niveus/fonts), rasterised once and committed, same arrangement as
|
||||
# logo.h. Needs Pillow, so it is run by hand: nix-shell -p 'python3.withPackages
|
||||
# (p: [p.pillow])' --run "make nvfont"
|
||||
nvfont:
|
||||
$(PYTHON) tools/python/mknvfont.py docs/Website/niveus/fonts/static include/gfx/nvfont.h
|
||||
|
||||
# The USB vendor/product name database, from linux-usb.org.
|
||||
#
|
||||
# Dual-licensed GPL-2.0-or-later **or** 3-clause BSD; SchOS takes the BSD
|
||||
|
|
@ -1839,7 +1846,7 @@ qrcheck: $(QRTEST)
|
|||
.PHONY: disk all kernel ramroot cmd ports projects sysroot-install qemu qemu-console qemu-kvm \
|
||||
schfs-test schfs-objtest schfs-image-test schfs-check kmem-test mkschfs \
|
||||
schfs-fsck crashshot qemu-schfs iso qemu-iso clean distclean \
|
||||
logo qrtest qrcheck crashqr usb-ids pci-ids hwdata ovmf esp uefi
|
||||
logo nvfont qrtest qrcheck crashqr usb-ids pci-ids hwdata ovmf esp uefi
|
||||
.PRECIOUS: $(BUILD)/%.o
|
||||
|
||||
# 'clean' removes generated output only. dist/ (upstream tarballs) survives,
|
||||
|
|
|
|||
695
boot/common/ui.c
695
boot/common/ui.c
|
|
@ -13,6 +13,7 @@
|
|||
#include "../../include/gfx/theme.h"
|
||||
#include "../../include/gfx/font.h"
|
||||
#include "../../include/gfx/logo.h"
|
||||
#include "../../include/gfx/nvfont.h"
|
||||
|
||||
/* "Draw the glyph, leave the background alone." Not a colour anyone can mean:
|
||||
* the palette is 24-bit, so the high byte is free for a sentinel. */
|
||||
|
|
@ -21,6 +22,25 @@
|
|||
static struct ui_fb FB;
|
||||
static int ACTIVE;
|
||||
|
||||
/* What the mark is blended against: the splash ground, or a window body. */
|
||||
static ui_u32 LOGO_BG = SCH_BG;
|
||||
|
||||
/* The boot manager's scale and faces. 2 on a screen big enough that a 15 px
|
||||
* face would be a speck; the faces are rasterised at both sizes, not doubled. */
|
||||
static int SC = 1;
|
||||
static const struct nvfont *F_BODY, *F_SMALL, *F_TITLE;
|
||||
static int CUR_ON; /* the pointer is drawn right now */
|
||||
|
||||
static void
|
||||
ui_set_scale(void)
|
||||
{
|
||||
SC = (FB.height >= 1400 || FB.width >= 2400) ? 2 : 1;
|
||||
F_BODY = SC == 2 ? &nvfont_body2 : &nvfont_body1;
|
||||
F_SMALL = SC == 2 ? &nvfont_small2 : &nvfont_small1;
|
||||
F_TITLE = SC == 2 ? &nvfont_title2 : &nvfont_title1;
|
||||
CUR_ON = 0; /* a new framebuffer holds no pointer */
|
||||
}
|
||||
|
||||
/* Progress state. Kept here rather than passed around because the caller ticks
|
||||
* it from inside a read loop that has no business carrying UI state. */
|
||||
static ui_u32 PROG_TOTAL, PROG_DONE;
|
||||
|
|
@ -101,6 +121,7 @@ ui_init(const struct ui_fb *fb)
|
|||
if (!FB.stride)
|
||||
FB.stride = FB.width * 4;
|
||||
ACTIVE = 1;
|
||||
ui_set_scale();
|
||||
}
|
||||
|
||||
int ui_active(void) { return ACTIVE; }
|
||||
|
|
@ -236,7 +257,7 @@ ui_logo(int y, int w)
|
|||
|
||||
/* Coverage blends the shaded metal against the ground, so the mark
|
||||
* has soft edges and a lit face at the same time. */
|
||||
put(x0 + dx, y + dy, mix(SCH_BG, shade, (int)cov, 15));
|
||||
put(x0 + dx, y + dy, mix(LOGO_BG, shade, (int)cov, 15));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -280,85 +301,521 @@ ui_splash(const char *subtitle)
|
|||
ui_text_centered(PROG_Y + PROG_H + SUB_GAP, subtitle, SCH_DIM);
|
||||
}
|
||||
|
||||
/* --- panels ---------------------------------------------------------------
|
||||
/* --- Niveus -----------------------------------------------------------------
|
||||
*
|
||||
* A panel is a plate: a slightly lighter ground than the screen, one lighter
|
||||
* row along the top and a darker border, which is the whole of the bevel. Two
|
||||
* rows of shading do more for the impression of a raised surface than any
|
||||
* amount of gradient inside it, and cost nothing to draw.
|
||||
* The boot manager is drawn in the same language as the rest of the system
|
||||
* (docs/Website/niveus): windows are solid and carry three lights, controls are
|
||||
* bevelled keys with light falling across them, fields are wells cut into the
|
||||
* surface, and the ground under all of it is warm graphite rather than blue.
|
||||
*
|
||||
* Every shape here is a rounded rectangle with a vertical gradient, which is
|
||||
* all that skeuomorphism needs at this size. Corners are anti-aliased by
|
||||
* supersampling the corner pixels only, and the AA blends against a colour the
|
||||
* caller *names*, never against what is in the framebuffer: reading the
|
||||
* framebuffer back is uncached on real hardware and ruinously slow.
|
||||
*
|
||||
* The typeface is Instrument Sans, rasterised at build time (nvfont.h).
|
||||
*/
|
||||
static void
|
||||
panel(int x, int y, int w, int h)
|
||||
|
||||
#define NV_BG_TOP 0x1B1B1Eu /* the ground, light at the top */
|
||||
#define NV_BG_BOT 0x0B0B0Du
|
||||
#define NV_WIN_BODY 0x232326u
|
||||
#define NV_WIN_EDGE 0x050507u
|
||||
#define NV_TITLE_TOP 0x45444Au
|
||||
#define NV_TITLE_BOT 0x2C2B30u
|
||||
#define NV_HILITE 0x5E5D64u /* the lit upper edge of a raised thing */
|
||||
#define NV_KEY_TOP 0x3F3E44u
|
||||
#define NV_KEY_BOT 0x2D2C32u
|
||||
#define NV_KEY_EDGE 0x111113u
|
||||
#define NV_KEY_SHADOW 0x141416u
|
||||
#define NV_WELL_TOP 0x101012u
|
||||
#define NV_WELL_BOT 0x171719u
|
||||
#define NV_WELL_EDGE 0x0A0A0Bu
|
||||
#define NV_WELL_LIP 0x3A393Eu /* light catching the lower lip of a well */
|
||||
#define NV_TXT 0xECEAE5u
|
||||
#define NV_TXT2 0xA9A7A1u
|
||||
#define NV_TXT3 0x74726Du
|
||||
#define NV_LIGHT_CLOSE 0xFF5F57u
|
||||
#define NV_LIGHT_MIN 0xFEBC2Eu
|
||||
#define NV_LIGHT_ZOOM 0x28C840u
|
||||
|
||||
#define S(n) ((n) * SC)
|
||||
#define WHITE 0xFFFFFFu
|
||||
#define BLACK 0x000000u
|
||||
|
||||
static ui_u32
|
||||
bg_at(int y)
|
||||
{
|
||||
ui_fill_rect(x, y, w, h, PANEL_BG);
|
||||
ui_fill_rect(x, y, w, 1, PANEL_TOP); /* lit top edge */
|
||||
ui_fill_rect(x, y + h - 1, w, 1, METAL_EDGE); /* shadowed bottom */
|
||||
ui_fill_rect(x, y, 1, h, METAL_EDGE);
|
||||
ui_fill_rect(x + w - 1, y, 1, h, METAL_EDGE);
|
||||
return mix(NV_BG_TOP, NV_BG_BOT, y, (int)FB.height > 1 ? (int)FB.height - 1 : 1);
|
||||
}
|
||||
|
||||
/* A heading in metal: the same shading the mark uses, applied per row of the
|
||||
* glyph cell, so a title looks struck out of the same material. */
|
||||
static void
|
||||
metal_text_centered(int y, const char *s)
|
||||
nv_backdrop(void)
|
||||
{
|
||||
int len = slen(s);
|
||||
int x = ((int)FB.width - len * GLYPH_W) / 2;
|
||||
for (int y = 0; y < (int)FB.height; y++)
|
||||
ui_fill_rect(0, y, (int)FB.width, 1, bg_at(y));
|
||||
}
|
||||
|
||||
for (int row = 0; row < GLYPH_H; row++) {
|
||||
ui_u32 shade = metal(row, GLYPH_H);
|
||||
for (int i = 0; i < len; i++) {
|
||||
ui_u8 bits = font_data[(ui_u8)s[i]][row];
|
||||
for (int col = 0; col < GLYPH_W; col++)
|
||||
if (bits & (0x80 >> col))
|
||||
put(x + i * GLYPH_W + col, y + row, shade);
|
||||
static int
|
||||
nv_glyph_index(unsigned c)
|
||||
{
|
||||
if (c < NVFONT_FIRST || c > NVFONT_LAST)
|
||||
c = '?';
|
||||
return (int)(c - NVFONT_FIRST);
|
||||
}
|
||||
|
||||
static int
|
||||
nv_width(const struct nvfont *f, const char *s)
|
||||
{
|
||||
int w = 0;
|
||||
for (; *s && *s != '\n'; s++)
|
||||
w += f->g[nv_glyph_index((ui_u8)*s)].adv;
|
||||
return (w + 32) >> 6;
|
||||
}
|
||||
|
||||
static int
|
||||
nv_height(const struct nvfont *f)
|
||||
{
|
||||
return f->ascent + f->descent;
|
||||
}
|
||||
|
||||
/* Draw `s` (up to a newline) with its origin on baseline `base`. The colour
|
||||
* under it is a vertical gradient from bgtop at row y0 to bgbot at y0+h. */
|
||||
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)
|
||||
{
|
||||
int pen = 0; /* 1/64 px: the face's own spacing */
|
||||
for (; *s && *s != '\n'; s++) {
|
||||
const struct nvglyph *g = &f->g[nv_glyph_index((ui_u8)*s)];
|
||||
const ui_u8 *bits = f->bits + g->off;
|
||||
int gx = x + ((pen + 32) >> 6);
|
||||
for (int r = 0; r < g->h; r++) {
|
||||
int py = base + g->oy + r;
|
||||
ui_u32 bg = mix(bgtop, bgbot, py - y0, h > 1 ? h - 1 : 1);
|
||||
for (int c = 0; c < g->w; c++) {
|
||||
ui_u8 a = bits[r * g->w + c];
|
||||
if (a)
|
||||
put(gx + g->ox + c, py, mix(bg, fg, a, 255));
|
||||
}
|
||||
}
|
||||
pen += g->adv;
|
||||
}
|
||||
}
|
||||
|
||||
/* How much of the pixel at (dx,dy) from a corner's centre lies inside a circle
|
||||
* of radius r, in sixteenths. */
|
||||
static int
|
||||
corner_cov(int dx, int dy, int r)
|
||||
{
|
||||
int n = 0, R = r * 8;
|
||||
for (int j = 0; j < 4; j++)
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int sx = dx * 8 + 2 * i + 1, sy = dy * 8 + 2 * j + 1;
|
||||
if (sx * sx + sy * sy <= R * R)
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (w <= 0 || h <= 0)
|
||||
return;
|
||||
if (r * 2 > h) r = h / 2;
|
||||
if (r * 2 > w) r = w / 2;
|
||||
|
||||
for (int rr = 0; rr < h; rr++) {
|
||||
int yy = y + rr;
|
||||
ui_u32 col = mix(top, bot, rr, h > 1 ? h - 1 : 1);
|
||||
int cy, corner = 1;
|
||||
if (rr < r) cy = y + r;
|
||||
else if (rr >= h - r) cy = y + h - r;
|
||||
else corner = 0;
|
||||
|
||||
if (!corner) {
|
||||
ui_fill_rect(x, yy, w, 1, col);
|
||||
continue;
|
||||
}
|
||||
ui_u32 bg = mix(bgtop, bgbot, yy - bgy, bgh > 1 ? bgh - 1 : 1);
|
||||
ui_fill_rect(x + r, yy, w - 2 * r, 1, col);
|
||||
for (int k = 0; k < r; k++) {
|
||||
int dy = yy - cy;
|
||||
int cl = corner_cov(k - r, dy, r);
|
||||
int cr = corner_cov(k, dy, r);
|
||||
if (cl) put(x + k, yy, mix(bg, col, cl, 16));
|
||||
if (cr) put(x + w - r + k, yy, mix(bg, col, cr, 16));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Panel metrics.
|
||||
*
|
||||
* TITLE_H is the whole title band -- the padding above the heading, the
|
||||
* heading itself, and the air beneath it before the rule. It was 34 while the
|
||||
* heading alone needs 18 + 16 = 34, so the rule at TITLE_H - 6 was drawn
|
||||
* *through* the lettering and the first row started on the heading's baseline.
|
||||
* Derived from its parts now, so it cannot drift from them again.
|
||||
*/
|
||||
#define ROW_H 24
|
||||
#define PANEL_PAD 18
|
||||
#define TITLE_GAP 14 /* heading to the rule */
|
||||
#define TITLE_H (PANEL_PAD + GLYPH_H + TITLE_GAP) /* = 48 */
|
||||
#define ROW_LIFT ((ROW_H - GLYPH_H) / 2) /* highlight overhang */
|
||||
/* A raised object: an edge, a gradient face, and a bright line just inside the
|
||||
* top where the light lands. */
|
||||
static void
|
||||
bevel(int x, int y, int w, int h, int r, ui_u32 top, ui_u32 bot, ui_u32 edge,
|
||||
ui_u32 hi, ui_u32 bgtop, ui_u32 bgbot, int bgy, int bgh)
|
||||
{
|
||||
rrect(x, y, w, h, r, edge, edge, bgtop, bgbot, bgy, bgh);
|
||||
rrect(x + SC, y + SC, w - 2 * SC, h - 2 * SC, r > SC ? r - SC : r,
|
||||
top, bot, edge, edge, 0, 1);
|
||||
if (hi)
|
||||
ui_fill_rect(x + r, y + SC, w - 2 * r, SC, hi);
|
||||
}
|
||||
|
||||
/* A recess: the same thing with the light reversed. Darker at the top where the
|
||||
* rim shades it, with a pale lip along the bottom edge. */
|
||||
static void
|
||||
well(int x, int y, int w, int h, ui_u32 bgcol)
|
||||
{
|
||||
int r = S(8);
|
||||
bevel(x, y, w, h, r, NV_WELL_TOP, NV_WELL_BOT, NV_WELL_EDGE, 0, bgcol, bgcol, 0, 1);
|
||||
ui_fill_rect(x + r, y + h - SC, w - 2 * r, SC, NV_WELL_LIP);
|
||||
}
|
||||
|
||||
/* The soft shadow beneath a window, as stacked translucent layers laid down
|
||||
* from the outermost in, each one blended against the backdrop's own gradient. */
|
||||
static void
|
||||
shadow(int x, int y, int w, int h, int r)
|
||||
{
|
||||
int spread = S(16), dy = S(8);
|
||||
for (int i = spread; i > 0; i -= SC) {
|
||||
int t = spread - i;
|
||||
int a = 60 * t * t / (spread * spread); /* percent black */
|
||||
int ty = y - i + dy, th = h + 2 * i;
|
||||
ui_u32 top = mix(bg_at(ty), BLACK, a, 100);
|
||||
ui_u32 bot = mix(bg_at(ty + th), BLACK, a, 100);
|
||||
rrect(x - i, ty, w + 2 * i, th, r + i, top, bot,
|
||||
bg_at(ty), bg_at(ty + th), ty, th);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
panel_frame(const char *title, int rows, const char *hint,
|
||||
int *out_x, int *out_y, int *out_w)
|
||||
light(int x, int y, int d, ui_u32 colour, int tb_y, int tb_h)
|
||||
{
|
||||
int w = (int)FB.width / 2;
|
||||
if (w < 360) w = 360;
|
||||
if (w > (int)FB.width - 60) w = (int)FB.width - 60;
|
||||
ui_u32 top = mix(colour, WHITE, 28, 100);
|
||||
ui_u32 bot = mix(colour, BLACK, 22, 100);
|
||||
ui_u32 rim = mix(colour, BLACK, 62, 100);
|
||||
rrect(x - SC, y - SC, d + 2 * SC, d + 2 * SC, d / 2 + SC, rim, rim,
|
||||
NV_TITLE_TOP, NV_TITLE_BOT, tb_y, tb_h);
|
||||
rrect(x, y, d, d, d / 2, top, bot, rim, rim, 0, 1);
|
||||
}
|
||||
|
||||
int h = TITLE_H + rows * ROW_H + PANEL_PAD * 2;
|
||||
int x = ((int)FB.width - w) / 2;
|
||||
int y = ((int)FB.height - h) / 2;
|
||||
/* --- the pointer ----------------------------------------------------------- */
|
||||
|
||||
ui_clear();
|
||||
#define CUR_W 12
|
||||
#define CUR_H 19
|
||||
static const char *const CUR_SHAPE[CUR_H] = {
|
||||
"X ",
|
||||
"XX ",
|
||||
"X.X ",
|
||||
"X..X ",
|
||||
"X...X ",
|
||||
"X....X ",
|
||||
"X.....X ",
|
||||
"X......X ",
|
||||
"X.......X ",
|
||||
"X........X ",
|
||||
"X.........X ",
|
||||
"X......XXXXX",
|
||||
"X...X..X ",
|
||||
"X..X X..X ",
|
||||
"X.X X..X ",
|
||||
"XX X..X ",
|
||||
"X X..X ",
|
||||
" X..X ",
|
||||
" XXX ",
|
||||
};
|
||||
static ui_u32 CUR_SAVE[CUR_W * 2 * CUR_H * 2];
|
||||
static int CUR_X, CUR_Y, CUR_WANT;
|
||||
|
||||
/* The mark, small, above the plate: it is the same screen, not a new one. */
|
||||
int mw = ui_mark_width() * 2 / 3;
|
||||
int mh = (LOGO_H * mw) / LOGO_W;
|
||||
if (y - mh - 24 > 0)
|
||||
ui_logo(y - mh - 24, mw);
|
||||
static void
|
||||
cursor_hide(void)
|
||||
{
|
||||
if (!CUR_ON || !ACTIVE)
|
||||
return;
|
||||
int w = CUR_W * SC, h = CUR_H * SC;
|
||||
for (int r = 0; r < h; r++)
|
||||
for (int c = 0; c < w; c++) {
|
||||
int px = CUR_X + c, py = CUR_Y + r;
|
||||
if (px >= 0 && py >= 0 && (ui_u32)px < FB.width && (ui_u32)py < FB.height)
|
||||
*(ui_u32 *)(FB.base + (ui_u64)py * FB.stride + (ui_u64)px * 4) =
|
||||
CUR_SAVE[r * w + c];
|
||||
}
|
||||
CUR_ON = 0;
|
||||
}
|
||||
|
||||
panel(x, y, w, h);
|
||||
metal_text_centered(y + PANEL_PAD, title);
|
||||
ui_fill_rect(x + PANEL_PAD, y + PANEL_PAD + GLYPH_H + TITLE_GAP / 2,
|
||||
w - PANEL_PAD * 2, 1, METAL_EDGE);
|
||||
static void
|
||||
cursor_show(void)
|
||||
{
|
||||
if (!CUR_WANT || CUR_ON || !ACTIVE)
|
||||
return;
|
||||
int w = CUR_W * SC, h = CUR_H * SC;
|
||||
for (int r = 0; r < h; r++)
|
||||
for (int c = 0; c < w; c++) {
|
||||
int px = CUR_X + c, py = CUR_Y + r;
|
||||
if (px < 0 || py < 0 || (ui_u32)px >= FB.width || (ui_u32)py >= FB.height)
|
||||
continue;
|
||||
ui_u32 *p = (ui_u32 *)(FB.base + (ui_u64)py * FB.stride + (ui_u64)px * 4);
|
||||
CUR_SAVE[r * w + c] = *p;
|
||||
char ch = CUR_SHAPE[r / SC][c / SC];
|
||||
if (ch == 'X') *p = BLACK;
|
||||
else if (ch == '.') *p = WHITE;
|
||||
}
|
||||
CUR_ON = 1;
|
||||
}
|
||||
|
||||
if (hint)
|
||||
ui_text_centered(y + h + 16, hint, SCH_DIM);
|
||||
void
|
||||
ui_cursor(int x, int y, int show)
|
||||
{
|
||||
cursor_hide();
|
||||
CUR_X = x; CUR_Y = y; CUR_WANT = show;
|
||||
cursor_show();
|
||||
}
|
||||
|
||||
*out_x = x; *out_y = y + TITLE_H; *out_w = w;
|
||||
/* --- window chrome --------------------------------------------------------- */
|
||||
|
||||
#define TB_H S(34)
|
||||
#define BODY_PAD S(18)
|
||||
#define HINT_H S(36)
|
||||
|
||||
static void
|
||||
window(int x, int y, int w, int h, const char *title)
|
||||
{
|
||||
int r = S(12), tb = TB_H;
|
||||
|
||||
shadow(x, y, w, h, r);
|
||||
rrect(x, y, w, h, r, NV_WIN_EDGE, NV_WIN_EDGE, bg_at(y), bg_at(y + h), y, h);
|
||||
rrect(x + SC, y + SC, w - 2 * SC, h - 2 * SC, r - SC, NV_WIN_BODY, NV_WIN_BODY,
|
||||
NV_WIN_EDGE, NV_WIN_EDGE, 0, 1);
|
||||
|
||||
/* The titlebar is rounded on top only: draw it tall enough that its bottom
|
||||
* corners fall below the line, then lay the body colour over the excess. */
|
||||
rrect(x + SC, y + SC, w - 2 * SC, tb + r, r - SC, NV_TITLE_TOP, NV_TITLE_BOT,
|
||||
NV_WIN_EDGE, NV_WIN_EDGE, 0, 1);
|
||||
ui_fill_rect(x + SC, y + SC + tb, w - 2 * SC, r, NV_WIN_BODY);
|
||||
ui_fill_rect(x + SC, y + SC + tb - SC, w - 2 * SC, SC, NV_WIN_EDGE);
|
||||
ui_fill_rect(x + r, y + SC, w - 2 * r, SC, NV_HILITE);
|
||||
|
||||
int d = S(12), ly = y + SC + (tb - d) / 2, lx = x + S(18);
|
||||
light(lx, ly, d, NV_LIGHT_CLOSE, y + SC, tb);
|
||||
light(lx + S(20), ly, d, NV_LIGHT_MIN, y + SC, tb);
|
||||
light(lx + S(40), ly, d, NV_LIGHT_ZOOM, y + SC, tb);
|
||||
|
||||
int tw = nv_width(F_BODY, title);
|
||||
int base = y + SC + (tb - nv_height(F_BODY)) / 2 + F_BODY->ascent;
|
||||
nv_text(F_BODY, x + (w - tw) / 2, base, title, NV_TXT2,
|
||||
NV_TITLE_TOP, NV_TITLE_BOT, y + SC, tb);
|
||||
}
|
||||
|
||||
static void
|
||||
footer(int x, int y, int w, int h, const char *hint)
|
||||
{
|
||||
if (!hint)
|
||||
return;
|
||||
int fy = y + h - HINT_H;
|
||||
ui_fill_rect(x + S(14), fy, w - S(28), SC, 0x1A1A1Cu);
|
||||
ui_fill_rect(x + S(14), fy + SC, w - S(28), SC, 0x2E2E32u);
|
||||
int tw = nv_width(F_SMALL, hint);
|
||||
int base = fy + (HINT_H - nv_height(F_SMALL)) / 2 + F_SMALL->ascent + SC;
|
||||
nv_text(F_SMALL, x + (w - tw) / 2, base, hint, NV_TXT3,
|
||||
NV_WIN_BODY, NV_WIN_BODY, fy, HINT_H);
|
||||
}
|
||||
|
||||
/* --- keys ------------------------------------------------------------------ */
|
||||
|
||||
#define MAXKEYS 40
|
||||
|
||||
static struct { int x, y, w, h; } KEY[MAXKEYS];
|
||||
|
||||
static struct {
|
||||
const char *title, *hint;
|
||||
const char *const *items;
|
||||
int n, sel, scroll, vis, kind; /* kind: 0 a list of keys, 1 a panel */
|
||||
int has_detail, kh, gap, logo;
|
||||
int wx, wy, ww, wh;
|
||||
int lx, ly, lw; /* the list's box */
|
||||
int ypos[MAXKEYS];
|
||||
} M;
|
||||
|
||||
static int
|
||||
has_nl(const char *s)
|
||||
{
|
||||
for (; *s; s++)
|
||||
if (*s == '\n')
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *
|
||||
label_of(const char *s)
|
||||
{
|
||||
return s[0] == 1 ? s + 1 : s;
|
||||
}
|
||||
|
||||
static const char *
|
||||
detail_of(const char *s)
|
||||
{
|
||||
for (; *s; s++)
|
||||
if (*s == '\n')
|
||||
return s + 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
paint_key(int i, int sel)
|
||||
{
|
||||
if (i < M.scroll || i >= M.scroll + M.vis || i >= MAXKEYS)
|
||||
return;
|
||||
|
||||
int x = KEY[i].x, y = KEY[i].y, w = KEY[i].w, h = KEY[i].h, r = S(8);
|
||||
const char *item = M.items[i];
|
||||
const char *label = label_of(item), *detail = detail_of(item);
|
||||
|
||||
/* Wipe the ring/shadow a previous state left, then draw this one. */
|
||||
ui_fill_rect(x - S(3), y - S(3), w + S(6), h + S(7), NV_WIN_BODY);
|
||||
|
||||
ui_u32 top, bot, edge, hi, fg, fg2;
|
||||
if (sel) {
|
||||
ui_u32 ring = mix(NV_WIN_BODY, SCH_ACCENT, 70, 100);
|
||||
rrect(x - S(2), y - S(2), w + S(4), h + S(4), r + S(2), ring, ring,
|
||||
NV_WIN_BODY, NV_WIN_BODY, 0, 1);
|
||||
top = mix(SCH_ACCENT, WHITE, 20, 100);
|
||||
bot = mix(SCH_ACCENT, BLACK, 24, 100);
|
||||
edge = mix(SCH_ACCENT, BLACK, 62, 100);
|
||||
hi = mix(top, WHITE, 45, 100);
|
||||
fg = WHITE; fg2 = 0xE2DBFFu;
|
||||
} else {
|
||||
rrect(x, y + SC, w, h, r, NV_KEY_SHADOW, NV_KEY_SHADOW,
|
||||
NV_WIN_BODY, NV_WIN_BODY, 0, 1);
|
||||
top = NV_KEY_TOP; bot = NV_KEY_BOT; edge = NV_KEY_EDGE; hi = NV_HILITE;
|
||||
fg = NV_TXT; fg2 = NV_TXT3;
|
||||
}
|
||||
bevel(x, y, w, h, r, top, bot, edge, hi, NV_WIN_BODY, NV_WIN_BODY, 0, 1);
|
||||
|
||||
int ty0 = y + SC, th = h - 2 * SC, tx = x + S(18);
|
||||
if (detail) {
|
||||
int lh = nv_height(F_BODY), dh = nv_height(F_SMALL);
|
||||
int total = lh + S(2) + dh;
|
||||
int t = y + (h - total) / 2;
|
||||
nv_text(F_BODY, tx, t + F_BODY->ascent, label, fg, top, bot, ty0, th);
|
||||
nv_text(F_SMALL, tx, t + lh + S(2) + F_SMALL->ascent, detail,
|
||||
sel ? fg2 : NV_TXT2, top, bot, ty0, th);
|
||||
} else {
|
||||
int base = y + (h - nv_height(F_BODY)) / 2 + F_BODY->ascent;
|
||||
nv_text(F_BODY, tx, base, label, fg, top, bot, ty0, th);
|
||||
}
|
||||
|
||||
if (M.kind == 0 && M.n <= 9 && M.vis == M.n) {
|
||||
char num[2] = { (char)('1' + i), 0 };
|
||||
int nw = nv_width(F_SMALL, num);
|
||||
int base = y + (h - nv_height(F_SMALL)) / 2 + F_SMALL->ascent;
|
||||
nv_text(F_SMALL, x + w - S(18) - nw, base, num, sel ? fg2 : NV_TXT3,
|
||||
top, bot, ty0, th);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
menu_layout(void)
|
||||
{
|
||||
int n = M.n > MAXKEYS ? MAXKEYS : M.n;
|
||||
M.n = n;
|
||||
M.has_detail = 0;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (has_nl(M.items[i]))
|
||||
M.has_detail = 1;
|
||||
M.gap = S(8);
|
||||
M.kh = M.has_detail ? S(50) : S(34);
|
||||
M.ww = S(520);
|
||||
if (M.ww > (int)FB.width - S(24))
|
||||
M.ww = (int)FB.width - S(24);
|
||||
|
||||
int maxh = (int)FB.height - S(24);
|
||||
int chrome = TB_H + BODY_PAD * 2 + HINT_H + 2 * SC;
|
||||
int logoh = S(56) + S(16);
|
||||
int full;
|
||||
|
||||
for (int pass = 0; pass < 2; pass++) {
|
||||
int y = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (pass == 0 && i && M.items[i][0] == 1)
|
||||
y += S(14);
|
||||
M.ypos[i] = y;
|
||||
y += M.kh + M.gap;
|
||||
}
|
||||
full = n ? y - M.gap : 0;
|
||||
if (pass == 0 && chrome + full <= maxh)
|
||||
break;
|
||||
if (pass == 0 && chrome + logoh + full <= maxh)
|
||||
break;
|
||||
}
|
||||
|
||||
M.logo = 1;
|
||||
M.vis = n;
|
||||
int list_h = full;
|
||||
if (chrome + logoh + full > maxh)
|
||||
M.logo = 0;
|
||||
if (chrome + full > maxh) {
|
||||
int avail = maxh - chrome;
|
||||
M.vis = (avail + M.gap) / (M.kh + M.gap);
|
||||
if (M.vis < 1) M.vis = 1;
|
||||
if (M.vis > n) M.vis = n;
|
||||
list_h = M.vis * M.kh + (M.vis - 1) * M.gap;
|
||||
}
|
||||
M.wh = chrome + (M.logo ? logoh : 0) + list_h;
|
||||
M.wx = ((int)FB.width - M.ww) / 2;
|
||||
M.wy = ((int)FB.height - M.wh) / 2;
|
||||
if (M.wy < 0) M.wy = 0;
|
||||
M.lx = M.wx + S(20);
|
||||
M.lw = M.ww - S(40) - (M.vis < n ? S(14) : 0);
|
||||
M.ly = M.wy + SC + TB_H + BODY_PAD + (M.logo ? logoh : 0);
|
||||
|
||||
if (M.sel < M.scroll) M.scroll = M.sel;
|
||||
if (M.sel >= M.scroll + M.vis) M.scroll = M.sel - M.vis + 1;
|
||||
if (M.scroll < 0) M.scroll = 0;
|
||||
if (M.scroll > n - M.vis) M.scroll = n - M.vis;
|
||||
if (M.scroll < 0) M.scroll = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
KEY[i].x = M.lx;
|
||||
KEY[i].w = M.lw;
|
||||
KEY[i].h = M.kh;
|
||||
KEY[i].y = M.ly + M.ypos[i] - M.ypos[M.scroll];
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
menu_paint(void)
|
||||
{
|
||||
cursor_hide();
|
||||
nv_backdrop();
|
||||
window(M.wx, M.wy, M.ww, M.wh, M.title);
|
||||
|
||||
if (M.logo) {
|
||||
ui_u32 saved = LOGO_BG;
|
||||
LOGO_BG = NV_WIN_BODY;
|
||||
ui_logo(M.wy + SC + TB_H + BODY_PAD, S(56));
|
||||
LOGO_BG = saved;
|
||||
}
|
||||
|
||||
for (int i = M.scroll; i < M.scroll + M.vis && i < M.n; i++)
|
||||
paint_key(i, i == M.sel);
|
||||
|
||||
if (M.vis < M.n) {
|
||||
int tx = M.lx + M.lw + S(8), tw = S(4);
|
||||
int th = M.vis * M.kh + (M.vis - 1) * M.gap;
|
||||
rrect(tx, M.ly, tw, th, tw / 2, NV_WELL_TOP, NV_WELL_BOT,
|
||||
NV_WIN_BODY, NV_WIN_BODY, 0, 1);
|
||||
int thumb = th * M.vis / M.n;
|
||||
if (thumb < S(16)) thumb = S(16);
|
||||
int ty = M.ly + (th - thumb) * M.scroll / (M.n - M.vis);
|
||||
rrect(tx, ty, tw, thumb, tw / 2, NV_KEY_TOP, NV_KEY_BOT,
|
||||
NV_WIN_BODY, NV_WIN_BODY, 0, 1);
|
||||
}
|
||||
|
||||
footer(M.wx, M.wy, M.ww, M.wh, M.hint);
|
||||
cursor_show();
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -367,47 +824,111 @@ ui_menu(const char *title, const char *const *items, int n, int selected,
|
|||
{
|
||||
if (!ACTIVE)
|
||||
return;
|
||||
M.title = title; M.items = items; M.n = n; M.sel = selected; M.hint = hint;
|
||||
M.scroll = 0; M.kind = 0;
|
||||
menu_layout();
|
||||
menu_paint();
|
||||
}
|
||||
|
||||
int x, y, w;
|
||||
panel_frame(title, n, hint, &x, &y, &w);
|
||||
void
|
||||
ui_menu_select(int selected)
|
||||
{
|
||||
if (!ACTIVE || M.kind != 0 || selected < 0 || selected >= M.n || selected == M.sel)
|
||||
return;
|
||||
int old = M.sel;
|
||||
M.sel = selected;
|
||||
if (selected < M.scroll || selected >= M.scroll + M.vis) {
|
||||
menu_layout();
|
||||
menu_paint();
|
||||
return;
|
||||
}
|
||||
cursor_hide();
|
||||
paint_key(old, 0);
|
||||
paint_key(selected, 1);
|
||||
cursor_show();
|
||||
}
|
||||
|
||||
int
|
||||
ui_menu_hit(int x, int y)
|
||||
{
|
||||
if (!ACTIVE)
|
||||
return -1;
|
||||
for (int i = M.scroll; i < M.scroll + M.vis && i < M.n; i++)
|
||||
if (x >= KEY[i].x && x < KEY[i].x + KEY[i].w &&
|
||||
y >= KEY[i].y && y < KEY[i].y + KEY[i].h)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* A panel: label/value rows in a well, and one button underneath. With no
|
||||
* values, the labels are a message and are centred. */
|
||||
static void
|
||||
box(const char *title, const char *const *labels, const char *const *values,
|
||||
int n, const char *button, const char *hint)
|
||||
{
|
||||
if (!ACTIVE)
|
||||
return;
|
||||
int rowh = S(28), wpad = S(12);
|
||||
int well_h = n * rowh + 2 * wpad;
|
||||
int btn_h = S(34), btn_w = S(180);
|
||||
|
||||
M.title = title; M.hint = hint; M.kind = 1;
|
||||
M.items = &button; M.n = 1; M.sel = 0; M.scroll = 0; M.vis = 1;
|
||||
M.ww = S(520);
|
||||
if (M.ww > (int)FB.width - S(24))
|
||||
M.ww = (int)FB.width - S(24);
|
||||
M.wh = TB_H + 2 * SC + BODY_PAD + well_h + S(18) + btn_h + BODY_PAD + HINT_H;
|
||||
M.wx = ((int)FB.width - M.ww) / 2;
|
||||
M.wy = ((int)FB.height - M.wh) / 2;
|
||||
if (M.wy < 0) M.wy = 0;
|
||||
|
||||
cursor_hide();
|
||||
nv_backdrop();
|
||||
window(M.wx, M.wy, M.ww, M.wh, title);
|
||||
|
||||
int wx = M.wx + S(20), ww = M.ww - S(40);
|
||||
int wy = M.wy + SC + TB_H + BODY_PAD;
|
||||
well(wx, wy, ww, well_h, NV_WIN_BODY);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
int ry = y + i * ROW_H;
|
||||
|
||||
if (i == selected) {
|
||||
/* The selected row is the only lit thing on the plate. Filling it
|
||||
* with the accent and putting the label in the ground colour reads
|
||||
* at a glance; an outline does not, at this size. */
|
||||
/* Centred on the glyph box: (ROW_H - GLYPH_H) / 2 above it, the
|
||||
* same below. Off-by-a-few here is what makes a highlight look
|
||||
* like it is sitting on the text rather than around it. */
|
||||
ui_fill_rect(x + PANEL_PAD / 2, ry - ROW_LIFT, w - PANEL_PAD, ROW_H,
|
||||
SCH_ACCENT);
|
||||
ui_text(x + PANEL_PAD, ry, items[i], PANEL_BG, SCH_BG_NONE);
|
||||
int ry = wy + wpad + i * rowh;
|
||||
int base = ry + (rowh - nv_height(F_BODY)) / 2 + F_BODY->ascent;
|
||||
if (i)
|
||||
ui_fill_rect(wx + S(14), ry, ww - S(28), SC, 0x1E1E21u);
|
||||
if (values) {
|
||||
nv_text(F_BODY, wx + S(16), base, labels[i], NV_TXT2,
|
||||
NV_WELL_TOP, NV_WELL_BOT, wy, well_h);
|
||||
int vw = nv_width(F_BODY, values[i]);
|
||||
nv_text(F_BODY, wx + ww - S(16) - vw, base, values[i], NV_TXT,
|
||||
NV_WELL_TOP, NV_WELL_BOT, wy, well_h);
|
||||
} else {
|
||||
ui_text(x + PANEL_PAD, ry, items[i], SCH_FG, SCH_BG_NONE);
|
||||
int lw = nv_width(F_BODY, labels[i]);
|
||||
nv_text(F_BODY, wx + (ww - lw) / 2, base, labels[i], NV_TXT,
|
||||
NV_WELL_TOP, NV_WELL_BOT, wy, well_h);
|
||||
}
|
||||
}
|
||||
|
||||
KEY[0].w = btn_w; KEY[0].h = btn_h;
|
||||
KEY[0].x = M.wx + (M.ww - btn_w) / 2;
|
||||
KEY[0].y = wy + well_h + S(18);
|
||||
M.has_detail = 0;
|
||||
paint_key(0, 1);
|
||||
footer(M.wx, M.wy, M.ww, M.wh, hint);
|
||||
cursor_show();
|
||||
}
|
||||
|
||||
void
|
||||
ui_panel(const char *title, const char *const *labels,
|
||||
const char *const *values, int n, const char *hint)
|
||||
{
|
||||
if (!ACTIVE)
|
||||
return;
|
||||
box(title, labels, values, n, "Back", hint);
|
||||
}
|
||||
|
||||
int x, y, w;
|
||||
panel_frame(title, n, hint, &x, &y, &w);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
int ry = y + i * ROW_H;
|
||||
ui_text(x + PANEL_PAD, ry, labels[i], SCH_DIM, SCH_BG_NONE);
|
||||
|
||||
/* Values right-aligned, so the column reads down rather than ragged. */
|
||||
int vw = slen(values[i]) * GLYPH_W;
|
||||
ui_text(x + w - PANEL_PAD - vw, ry, values[i], SCH_FG, SCH_BG_NONE);
|
||||
}
|
||||
void
|
||||
ui_notice(const char *title, const char *const *lines, int n,
|
||||
const char *button, const char *hint)
|
||||
{
|
||||
box(title, lines, 0, n, button, hint);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -77,6 +77,21 @@ void ui_splash(const char *subtitle);
|
|||
void ui_menu(const char *title, const char *const *items, int n, int selected,
|
||||
const char *hint);
|
||||
|
||||
/* Repaint only the keys that changed when the highlight moves (a full paint
|
||||
* redraws the screen). Returns without effect on a panel. */
|
||||
void ui_menu_select(int selected);
|
||||
|
||||
/* Which key is under a point, or -1. A panel has one key, index 0. */
|
||||
int ui_menu_hit(int x, int y);
|
||||
|
||||
/* The pointer: drawn on top of everything, with what is under it saved and put
|
||||
* back. Menus hide it while they repaint and show it again afterwards. */
|
||||
void ui_cursor(int x, int y, int show);
|
||||
|
||||
/* A message in the same window as the panels, with one button. */
|
||||
void ui_notice(const char *title, const char *const *lines, int n,
|
||||
const char *button, const char *hint);
|
||||
|
||||
/* A panel of label/value rows -- the system information screen. */
|
||||
void ui_panel(const char *title, const char *const *labels,
|
||||
const char *const *values, int n, const char *hint);
|
||||
|
|
|
|||
|
|
@ -27,3 +27,11 @@ What this will need, in rough order:
|
|||
|
||||
The image goes at `EFI/BOOT/BOOTX64.EFI` on a FAT ESP, which also means the
|
||||
ISO carries a single El Torito EFI entry.
|
||||
|
||||
## The boot manager
|
||||
|
||||
`F8` (or `o` on a serial line) opens a Niveus-styled boot manager, drawn by
|
||||
`../common/ui.c` in Instrument Sans (`include/gfx/nvfont.h`, regenerate with
|
||||
`make nvfont`). It takes keyboard and pointer, lists other systems it finds on
|
||||
the disks and can chain-load them, and has working Firmware Setup, Restart and
|
||||
Shut Down. See [docs/architecture/boot.md](../../docs/architecture/boot.md#the-boot-manager).
|
||||
|
|
|
|||
|
|
@ -153,17 +153,22 @@ typedef struct {
|
|||
void *LocateDevicePath;
|
||||
void *InstallConfigurationTable;
|
||||
|
||||
void *LoadImage;
|
||||
void *StartImage;
|
||||
EFI_STATUS (*LoadImage)(BOOLEAN BootPolicy, EFI_HANDLE ParentImage,
|
||||
void *DevicePath, void *SourceBuffer,
|
||||
UINTN SourceSize, EFI_HANDLE *ImageHandle);
|
||||
EFI_STATUS (*StartImage)(EFI_HANDLE ImageHandle, UINTN *ExitDataSize,
|
||||
CHAR16 **ExitData);
|
||||
void *Exit;
|
||||
void *UnloadImage;
|
||||
EFI_STATUS (*UnloadImage)(EFI_HANDLE ImageHandle);
|
||||
EFI_STATUS (*ExitBootServices)(EFI_HANDLE ImageHandle, UINTN MapKey);
|
||||
|
||||
void *GetNextMonotonicCount;
|
||||
EFI_STATUS (*Stall)(UINTN Microseconds);
|
||||
void *SetWatchdogTimer;
|
||||
|
||||
void *ConnectController;
|
||||
/* Recursive = TRUE asks for every driver that will bind, down the tree. */
|
||||
EFI_STATUS (*ConnectController)(EFI_HANDLE Controller, EFI_HANDLE *DriverImage,
|
||||
void *RemainingDevicePath, BOOLEAN Recursive);
|
||||
void *DisconnectController;
|
||||
|
||||
void *OpenProtocol;
|
||||
|
|
@ -181,6 +186,43 @@ typedef struct {
|
|||
void **Interface);
|
||||
} EFI_BOOT_SERVICES;
|
||||
|
||||
// --- runtime services -----------------------------------------------------
|
||||
//
|
||||
// Only the two things the boot manager needs: variables, to ask the firmware to
|
||||
// show its own setup on the next start, and ResetSystem, to restart or power
|
||||
// off. The slots before them are spelled out as padding so the offsets hold.
|
||||
|
||||
#define EfiResetCold 0
|
||||
#define EfiResetShutdown 2
|
||||
|
||||
#define EFI_VARIABLE_NON_VOLATILE 0x1
|
||||
#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x2
|
||||
#define EFI_VARIABLE_RUNTIME_ACCESS 0x4
|
||||
|
||||
#define EFI_GLOBAL_VARIABLE_GUID \
|
||||
{ 0x8BE4DF61, 0x93CA, 0x11D2, { 0xAA, 0x0D, 0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C } }
|
||||
|
||||
/* OsIndications bit 0: "boot into the firmware's own user interface". */
|
||||
#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x1ULL
|
||||
|
||||
typedef struct {
|
||||
char Hdr[24];
|
||||
void *GetTime;
|
||||
void *SetTime;
|
||||
void *GetWakeupTime;
|
||||
void *SetWakeupTime;
|
||||
void *SetVirtualAddressMap;
|
||||
void *ConvertPointer;
|
||||
EFI_STATUS (*GetVariable)(CHAR16 *Name, EFI_GUID *Vendor, UINT32 *Attributes,
|
||||
UINTN *DataSize, void *Data);
|
||||
void *GetNextVariableName;
|
||||
EFI_STATUS (*SetVariable)(CHAR16 *Name, EFI_GUID *Vendor, UINT32 Attributes,
|
||||
UINTN DataSize, void *Data);
|
||||
void *GetNextHighMonotonicCount;
|
||||
void (*ResetSystem)(UINTN ResetType, EFI_STATUS Status, UINTN DataSize,
|
||||
void *ResetData);
|
||||
} EFI_RUNTIME_SERVICES;
|
||||
|
||||
typedef struct {
|
||||
char Hdr[24];
|
||||
CHAR16 *FirmwareVendor;
|
||||
|
|
@ -191,7 +233,7 @@ typedef struct {
|
|||
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;
|
||||
EFI_HANDLE StandardErrorHandle;
|
||||
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr;
|
||||
void *RuntimeServices;
|
||||
EFI_RUNTIME_SERVICES *RuntimeServices;
|
||||
EFI_BOOT_SERVICES *BootServices;
|
||||
UINTN NumberOfTableEntries;
|
||||
void *ConfigurationTable;
|
||||
|
|
@ -348,4 +390,42 @@ typedef struct EFI_GRAPHICS_OUTPUT_PROTOCOL {
|
|||
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode;
|
||||
} EFI_GRAPHICS_OUTPUT_PROTOCOL;
|
||||
|
||||
// --- pointers -------------------------------------------------------------
|
||||
//
|
||||
// Two protocols, because firmware offers whichever the device is: a mouse or
|
||||
// touchpad reports movement (SIMPLE_POINTER), a tablet -- QEMU's, and most
|
||||
// touchscreens -- reports where it is (ABSOLUTE_POINTER). Both are polled.
|
||||
|
||||
#define EFI_SIMPLE_POINTER_PROTOCOL_GUID \
|
||||
{ 0x31878C87, 0x0B75, 0x11D5, { 0x9A, 0x4F, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D } }
|
||||
#define EFI_ABSOLUTE_POINTER_PROTOCOL_GUID \
|
||||
{ 0x8D59D32B, 0xC655, 0x4AE9, { 0x9B, 0x15, 0xF2, 0x59, 0x04, 0x99, 0x2A, 0x43 } }
|
||||
|
||||
#define EFI_NOT_READY ((EFI_STATUS)(0x8000000000000000ULL | 6))
|
||||
|
||||
typedef struct { int RelativeMovementX, RelativeMovementY, RelativeMovementZ;
|
||||
BOOLEAN LeftButton, RightButton; } EFI_SIMPLE_POINTER_STATE;
|
||||
typedef struct { UINT64 ResolutionX, ResolutionY, ResolutionZ;
|
||||
BOOLEAN LeftButton, RightButton; } EFI_SIMPLE_POINTER_MODE;
|
||||
typedef struct EFI_SIMPLE_POINTER_PROTOCOL {
|
||||
EFI_STATUS (*Reset)(struct EFI_SIMPLE_POINTER_PROTOCOL *This, BOOLEAN Extended);
|
||||
EFI_STATUS (*GetState)(struct EFI_SIMPLE_POINTER_PROTOCOL *This,
|
||||
EFI_SIMPLE_POINTER_STATE *State);
|
||||
void *WaitForInput;
|
||||
EFI_SIMPLE_POINTER_MODE *Mode;
|
||||
} EFI_SIMPLE_POINTER_PROTOCOL;
|
||||
|
||||
typedef struct { UINT64 CurrentX, CurrentY, CurrentZ; UINT32 ActiveButtons; }
|
||||
EFI_ABSOLUTE_POINTER_STATE;
|
||||
typedef struct { UINT64 AbsoluteMinX, AbsoluteMinY, AbsoluteMinZ,
|
||||
AbsoluteMaxX, AbsoluteMaxY, AbsoluteMaxZ;
|
||||
UINT32 Attributes; } EFI_ABSOLUTE_POINTER_MODE;
|
||||
typedef struct EFI_ABSOLUTE_POINTER_PROTOCOL {
|
||||
EFI_STATUS (*Reset)(struct EFI_ABSOLUTE_POINTER_PROTOCOL *This, BOOLEAN Extended);
|
||||
EFI_STATUS (*GetState)(struct EFI_ABSOLUTE_POINTER_PROTOCOL *This,
|
||||
EFI_ABSOLUTE_POINTER_STATE *State);
|
||||
void *WaitForInput;
|
||||
EFI_ABSOLUTE_POINTER_MODE *Mode;
|
||||
} EFI_ABSOLUTE_POINTER_PROTOCOL;
|
||||
|
||||
#endif /* SCHOS_EFI_H */
|
||||
|
|
|
|||
649
boot/uefi/main.c
649
boot/uefi/main.c
|
|
@ -81,6 +81,8 @@ struct elf64_phdr {
|
|||
#define PT_LOAD 1
|
||||
|
||||
static EFI_SYSTEM_TABLE *ST;
|
||||
static EFI_HANDLE SELF; /* this image */
|
||||
static EFI_HANDLE BOOT_VOL; /* the volume it was loaded from */
|
||||
static EFI_BOOT_SERVICES *BS;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -183,7 +185,20 @@ serial_item(const char *const *items, int i, int sel, int numbered)
|
|||
} else if (numbered) {
|
||||
serial_puts(" "); /* keep the column */
|
||||
}
|
||||
serial_puts(items[i]);
|
||||
/* The label, and its detail in brackets. Items carry both, with a newline
|
||||
* between and a leading \001 where a group begins: the screen uses those
|
||||
* for layout and a serial line has no use for either. */
|
||||
const char *t = items[i];
|
||||
if (t[0] == 1)
|
||||
t++;
|
||||
for (; *t && *t != '\n'; t++)
|
||||
serial_putc(*t);
|
||||
if (*t == '\n') {
|
||||
serial_puts(" (");
|
||||
for (t++; *t; t++)
|
||||
serial_putc(*t);
|
||||
serial_putc(')');
|
||||
}
|
||||
serial_puts("\n");
|
||||
}
|
||||
|
||||
|
|
@ -360,6 +375,7 @@ open_boot_volume(EFI_HANDLE image)
|
|||
// is not a thing to do by accident.
|
||||
st = BS->HandleProtocol(image, &li_guid, (void **)&li);
|
||||
if (EFI_ERROR(st)) die(L"cannot get LOADED_IMAGE", st);
|
||||
BOOT_VOL = li->DeviceHandle;
|
||||
self_base = (UINT64)(UINTN)li->ImageBase;
|
||||
self_size = li->ImageSize;
|
||||
|
||||
|
|
@ -904,6 +920,231 @@ u64_to_dec(UINT64 v, char *out, int cap)
|
|||
out[i] = 0;
|
||||
}
|
||||
|
||||
/* --- pointers, and one place to wait for input ------------------------------
|
||||
*
|
||||
* The boot manager is operated by keyboard, mouse, touchpad or touchscreen, and
|
||||
* every screen waits through here so that all of them behave the same way.
|
||||
*
|
||||
* Both pointer protocols are polled rather than waited on: the loop is already
|
||||
* polling the keyboard, and an event per device would mean a wait-for-any call
|
||||
* that most firmware implements badly. A machine with no pointer simply never
|
||||
* shows a cursor.
|
||||
*/
|
||||
#define MAX_PTR 4
|
||||
static EFI_SIMPLE_POINTER_PROTOCOL *PTR_REL[MAX_PTR];
|
||||
static EFI_ABSOLUTE_POINTER_PROTOCOL *PTR_ABS[MAX_PTR];
|
||||
static int PTR_NREL, PTR_NABS;
|
||||
static int PTR_X, PTR_Y, PTR_DOWN;
|
||||
static int PTR_RESCANS, PTR_TICKS;
|
||||
static int PTR_ACTIVE; /* a real event has arrived; until then there is no cursor */
|
||||
static void u64_to_dec(UINT64 v, char *out, int cap);
|
||||
|
||||
#define EV_NONE 0
|
||||
#define EV_KEY 1
|
||||
#define EV_HOVER 2 /* the pointer moved onto a key: *item */
|
||||
#define EV_CLICK 3 /* a key was clicked: *item */
|
||||
|
||||
#define PE_MOVED 1
|
||||
#define PE_DOWN 2
|
||||
#define PE_UP 4 /* wheel */
|
||||
#define PE_DOWNW 8
|
||||
|
||||
/* Bind every driver to every controller.
|
||||
*
|
||||
* The firmware connects only what it needs to boot: the devices that carry a
|
||||
* console, and the disks. A USB mouse or tablet is neither, so on most
|
||||
* machines its driver has not been started and no pointer protocol exists yet.
|
||||
* Doing it here, when the menu opens, costs a second or so on a real machine
|
||||
* and nothing on a boot that never shows a menu. */
|
||||
static void
|
||||
connect_all(void)
|
||||
{
|
||||
EFI_HANDLE *h = 0;
|
||||
UINTN n = 0;
|
||||
|
||||
if (EFI_ERROR(BS->LocateHandleBuffer(0 /* AllHandles */, 0, 0, &n, &h)) || !h)
|
||||
return;
|
||||
for (UINTN i = 0; i < n; i++)
|
||||
BS->ConnectController(h[i], 0, 0, 1);
|
||||
BS->FreePool(h);
|
||||
}
|
||||
|
||||
static void
|
||||
pointers_find(void)
|
||||
{
|
||||
EFI_GUID g1 = EFI_SIMPLE_POINTER_PROTOCOL_GUID;
|
||||
EFI_GUID g2 = EFI_ABSOLUTE_POINTER_PROTOCOL_GUID;
|
||||
EFI_HANDLE *h = 0;
|
||||
UINTN n = 0;
|
||||
|
||||
if (!EFI_ERROR(BS->LocateHandleBuffer(ByProtocol, &g1, 0, &n, &h)) && h) {
|
||||
for (UINTN i = 0; i < n && PTR_NREL < MAX_PTR; i++) {
|
||||
void *p = 0;
|
||||
if (!EFI_ERROR(BS->HandleProtocol(h[i], &g1, &p)) && p) {
|
||||
EFI_SIMPLE_POINTER_PROTOCOL *sp = p;
|
||||
int seen = 0;
|
||||
for (int k = 0; k < PTR_NREL; k++)
|
||||
if (PTR_REL[k] == sp) seen = 1;
|
||||
if (!seen) {
|
||||
sp->Reset(sp, 0);
|
||||
PTR_REL[PTR_NREL++] = sp;
|
||||
}
|
||||
}
|
||||
}
|
||||
BS->FreePool(h);
|
||||
}
|
||||
h = 0; n = 0;
|
||||
if (!EFI_ERROR(BS->LocateHandleBuffer(ByProtocol, &g2, 0, &n, &h)) && h) {
|
||||
for (UINTN i = 0; i < n && PTR_NABS < MAX_PTR; i++) {
|
||||
void *p = 0;
|
||||
if (!EFI_ERROR(BS->HandleProtocol(h[i], &g2, &p)) && p) {
|
||||
EFI_ABSOLUTE_POINTER_PROTOCOL *ap = p;
|
||||
int seen = 0;
|
||||
for (int k = 0; k < PTR_NABS; k++)
|
||||
if (PTR_ABS[k] == ap) seen = 1;
|
||||
if (!seen) {
|
||||
ap->Reset(ap, 0);
|
||||
PTR_ABS[PTR_NABS++] = ap;
|
||||
}
|
||||
}
|
||||
}
|
||||
BS->FreePool(h);
|
||||
}
|
||||
if (PTR_X == 0 && PTR_Y == 0)
|
||||
PTR_X = PTR_Y = -1; /* centred on first use, when the size is known */
|
||||
}
|
||||
|
||||
static int
|
||||
pointer_poll(void)
|
||||
{
|
||||
int ev = 0, w = (int)ui_width(), h = (int)ui_height();
|
||||
int down = PTR_DOWN;
|
||||
|
||||
for (int i = 0; i < PTR_NREL; i++) {
|
||||
EFI_SIMPLE_POINTER_STATE st;
|
||||
if (EFI_ERROR(PTR_REL[i]->GetState(PTR_REL[i], &st)))
|
||||
continue;
|
||||
int dx = st.RelativeMovementX, dy = st.RelativeMovementY;
|
||||
/* A little acceleration: the counts are the device's, not pixels, and
|
||||
* a fast flick has to be able to cross a 4K panel. */
|
||||
if (dx > 6 || dx < -6) dx *= 2;
|
||||
if (dy > 6 || dy < -6) dy *= 2;
|
||||
if (dx || dy) {
|
||||
PTR_X += dx; PTR_Y += dy;
|
||||
ev |= PE_MOVED;
|
||||
}
|
||||
if (st.RelativeMovementZ)
|
||||
ev |= st.RelativeMovementZ < 0 ? PE_UP : PE_DOWNW;
|
||||
down = st.LeftButton;
|
||||
}
|
||||
for (int i = 0; i < PTR_NABS; i++) {
|
||||
EFI_ABSOLUTE_POINTER_STATE st;
|
||||
EFI_ABSOLUTE_POINTER_MODE *m = PTR_ABS[i]->Mode;
|
||||
if (EFI_ERROR(PTR_ABS[i]->GetState(PTR_ABS[i], &st)) || !m)
|
||||
continue;
|
||||
UINT64 rx = m->AbsoluteMaxX - m->AbsoluteMinX;
|
||||
UINT64 ry = m->AbsoluteMaxY - m->AbsoluteMinY;
|
||||
if (rx && ry) {
|
||||
PTR_X = (int)(((st.CurrentX - m->AbsoluteMinX) * (UINT64)(w - 1)) / rx);
|
||||
PTR_Y = (int)(((st.CurrentY - m->AbsoluteMinY) * (UINT64)(h - 1)) / ry);
|
||||
ev |= PE_MOVED;
|
||||
}
|
||||
down = (int)(st.ActiveButtons & 1);
|
||||
}
|
||||
|
||||
if (PTR_X < 0) PTR_X = 0;
|
||||
if (PTR_Y < 0) PTR_Y = 0;
|
||||
if (PTR_X >= w) PTR_X = w - 1;
|
||||
if (PTR_Y >= h) PTR_Y = h - 1;
|
||||
if (down && !PTR_DOWN)
|
||||
ev |= PE_DOWN;
|
||||
PTR_DOWN = down;
|
||||
return ev;
|
||||
}
|
||||
|
||||
/* Wait for a key, a click on a key, or the pointer moving onto one.
|
||||
*
|
||||
* `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)
|
||||
{
|
||||
int have = PTR_NREL + PTR_NABS;
|
||||
int last = -2;
|
||||
|
||||
if (PTR_X < 0) {
|
||||
PTR_X = (int)ui_width() / 2;
|
||||
PTR_Y = (int)ui_height() / 2;
|
||||
}
|
||||
ui_cursor(PTR_X, PTR_Y, PTR_ACTIVE);
|
||||
for (;;) {
|
||||
if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, key)))
|
||||
return EV_KEY;
|
||||
/* USB devices appear some time after their controller is connected,
|
||||
* and the menu is already on screen by then: look again, twice a
|
||||
* second for the first ten. */
|
||||
if (PTR_RESCANS < 20 && ++PTR_TICKS >= 250) {
|
||||
PTR_TICKS = 0;
|
||||
PTR_RESCANS++;
|
||||
pointers_find();
|
||||
have = PTR_NREL + PTR_NABS;
|
||||
}
|
||||
if (have) {
|
||||
int e = pointer_poll();
|
||||
if ((e & (PE_MOVED | PE_DOWN)) && !PTR_ACTIVE)
|
||||
PTR_ACTIVE = 1;
|
||||
if (e & PE_MOVED || ((e & PE_DOWN) && PTR_ACTIVE))
|
||||
ui_cursor(PTR_X, PTR_Y, PTR_ACTIVE);
|
||||
if (e & (PE_UP | PE_DOWNW)) {
|
||||
key->UnicodeChar = 0;
|
||||
key->ScanCode = (e & PE_UP) ? 0x01 : 0x02;
|
||||
return EV_KEY;
|
||||
}
|
||||
int hit = ui_menu_hit(PTR_X, PTR_Y);
|
||||
if ((e & PE_DOWN) && hit >= 0) {
|
||||
*item = hit;
|
||||
return EV_CLICK;
|
||||
}
|
||||
if ((e & PE_MOVED) && hit >= 0 && hit != last) {
|
||||
last = hit;
|
||||
*item = hit;
|
||||
return EV_HOVER;
|
||||
}
|
||||
if (hit < 0)
|
||||
last = -2;
|
||||
}
|
||||
if (budget) {
|
||||
if (*budget < 2)
|
||||
return EV_NONE;
|
||||
*budget -= 2;
|
||||
}
|
||||
BS->Stall(2000);
|
||||
}
|
||||
}
|
||||
|
||||
/* A message and a button. Any key or a click dismisses it. */
|
||||
static void
|
||||
notice(const char *title, const char *const *lines, int n, const char *button,
|
||||
const char *hint)
|
||||
{
|
||||
EFI_INPUT_KEY key;
|
||||
int item = 0;
|
||||
|
||||
ui_notice(title, lines, n, button, hint);
|
||||
mirrors_show();
|
||||
for (;;) {
|
||||
int e = wait_event(&key, &item, 0);
|
||||
if (e == EV_KEY || e == EV_CLICK)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
res_row(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, char *out, int cap)
|
||||
{
|
||||
|
|
@ -916,6 +1157,8 @@ res_row(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, char *out, int cap)
|
|||
for (char *p = wa; *p && i < cap - 1; p++) out[i++] = *p;
|
||||
if (i < cap - 1) out[i++] = 'x';
|
||||
for (char *p = ha; *p && i < cap - 1; p++) out[i++] = *p;
|
||||
for (const char *p = "\nChange the screen size"; *p && i < cap - 1; p++)
|
||||
out[i++] = *p;
|
||||
out[i] = 0;
|
||||
}
|
||||
|
||||
|
|
@ -1038,16 +1281,16 @@ res_read_number(const char *prompt)
|
|||
shown[i] = 0;
|
||||
|
||||
const char *rows[1] = { shown };
|
||||
ui_menu("Custom resolution", rows, 1, 0, "digits, Enter to accept");
|
||||
ui_notice("Custom resolution", rows, 1, "Accept", "digits, Enter to accept");
|
||||
mirrors_show();
|
||||
|
||||
EFI_INPUT_KEY key;
|
||||
for (;;) {
|
||||
if (ST->ConIn &&
|
||||
!EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
|
||||
break;
|
||||
BS->Stall(10000);
|
||||
}
|
||||
int item = 0, e;
|
||||
do
|
||||
e = wait_event(&key, &item, 0);
|
||||
while (e == EV_HOVER);
|
||||
if (e == EV_CLICK)
|
||||
return v;
|
||||
if (key.UnicodeChar == L'\r' || key.UnicodeChar == L'\n')
|
||||
return v;
|
||||
if (key.UnicodeChar >= L'0' && key.UnicodeChar <= L'9')
|
||||
|
|
@ -1091,15 +1334,21 @@ mode_confirmed(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
uifb.height = gop->Mode->Info->VerticalResolution;
|
||||
uifb.stride = gop->Mode->Info->PixelsPerScanLine * 4;
|
||||
ui_init(&uifb);
|
||||
ui_menu("Resolution", msg, 2, 0, "going back in 10 seconds");
|
||||
ui_notice("Resolution", msg, 2, "Keep", "going back in 10 seconds");
|
||||
|
||||
if (ST->ConIn)
|
||||
while (!EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
|
||||
; /* the key that chose the mode is not an answer */
|
||||
for (UINTN waited = 0; waited < 10000; waited += 10) {
|
||||
if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
|
||||
UINTN left = 10000; /* one countdown, however many hovers */
|
||||
for (;;) {
|
||||
int item = 0;
|
||||
int e = wait_event(&key, &item, &left);
|
||||
if (e == EV_NONE)
|
||||
break;
|
||||
if (e == EV_CLICK)
|
||||
return 1;
|
||||
if (e == EV_KEY)
|
||||
return key.UnicodeChar == L'\r' || key.UnicodeChar == L'\n';
|
||||
BS->Stall(10000);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1136,7 +1385,6 @@ static void
|
|||
res_refused(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
||||
{
|
||||
struct ui_fb uifb;
|
||||
EFI_INPUT_KEY key;
|
||||
const char *msg[1] = { "Not kept -- back to the size before" };
|
||||
|
||||
uifb.base = (ui_u8 *)(UINTN)gop->Mode->FrameBufferBase;
|
||||
|
|
@ -1144,13 +1392,7 @@ res_refused(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
uifb.height = gop->Mode->Info->VerticalResolution;
|
||||
uifb.stride = gop->Mode->Info->PixelsPerScanLine * 4;
|
||||
ui_init(&uifb);
|
||||
ui_menu("Resolution", msg, 1, 0, "any key to go back");
|
||||
mirrors_show();
|
||||
for (;;) {
|
||||
if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
|
||||
break;
|
||||
BS->Stall(10000);
|
||||
}
|
||||
notice("Resolution", msg, 1, "OK", "any key to go back");
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -1158,6 +1400,8 @@ res_choose(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
{
|
||||
int sel = 0;
|
||||
int echoed = -1;
|
||||
static const char hint[] =
|
||||
"W/S or arrows to choose, Enter to apply, Esc or Q to go back";
|
||||
|
||||
for (;;) {
|
||||
struct ui_fb uifb;
|
||||
|
|
@ -1174,41 +1418,53 @@ res_choose(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
items[i] = RES_TEXT[i];
|
||||
|
||||
if (sel > RES_N) sel = RES_N;
|
||||
ui_menu("Resolution", items, RES_N + 1, sel,
|
||||
"W/S to choose, Enter to apply, Esc or Q to go back");
|
||||
ui_menu("Resolution", items, RES_N + 1, sel, hint);
|
||||
|
||||
if (echoed < 0) {
|
||||
serial_menu("Resolution", items, RES_N + 1, sel,
|
||||
"W/S to choose, Enter to apply, Esc or Q to go back", 0);
|
||||
} else if (echoed != sel) {
|
||||
serial_item(items, sel, sel, 0);
|
||||
}
|
||||
if (echoed < 0)
|
||||
serial_menu("Resolution", items, RES_N + 1, sel, hint, 0);
|
||||
echoed = sel;
|
||||
mirrors_show();
|
||||
|
||||
EFI_INPUT_KEY key;
|
||||
for (;;) {
|
||||
if (ST->ConIn &&
|
||||
!EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
|
||||
break;
|
||||
BS->Stall(10000);
|
||||
}
|
||||
EFI_INPUT_KEY key;
|
||||
int item = -1, act = 0;
|
||||
int e = wait_event(&key, &item, 0);
|
||||
|
||||
if (e == EV_HOVER || e == EV_CLICK) {
|
||||
if (item != sel) {
|
||||
sel = item;
|
||||
ui_menu_select(sel);
|
||||
serial_item(items, sel, sel, 0);
|
||||
mirrors_show();
|
||||
}
|
||||
act = e == EV_CLICK;
|
||||
} else {
|
||||
CHAR16 c = key.UnicodeChar;
|
||||
/* Q as well as Esc, for the same reason f8_window() accepts 'o' as
|
||||
* well as F8: on a serial line a lone ESC byte is indistinguishable
|
||||
* from the start of an escape sequence, so the terminal driver holds
|
||||
* it back waiting for the rest and it never arrives as a key. Esc is
|
||||
* the obvious thing to press on a screen and unreachable on a cable. */
|
||||
/* Q as well as Esc, for the same reason f8_window() accepts
|
||||
* 'o' as well as F8: on a serial line a lone ESC byte is
|
||||
* indistinguishable from the start of an escape sequence, so
|
||||
* the terminal driver holds it back waiting for the rest and
|
||||
* it never arrives as a key. Esc is the obvious thing to
|
||||
* press on a screen and unreachable on a cable. */
|
||||
if (key.ScanCode == 0x17 || c == 27 || c == L'q' || c == L'Q')
|
||||
return;
|
||||
if (key.ScanCode == 0x01 || c == L'w' || c == L'W') {
|
||||
sel = (sel + RES_N) % (RES_N + 1); continue;
|
||||
int to = sel;
|
||||
if (key.ScanCode == 0x01 || c == L'w' || c == L'W')
|
||||
to = (sel + RES_N) % (RES_N + 1);
|
||||
else if (key.ScanCode == 0x02 || c == L's' || c == L'S')
|
||||
to = (sel + 1) % (RES_N + 1);
|
||||
else if (c == L'\r' || c == L'\n')
|
||||
act = 1;
|
||||
if (to != sel) {
|
||||
sel = to;
|
||||
ui_menu_select(sel);
|
||||
serial_item(items, sel, sel, 0);
|
||||
mirrors_show();
|
||||
}
|
||||
if (key.ScanCode == 0x02 || c == L's' || c == L'S') {
|
||||
sel = (sel + 1) % (RES_N + 1); continue;
|
||||
}
|
||||
if (c == L'\r' || c == L'\n') {
|
||||
if (!act)
|
||||
continue;
|
||||
|
||||
if (sel < RES_N) {
|
||||
if (!mode_set_checked(gop, RES_MODE[sel])) {
|
||||
res_refused(gop);
|
||||
|
|
@ -1242,14 +1498,7 @@ res_choose(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
/* Nothing matched. Saying so beats silently keeping the old mode
|
||||
* and letting the user wonder whether the key registered. */
|
||||
const char *msg[1] = { "No such mode -- keeping this one" };
|
||||
ui_menu("Resolution", msg, 1, 0, "any key to go back");
|
||||
mirrors_show();
|
||||
for (;;) {
|
||||
if (ST->ConIn &&
|
||||
!EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
|
||||
break;
|
||||
BS->Stall(10000);
|
||||
}
|
||||
notice("Resolution", msg, 1, "OK", "any key to go back");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1505,19 +1754,205 @@ sys_info(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
mirrors_show();
|
||||
|
||||
EFI_INPUT_KEY key;
|
||||
int item = 0;
|
||||
for (;;) {
|
||||
if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
|
||||
int e = wait_event(&key, &item, 0);
|
||||
if (e == EV_KEY || e == EV_CLICK)
|
||||
return;
|
||||
BS->Stall(10000);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- other systems ------------------------------------------------------------
|
||||
*
|
||||
* A boot manager that can only boot itself is a menu with one item. Other
|
||||
* operating systems on this machine leave their loader at a known path on an
|
||||
* EFI system partition, and chain-loading one is the firmware's own
|
||||
* LoadImage and StartImage: no ExitBootServices of ours is involved, and if the
|
||||
* other loader gives control back the menu is still here.
|
||||
*
|
||||
* Only paths that are well known are probed. Walking every directory of every
|
||||
* volume would find more and would also find things that are not an operating
|
||||
* system, and a boot menu that lists a firmware updater is not an improvement.
|
||||
*/
|
||||
#define MAX_OTHERS 6
|
||||
|
||||
static const struct { const CHAR16 *path; const char *name; } KNOWN_LOADERS[] = {
|
||||
{ L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi", "Windows Boot Manager" },
|
||||
{ L"\\EFI\\ubuntu\\shimx64.efi", "Ubuntu" },
|
||||
{ L"\\EFI\\debian\\shimx64.efi", "Debian" },
|
||||
{ L"\\EFI\\fedora\\shimx64.efi", "Fedora" },
|
||||
{ L"\\EFI\\arch\\grubx64.efi", "Arch Linux" },
|
||||
{ L"\\EFI\\opensuse\\grubx64.efi", "openSUSE" },
|
||||
{ L"\\EFI\\systemd\\systemd-bootx64.efi", "systemd-boot" },
|
||||
{ L"\\EFI\\refind\\refind_x64.efi", "rEFInd" },
|
||||
{ L"\\EFI\\BOOT\\BOOTX64.EFI", "Another EFI loader" },
|
||||
};
|
||||
|
||||
static struct {
|
||||
EFI_HANDLE vol;
|
||||
const CHAR16 *path;
|
||||
char text[96]; /* "name\ndetail", as the menu wants it */
|
||||
} OTHERS[MAX_OTHERS];
|
||||
static int OTHER_N;
|
||||
|
||||
static void
|
||||
others_find(void)
|
||||
{
|
||||
EFI_GUID fs_guid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
|
||||
EFI_HANDLE *h = 0;
|
||||
UINTN n = 0;
|
||||
|
||||
OTHER_N = 0;
|
||||
if (EFI_ERROR(BS->LocateHandleBuffer(ByProtocol, &fs_guid, 0, &n, &h)) || !h)
|
||||
return;
|
||||
|
||||
for (UINTN i = 0; i < n && OTHER_N < MAX_OTHERS; i++) {
|
||||
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fs = 0;
|
||||
EFI_FILE_PROTOCOL *root = 0;
|
||||
if (EFI_ERROR(BS->HandleProtocol(h[i], &fs_guid, (void **)&fs)) || !fs)
|
||||
continue;
|
||||
if (EFI_ERROR(fs->OpenVolume(fs, &root)) || !root)
|
||||
continue;
|
||||
|
||||
for (UINTN k = 0; k < sizeof KNOWN_LOADERS / sizeof KNOWN_LOADERS[0] &&
|
||||
OTHER_N < MAX_OTHERS; k++) {
|
||||
/* Our own volume is probed too -- SchOS shares the ESP with
|
||||
* Windows on a laptop -- but the fallback path there is this
|
||||
* loader, and offering to start ourselves again is no help. */
|
||||
if (h[i] == BOOT_VOL && k == sizeof KNOWN_LOADERS / sizeof KNOWN_LOADERS[0] - 1)
|
||||
continue;
|
||||
EFI_FILE_PROTOCOL *f = 0;
|
||||
if (EFI_ERROR(root->Open(root, &f, (CHAR16 *)KNOWN_LOADERS[k].path,
|
||||
EFI_FILE_MODE_READ, 0)) || !f)
|
||||
continue;
|
||||
f->Close(f);
|
||||
|
||||
int o = OTHER_N++;
|
||||
OTHERS[o].vol = h[i];
|
||||
OTHERS[o].path = KNOWN_LOADERS[k].path;
|
||||
int c = 0;
|
||||
for (const char *q = KNOWN_LOADERS[k].name; *q && c < 40; q++)
|
||||
OTHERS[o].text[c++] = *q;
|
||||
OTHERS[o].text[c++] = '\n';
|
||||
for (const char *q = "Start it from "; *q; q++)
|
||||
OTHERS[o].text[c++] = *q;
|
||||
char pn[48];
|
||||
narrow(KNOWN_LOADERS[k].path, pn, sizeof pn);
|
||||
for (const char *q = pn; *q && c < (int)sizeof OTHERS[o].text - 1; q++)
|
||||
OTHERS[o].text[c++] = *q;
|
||||
OTHERS[o].text[c] = 0;
|
||||
}
|
||||
root->Close(root);
|
||||
}
|
||||
BS->FreePool(h);
|
||||
}
|
||||
|
||||
/* Start another loader. The device path for LoadImage is the volume's own path
|
||||
* with one more node on the end naming the file: copy the volume's nodes up to
|
||||
* its terminator, append a file-path node, and terminate again. */
|
||||
static void
|
||||
chain_load(int o)
|
||||
{
|
||||
EFI_GUID dp_guid = EFI_DEVICE_PATH_PROTOCOL_GUID;
|
||||
static UINT8 dp[512];
|
||||
UINT8 *vol = 0;
|
||||
UINTN len = 0;
|
||||
|
||||
if (EFI_ERROR(BS->HandleProtocol(OTHERS[o].vol, &dp_guid, (void **)&vol)) || !vol)
|
||||
return;
|
||||
|
||||
for (;;) {
|
||||
UINTN nl = (UINTN)vol[len + 2] | ((UINTN)vol[len + 3] << 8);
|
||||
if (nl < 4 || len + nl > sizeof dp - 64)
|
||||
return;
|
||||
if (vol[len] == 0x7F && vol[len + 1] == 0xFF) /* End of Entire Path */
|
||||
break;
|
||||
len += nl;
|
||||
}
|
||||
copy(dp, vol, len);
|
||||
|
||||
UINTN chars = 0;
|
||||
while (OTHERS[o].path[chars])
|
||||
chars++;
|
||||
UINTN nl = 4 + (chars + 1) * 2;
|
||||
if (len + nl + 4 > sizeof dp)
|
||||
return;
|
||||
dp[len] = 4; dp[len + 1] = 4; /* media, file path */
|
||||
dp[len + 2] = (UINT8)nl; dp[len + 3] = (UINT8)(nl >> 8);
|
||||
copy(dp + len + 4, OTHERS[o].path, (chars + 1) * 2);
|
||||
len += nl;
|
||||
dp[len] = 0x7F; dp[len + 1] = 0xFF; dp[len + 2] = 4; dp[len + 3] = 0;
|
||||
|
||||
EFI_HANDLE img = 0;
|
||||
if (EFI_ERROR(BS->LoadImage(0, SELF, dp, 0, 0, &img)) || !img) {
|
||||
const char *msg[1] = { "The firmware would not load it." };
|
||||
notice("Start another system", msg, 1, "OK", "any key to go back");
|
||||
return;
|
||||
}
|
||||
BS->StartImage(img, 0, 0);
|
||||
BS->UnloadImage(img);
|
||||
}
|
||||
|
||||
/* Ask the firmware to open its own setup on the next start, then restart.
|
||||
*
|
||||
* This used to halt with "reset to reach firmware setup", which is the screen
|
||||
* of a loader that never learned the protocol. The firmware publishes a
|
||||
* variable for exactly this: OsIndicationsSupported says what it can do and
|
||||
* OsIndications is where the request goes. Not every firmware has it, and the
|
||||
* ones that do not are told so rather than restarted for nothing. */
|
||||
static void
|
||||
firmware_setup(void)
|
||||
{
|
||||
EFI_RUNTIME_SERVICES *rt = ST->RuntimeServices;
|
||||
EFI_GUID gv = EFI_GLOBAL_VARIABLE_GUID;
|
||||
UINT64 supported = 0, cur = 0;
|
||||
UINTN sz = sizeof supported;
|
||||
UINT32 attr = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
||||
EFI_VARIABLE_RUNTIME_ACCESS;
|
||||
|
||||
if (EFI_ERROR(rt->GetVariable(L"OsIndicationsSupported", &gv, 0, &sz,
|
||||
&supported)) ||
|
||||
!(supported & EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) {
|
||||
const char *msg[2] = { "This firmware cannot be asked to open its",
|
||||
"setup. Use its own key when powering on." };
|
||||
notice("Firmware Setup", msg, 2, "OK", "any key to go back");
|
||||
return;
|
||||
}
|
||||
|
||||
UINT32 a = 0;
|
||||
sz = sizeof cur;
|
||||
if (EFI_ERROR(rt->GetVariable(L"OsIndications", &gv, &a, &sz, &cur)))
|
||||
cur = 0;
|
||||
cur |= EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
|
||||
if (EFI_ERROR(rt->SetVariable(L"OsIndications", &gv, attr, sizeof cur, &cur))) {
|
||||
const char *msg[1] = { "The firmware refused the request." };
|
||||
notice("Firmware Setup", msg, 1, "OK", "any key to go back");
|
||||
return;
|
||||
}
|
||||
rt->ResetSystem(EfiResetCold, 0, 0, 0);
|
||||
for (;;) __asm__ volatile("hlt");
|
||||
}
|
||||
|
||||
static void
|
||||
power(UINTN how)
|
||||
{
|
||||
ST->RuntimeServices->ResetSystem(how, 0, 0, 0);
|
||||
for (;;) __asm__ volatile("hlt");
|
||||
}
|
||||
|
||||
enum { A_BOOT, A_VERBOSE, A_OTHER, A_DISPLAY, A_INFO, A_FIRMWARE, A_RESTART,
|
||||
A_SHUTDOWN, A_DEBUG };
|
||||
|
||||
static int
|
||||
boot_menu(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
||||
{
|
||||
int selected = 0;
|
||||
int echoed = -1; // the serial copy: header once, then moves
|
||||
char resbuf[48];
|
||||
char resbuf[96];
|
||||
|
||||
connect_all();
|
||||
pointers_find();
|
||||
others_find();
|
||||
|
||||
for (;;) {
|
||||
/* Re-read the framebuffer every pass: choosing a resolution moves it,
|
||||
|
|
@ -1532,29 +1967,38 @@ boot_menu(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
|
||||
res_row(gop, resbuf, sizeof resbuf);
|
||||
|
||||
// Six entries in a debug build, five in a release one.
|
||||
//
|
||||
// The debugger entry is compiled out rather than hidden, because a
|
||||
// menu item that is present and refuses is an invitation to work out
|
||||
// how to make it not refuse. An ISO built for anybody else has no way
|
||||
// to turn a network debugger on, because the code that would set the
|
||||
// flag is not in it.
|
||||
const char *items[6];
|
||||
int nitems = 5;
|
||||
items[0] = "Boot SchOS";
|
||||
items[1] = "Boot SchOS (verbose)";
|
||||
items[2] = resbuf;
|
||||
items[3] = "System Information";
|
||||
items[4] = "Reboot to firmware";
|
||||
//
|
||||
// An item is a label, then a newline and a line of detail; a leading
|
||||
// \001 starts a new group, drawn with a gap above it. `what` says what
|
||||
// each row does, because the number of rows is not fixed: other
|
||||
// systems found on the disks are listed after the two ways of
|
||||
// starting this one.
|
||||
const char *items[8 + MAX_OTHERS];
|
||||
int what[8 + MAX_OTHERS], arg[8 + MAX_OTHERS];
|
||||
int nitems = 0;
|
||||
#define ITEM(text, a, x) do { items[nitems] = (text); what[nitems] = (a); arg[nitems] = (x); nitems++; } while (0)
|
||||
ITEM("Boot SchOS\nStart the system", A_BOOT, 0);
|
||||
ITEM("Boot SchOS (verbose)\nShow kernel messages while starting", A_VERBOSE, 0);
|
||||
for (int o = 0; o < OTHER_N; o++)
|
||||
ITEM(OTHERS[o].text, A_OTHER, o);
|
||||
ITEM(resbuf, A_DISPLAY, 0);
|
||||
ITEM("System Information\nFirmware, processor and memory", A_INFO, 0);
|
||||
ITEM("\001Firmware Setup\nRestart into the UEFI settings", A_FIRMWARE, 0);
|
||||
ITEM("Restart\nReboot the machine", A_RESTART, 0);
|
||||
ITEM("Shut Down\nPower the machine off", A_SHUTDOWN, 0);
|
||||
#ifdef SCHOS_DEBUG
|
||||
items[5] = DEBUG_BOOT ? "Remote debugging: on (schdbgd, port 4444)"
|
||||
: "Remote debugging: off";
|
||||
nitems = 6;
|
||||
ITEM(DEBUG_BOOT ? "Remote debugging: on\nschdbgd, port 4444"
|
||||
: "Remote debugging: off\nschdbgd, port 4444", A_DEBUG, 0);
|
||||
#endif
|
||||
const char *hint = nitems == 6 ? "W/S or 1-6 to choose, Enter to select"
|
||||
: "W/S or 1-5 to choose, Enter to select";
|
||||
const char *hint = "W/S or arrows to choose, Enter to select - number keys jump";
|
||||
if (selected >= nitems) selected = 0;
|
||||
|
||||
ui_menu("Boot Manager", items, nitems, selected, hint);
|
||||
ui_menu("SchOS Boot Manager", items, nitems, selected, hint);
|
||||
|
||||
// The same menu on the serial line: the whole thing once, then one
|
||||
// line per *move*. Reprinting on every keypress would be correct and
|
||||
|
|
@ -1562,46 +2006,58 @@ boot_menu(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
// unrecognised key would scroll the selection past again for nothing.
|
||||
if (echoed < 0) {
|
||||
serial_menu("Boot Manager", items, nitems, selected, hint, 1);
|
||||
} else if (echoed != selected) {
|
||||
serial_item(items, selected, selected, 1);
|
||||
}
|
||||
echoed = selected;
|
||||
mirrors_show();
|
||||
|
||||
EFI_INPUT_KEY key;
|
||||
CHAR16 c = 0;
|
||||
UINT16 scan = 0;
|
||||
for (;;) {
|
||||
if (ST->ConIn &&
|
||||
!EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key))) {
|
||||
c = key.UnicodeChar; scan = key.ScanCode; break;
|
||||
}
|
||||
BS->Stall(10000);
|
||||
EFI_INPUT_KEY key;
|
||||
int item = -1, act = 0;
|
||||
int e = wait_event(&key, &item, 0);
|
||||
int to = selected;
|
||||
|
||||
if (e == EV_HOVER || e == EV_CLICK) {
|
||||
to = item;
|
||||
act = e == EV_CLICK;
|
||||
} else {
|
||||
CHAR16 c = key.UnicodeChar;
|
||||
if (key.ScanCode == 0x01 || c == L'w' || c == L'W')
|
||||
to = (selected + nitems - 1) % nitems;
|
||||
else if (key.ScanCode == 0x02 || c == L's' || c == L'S')
|
||||
to = (selected + 1) % nitems;
|
||||
else if (c >= L'1' && c <= L'9' && (int)(c - L'1') < nitems)
|
||||
to = (int)(c - L'1');
|
||||
else if (c == L'\r' || c == L'\n')
|
||||
act = 1;
|
||||
}
|
||||
|
||||
if (scan == 0x01) { selected = (selected + nitems - 1) % nitems; continue; }
|
||||
if (scan == 0x02) { selected = (selected + 1) % nitems; continue; }
|
||||
if (c == L'w' || c == L'W') { selected = (selected + nitems - 1) % nitems; continue; }
|
||||
if (c == L's' || c == L'S') { selected = (selected + 1) % nitems; continue; }
|
||||
if (c >= L'1' && c <= L'0' + nitems) { selected = (int)(c - L'1'); continue; }
|
||||
if (to != selected) {
|
||||
selected = to;
|
||||
ui_menu_select(selected);
|
||||
serial_item(items, selected, selected, 1);
|
||||
echoed = selected;
|
||||
mirrors_show();
|
||||
}
|
||||
if (!act)
|
||||
continue;
|
||||
|
||||
if (c == L'\r' || c == L'\n') {
|
||||
if (selected == 0) return 0;
|
||||
if (selected == 1) return 1;
|
||||
if (selected == 2) { res_choose(gop); continue; }
|
||||
if (selected == 3) { sys_info(gop); continue; }
|
||||
switch (what[selected]) {
|
||||
case A_BOOT: return 0;
|
||||
case A_VERBOSE: return 1;
|
||||
case A_OTHER: chain_load(arg[selected]); break;
|
||||
case A_DISPLAY: res_choose(gop); break;
|
||||
case A_INFO: sys_info(gop); break;
|
||||
case A_FIRMWARE: firmware_setup(); break;
|
||||
case A_RESTART: power(EfiResetCold); break;
|
||||
case A_SHUTDOWN: power(EfiResetShutdown); break;
|
||||
#ifdef SCHOS_DEBUG
|
||||
// A toggle rather than an action: picking it changes what the
|
||||
// entry says and leaves you in the menu, so the state you booted
|
||||
// with is the state you can see before you boot.
|
||||
if (selected == 5) { DEBUG_BOOT = !DEBUG_BOOT; echoed = -1; continue; }
|
||||
case A_DEBUG: DEBUG_BOOT = !DEBUG_BOOT; echoed = -1; break;
|
||||
#endif
|
||||
if (selected == 4) {
|
||||
ui_clear();
|
||||
ui_text_centered((int)ui_height() / 2, "Halted -- reset to reach firmware setup",
|
||||
0xB9BEC7u);
|
||||
for (;;) __asm__ volatile("hlt");
|
||||
}
|
||||
break; // repaint: the thing that ran may have changed the screen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2180,6 +2636,7 @@ EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
|
|||
{
|
||||
ST = systab;
|
||||
BS = systab->BootServices;
|
||||
SELF = image;
|
||||
|
||||
print(L"SchOS UEFI loader\r\n");
|
||||
|
||||
|
|
|
|||
|
|
@ -330,9 +330,64 @@ Dropping back to protected mode to re-enter through the 32-bit multiboot path
|
|||
is a mode switch whose failure mode is a triple fault with no console. The
|
||||
32-bit entry is still present in `entry.S` and now has no caller.
|
||||
|
||||
### The boot manager
|
||||
|
||||
F8 (or `o` on a serial line) opens the boot manager. It used to be a text list
|
||||
on a dark plate; it is now drawn in the Niveus language the site is drawn in
|
||||
(`docs/Website/niveus`): a window with three lights, keys with a lit upper edge
|
||||
and a shadow beneath, panels cut into the surface as wells, a graphite ground
|
||||
that darkens towards the bottom, and the mark above the list.
|
||||
|
||||
What makes that possible in a loader is that nothing in it is large:
|
||||
|
||||
- **Shapes** are rounded rectangles with a vertical gradient. Corners are
|
||||
anti-aliased by supersampling only the corner pixels, against a colour the
|
||||
caller names. Nothing reads the framebuffer back, because that memory is
|
||||
uncached on real hardware and a read per pixel is the difference between a
|
||||
menu that appears and one that crawls. (The one exception is the pointer,
|
||||
which saves sixteen by nineteen pixels.)
|
||||
- **Type** is Instrument Sans, the face Niveus ships. A TrueType rasteriser
|
||||
does not belong in a loader, so `tools/python/mknvfont.py` renders it once
|
||||
into `include/gfx/nvfont.h` as 8-bit coverage bitmaps with fractional
|
||||
advances, at the size for ordinary screens and at twice that for screens 1400
|
||||
pixels tall or more. Rasterising at the real size is what keeps it crisp;
|
||||
doubling pixels does not.
|
||||
- **Layout** is computed from the item list, not drawn from fixed positions: a
|
||||
list taller than the screen drops the mark, then scrolls with a bar.
|
||||
|
||||
An entry is a label, a newline and a line of detail. The entries are:
|
||||
|
||||
| Entry | Does |
|
||||
|---|---|
|
||||
| Boot SchOS / (verbose) | the two ways of starting this system |
|
||||
| other systems | one entry per loader found at a well-known path on any volume: Windows Boot Manager, Ubuntu, Debian, Fedora, Arch, openSUSE, systemd-boot, rEFInd, and any other `\EFI\BOOT\BOOTX64.EFI` that is not this one. Chosen, they are started with the firmware's own `LoadImage`/`StartImage`, and if the other loader returns, the menu is still here |
|
||||
| Resolution | the list of modes the firmware offers, or a typed size, with the "keep this?" confirmation |
|
||||
| System Information | firmware, UEFI revision, processor, memory, display, ACPI |
|
||||
| Firmware Setup | sets `OsIndications` and restarts into the firmware's own settings; on firmware that does not support it, says so instead of restarting for nothing |
|
||||
| Restart, Shut Down | `ResetSystem` |
|
||||
| Remote debugging | `make DEBUG=1` builds only |
|
||||
|
||||
Keyboard, mouse, touchpad and touchscreen all drive it: both UEFI pointer
|
||||
protocols are polled (a mouse reports movement, a tablet reports position),
|
||||
hover moves the highlight, a click activates, and the wheel scrolls. All input
|
||||
goes through one function, `wait_event()`, so every screen behaves the same way.
|
||||
The cursor stays hidden until the pointer has actually done something, because
|
||||
the firmware's console layer installs a pointer protocol whether or not a mouse
|
||||
is attached. USB devices appear some time after their controller is connected
|
||||
and the menu is already up by then, so the loader connects every controller when
|
||||
the menu opens and looks for pointers again twice a second for ten seconds.
|
||||
|
||||
The "keep this resolution?" confirmation counts ten seconds down once, however
|
||||
many times the pointer moves over the button: a person with a garbled screen
|
||||
waving a mouse around must not be able to keep postponing the revert.
|
||||
|
||||
**Not verified:** hover and click were exercised with synthetic events. Under
|
||||
QEMU and OVMF a USB mouse or tablet never delivered an event to the loader, so
|
||||
the pointer path has not been seen working on a device -- real hardware first.
|
||||
|
||||
### The menu is on the serial line too
|
||||
|
||||
The three F8 screens paint into the framebuffer, which is the right place for
|
||||
The screens paint into the framebuffer, which is the right place for
|
||||
them on a machine with a screen and nowhere at all on one driven over a serial
|
||||
cable — which is how this loader is usually driven. `f8_window()` already went
|
||||
out of its way to support that, accepting `o` as well as F8 because *"a serial
|
||||
|
|
@ -351,7 +406,7 @@ its text across the menu that was just painted there.
|
|||
Two details that are not cosmetic:
|
||||
|
||||
- **The item number is shown only where it is a key you can press.** The boot
|
||||
menu takes 1–5. The resolution list takes only W/S and Enter and is longer
|
||||
menu takes 1–9. The resolution list takes only W/S and Enter and is longer
|
||||
than nine entries, so numbering it printed `:`, `;`, `<` past the ninth — a
|
||||
character nobody can type, offering to select something.
|
||||
- **Q goes back as well as Esc.** On a serial line a lone `ESC` byte is
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@ asked for one. That is the right default and it has one bad consequence —
|
|||
to enable it.** A laptop with no serial port that stops before it can draw has
|
||||
nowhere to say why, and `schinitd enable` needs a prompt that does not exist.
|
||||
|
||||
So a build made with `make DEBUG=1` has a sixth entry in the F8 menu:
|
||||
So a build made with `make DEBUG=1` has one more entry at the end of the F8 menu:
|
||||
|
||||
```
|
||||
> 6. Remote debugging: off
|
||||
> Remote debugging: off
|
||||
```
|
||||
|
||||
Picking it toggles the entry, booting carries the choice through
|
||||
|
|
|
|||
|
|
@ -82,15 +82,21 @@ Nothing here starts before [1.0.md](1.0.md)'s register is empty, except where th
|
|||
|
||||
## 5b. The boot manager
|
||||
|
||||
The loader ([boot.md](../architecture/boot.md)) is a text menu: a splash, "Press F8", a list of options. UEFI gives much more:
|
||||
a graphics-output framebuffer at the panel's native mode, a pointer protocol, file systems to read themes from. The plan is a
|
||||
boot manager that is worth looking at — in the **Niveus / skeuomorphic** style the site and the wallpapers already use (the
|
||||
Shift mark, real materials, depth) — and usable: a mouse and keyboard, a list of what can be booted (this ISO, a SchFS
|
||||
partition, other operating systems' loaders), the options that live in the F8 menu now (resolution with its "keep this?"
|
||||
confirmation, verbose boot, drivers off), and an honest failure screen. It is its own piece of work in `boot/uefi/`
|
||||
(a small text and image renderer in the loader — the kernel's TrueType rasteriser is not available yet at that point) and it
|
||||
replaces the boot-time display handling that grew by accretion (GOP mode choice, the "keep this resolution" probe, the font scaling
|
||||
in the kernel) with one design.
|
||||
The loader ([boot.md](../architecture/boot.md#the-boot-manager)) is now a Niveus-styled window: bevelled keys, wells, Instrument Sans,
|
||||
keyboard and pointer, a list of other systems it finds on the disks and can chain-load, the F8 options (resolution with its
|
||||
"keep this?" confirmation, verbose boot), and real Firmware Setup, Restart and Shut Down.
|
||||
|
||||
Still to do:
|
||||
|
||||
- [ ] boot entries from a file on the ESP (several kernels, command lines, "drivers off", a default and a timeout), instead of
|
||||
the two fixed ways of starting this system
|
||||
- [ ] a SchFS partition as a boot source, not just the volume the loader came from
|
||||
- [ ] an honest failure screen for the loader's own errors (today `die()` prints to the console)
|
||||
- [ ] the pointer path verified on a real machine; QEMU with OVMF never delivered a USB mouse event to the loader
|
||||
- [ ] light appearance, and a theme read from the ESP
|
||||
|
||||
It is its own piece of work in `boot/uefi/`; the kernel's TrueType rasteriser is not available at that point, which is why the
|
||||
loader carries a pre-rasterised face (`include/gfx/nvfont.h`).
|
||||
|
||||
### The installer and `.sip`
|
||||
|
||||
|
|
|
|||
7867
include/gfx/nvfont.h
Normal file
7867
include/gfx/nvfont.h
Normal file
File diff suppressed because it is too large
Load diff
80
tools/python/mknvfont.py
Normal file
80
tools/python/mknvfont.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Rasterise Niveus's interface face into a header the boot manager can draw.
|
||||
|
||||
The loader cannot carry a TrueType rasteriser, and the answer to that has been
|
||||
the same since include/gfx/font.h: do the work at build time and commit the
|
||||
result. This takes Instrument Sans (SIL OFL, see docs/Website/niveus/fonts/)
|
||||
at three sizes and writes 8-bit coverage bitmaps, one per printable ASCII
|
||||
character, with the metrics needed to lay them out proportionally.
|
||||
|
||||
python3 tools/python/mknvfont.py docs/Website/niveus/fonts/static include/gfx/nvfont.h
|
||||
|
||||
Needs Pillow; nothing else, and nothing at all at build time of the OS.
|
||||
"""
|
||||
import sys, os
|
||||
from PIL import ImageFont
|
||||
|
||||
# Each size twice: x1 for ordinary screens and x2 for the ones where a 15 px
|
||||
# face would be a speck. Rendering at the real size beats doubling pixels.
|
||||
SIZES = []
|
||||
for _scale in (1, 2):
|
||||
SIZES += [
|
||||
("body%d" % _scale, "InstrumentSans-Medium.ttf", 15 * _scale),
|
||||
("small%d" % _scale, "InstrumentSans-Regular.ttf", 12 * _scale),
|
||||
("title%d" % _scale, "InstrumentSans-SemiBold.ttf", 24 * _scale),
|
||||
]
|
||||
FIRST, LAST = 32, 126
|
||||
|
||||
|
||||
def render(path, px):
|
||||
f = ImageFont.truetype(path, px)
|
||||
asc, desc = f.getmetrics()
|
||||
glyphs, blob = [], bytearray()
|
||||
for code in range(FIRST, LAST + 1):
|
||||
ch = chr(code)
|
||||
adv = int(round(f.getlength(ch) * 64)) # 26.6 fixed point: whole-pixel advances make text look cramped
|
||||
mask, (ox, oy) = f.getmask2(ch, mode="L", anchor="ls")
|
||||
w, h = mask.size
|
||||
data = bytes(mask) if (w and h) else b""
|
||||
glyphs.append((len(blob), w, h, ox, oy, adv))
|
||||
blob += data
|
||||
return asc, desc, glyphs, blob
|
||||
|
||||
|
||||
def main():
|
||||
src, out = sys.argv[1], sys.argv[2]
|
||||
lines = [
|
||||
"#pragma once",
|
||||
"",
|
||||
"// GENERATED by tools/python/mknvfont.py -- do not edit. Run `make nvfont`.",
|
||||
"// source: docs/Website/niveus/fonts/static (Instrument Sans, SIL OFL 1.1)",
|
||||
"//",
|
||||
"// 8-bit coverage bitmaps for printable ASCII, proportionally spaced, used by",
|
||||
"// the boot manager (boot/common/ui.c). Not included by the kernel.",
|
||||
"",
|
||||
"struct nvglyph { unsigned short off; unsigned char w, h; signed char ox, oy; unsigned short adv; /* 1/64 px */ };",
|
||||
"struct nvfont { unsigned char ascent, descent; const struct nvglyph *g; const unsigned char *bits; };",
|
||||
"#define NVFONT_FIRST %d" % FIRST,
|
||||
"#define NVFONT_LAST %d" % LAST,
|
||||
"",
|
||||
]
|
||||
for name, fn, px in SIZES:
|
||||
asc, desc, glyphs, blob = render(os.path.join(src, fn), px)
|
||||
lines.append("static const struct nvglyph nvg_%s[] = {" % name)
|
||||
for (o, w, h, ox, oy, adv) in glyphs:
|
||||
lines.append(" {%d,%d,%d,%d,%d,%d}," % (o, w, h, ox, oy, adv))
|
||||
lines.append("};")
|
||||
lines.append("static const unsigned char nvb_%s[] = {" % name)
|
||||
for i in range(0, len(blob), 24):
|
||||
lines.append(" " + ",".join(str(b) for b in blob[i:i + 24]) + ",")
|
||||
if not blob:
|
||||
lines.append(" 0")
|
||||
lines.append("};")
|
||||
lines.append("static const struct nvfont nvfont_%s = { %d, %d, nvg_%s, nvb_%s };" %
|
||||
(name, asc, desc, name, name))
|
||||
lines.append("")
|
||||
open(out, "w").write("\n".join(lines) + "\n")
|
||||
print("wrote", out)
|
||||
|
||||
|
||||
main()
|
||||
Loading…
Reference in a new issue