# 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/.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 /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.