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
129
130
131
132
|
# 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.
## Notes for the user
`/etc/default/grub` has `GRUB_GFXMODE=2560x1080×32` with a Unicode `×`, so
GRUB likely rejects that mode and falls back to 1920x1080. Replace with `x`.
|