aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers/specs/2026-09-29-grub-theme-udt-design.md
blob: f8bc517c8d5e043934b54f3b7ba412d858e0bf12 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# grub-theme-udt design

A GRUB 2 theme for the unified-desktop-theme (udt) family. Colours come from
the active udt scheme; the background is a snapshot of the current wallpaper;
the layout echoes sddm-theme-udt's left panel.

## Constraints

- GRUB reads `/boot` before anything else is mounted, so the installed theme is
  a **copy**, never a symlink into `/home`.
- Installing is root's job. Nothing in this repo or in udt writes under `/boot`
  or `/etc`; udt's `install.sh` only prints the root commands.
- Nothing at boot can read the wallpaper-tracking accent, so the theme uses the
  scheme's fixed `accent` role.
- `theme.txt` has no include mechanism, so it is a template rendered whole.
- Target: GRUB 2.14, x86_64-efi. 2.14 supports `desktop-image-scale-method`.
- GRUB's image readers are limited: no progressive JPEG, no interlaced PNG, no
  webp. Anything GRUB loads is written by `build` in a format it can read.
- Minimal dependencies: Python stdlib, `grub-mkfont`, `fc-match`, `magick`,
  all already installed.

## Layout

- **Background:** `desktop-image: "background.jpg"` with
  `desktop-image-scale-method: "crop"`, so any mode (including 2560x1080) is
  filled without distortion. `desktop-color` is `@BG@`, shown if the image fails
  to load.
- **Panel:** a `+ image` component at `left = 0`, `top = 0`, `width = 25%`,
  `height = 100%`, file `panel.png`: a small RGBA PNG of `bg_alt` at about 85%
  alpha. GRUB scales the image to the component and alpha-blends it over the
  wallpaper. Square corners, as in SDDM.
- **Inside the panel,** top to bottom:
  - title `+ label`: the `NAME` from `/etc/os-release`, colour `@FG@`,
    Noto Sans Bold;
  - `+ boot_menu`: `item_color = @FG_DIM@`, `selected_item_color = @FG@`,
    Noto Sans Regular, no icons (`icon_width = 0`);
  - `+ label` with `id = "__timeout__"`, colour `@FG_FAINT@`;
  - `+ progress_bar` with `id = "__timeout__"`, `fg_color = @ACCENT@`,
    `bg_color = @SURFACE@`, a few pixels high.
- **Selected item:** `selected_item_pixmap_style = "select_*.png"`. The `w`
  slice is a 4px `accent` bar, the `c` and `e` slices are solid `surface`.
- **Console:** `terminal-box: "terminal_box_*.png"`, all nine slices solid
  `bg_deep`; `terminal-font` is Inconsolata Nerd Font Mono.

Menu entry icons are out of scope. GRUB would look for `icons/<class>.png` per
entry class; adding them later is additive.

## Files

| Path | Tracked | Role |
|---|---|---|
| `theme.txt.in` | yes | the template, `@ROLE@` placeholders |
| `theme.txt` | no | rendered by udt-palette |
| `build` | yes | stdlib Python script, assembles `build/udt/` |
| `build/udt/` | no | the complete theme, what root copies |
| `README.md`, `LICENSE` | yes | GPLv2 only |
| `.gitignore` | yes | `theme.txt`, `build/` |

## Placeholders

udt-palette substitutes these in `theme.txt.in`, each as `#rrggbb`:

`@BG@ @BG_ALT@ @BG_DEEP@ @SURFACE@ @FG@ @FG_DIM@ @FG_FAINT@ @ACCENT@`

plus `@SCHEME@`, the scheme name, used in a header comment.

The template carries one machine-readable comment line that `build` parses to
get the colours it needs for the PNGs, so there is no second template:

    # udt: bg_alt=@BG_ALT@ bg_deep=@BG_DEEP@ surface=@SURFACE@ accent=@ACCENT@

GRUB ignores comment lines.

## Data flow

1. udt's `install.sh` runs udt-palette, which renders `theme.txt.in` to
   `theme.txt` in this repo (the gen function and TARGETS line are owned by
   the udt session).
2. udt's `install.sh` runs `./build` here (skipped if the repo is not cloned).
3. `build`:
   - reads `theme.txt`, fails if any `@...@` remains or the `# udt:` line is
     missing;
   - substitutes the title from `/etc/os-release` into `build/udt/theme.txt`;
   - writes `panel.png`, `select_{w,c,e}.png`, `terminal_box_{nw,n,ne,w,c,e,sw,s,se}.png`
     with an in-script PNG encoder (`zlib` + `struct`, 8-bit RGBA, not
     interlaced);
   - builds the `.pf2` fonts with `grub-mkfont`, resolving the TTF paths with
     `fc-match -f '%{file}'`; rebuilt only when missing, since fonts do not
     change with the scheme;
   - converts `~/.cache/udt/wpaper` (following the symlink) with `magick` to a
     baseline, non-interlaced `background.jpg`, capped at 2560px on the long
     side, metadata stripped.
4. udt's `install.sh` prints the root commands:

       rm -rf /boot/grub/themes/udt && cp -r <repo>/build/udt /boot/grub/themes/
       # once: set GRUB_THEME="/boot/grub/themes/udt/theme.txt" in /etc/default/grub
       grub-mkconfig -o /boot/grub/grub.cfg

   The copy must be rerun after a scheme or wallpaper change. `grub-mkconfig`
   is needed only when `GRUB_THEME` changes.

## Fonts

| Use | Source | Size |
|---|---|---|
| title | Noto Sans Bold | 32 |
| menu items, countdown | Noto Sans Regular | 24 |
| console | Inconsolata Nerd Font Mono | 18 |

Font names in `theme.txt.in` must match what `grub-mkfont` records (family,
style, size), e.g. `"Noto Sans Regular 24"`; `build` checks the generated
names against the template.

## Errors

`build` exits non-zero with a one-line message when: `theme.txt` is missing
or unrendered, the wallpaper link is missing, `magick`/`grub-mkfont`/`fc-match`
is absent, or `fc-match` resolves to a fallback font rather than the requested
family. Nothing is written to `build/udt/` until all inputs are valid, so a
failed build never leaves a half theme for root to copy.

## Testing

- `./build --selftest`: encodes PNGs, decodes them back with `zlib`, checks
  pixel values against the input colours; checks placeholder detection.
- Visual check before install: `grub-emu` is not packaged here, so the check
  is a real boot. The previous `GRUB_THEME` line is the rollback.