aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-29 10:14:05 +0200
committerDanilo M. <danix@danix.xyz>2026-09-29 10:14:05 +0200
commitfe118f0dffcd6f80c20a6c69b33232f2207ebf1f (patch)
tree82b977e068edad6b4669ebf4e05fbf5735fcc219
parent7a243c8fca38b14d20124da064891b4eea5a5636 (diff)
downloadgrub-theme-udt-fe118f0dffcd6f80c20a6c69b33232f2207ebf1f.tar.gz
grub-theme-udt-fe118f0dffcd6f80c20a6c69b33232f2207ebf1f.zip
Add build script with PNG encoder
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rwxr-xr-xbuild70
1 files changed, 70 insertions, 0 deletions
diff --git a/build b/build
new file mode 100755
index 0000000..73b5823
--- /dev/null
+++ b/build
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+# build: assemble the installable GRUB theme into dist/udt/.
+# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+# Licensed under the GNU General Public License v2 only.
+"""Assemble dist/udt/ from the theme.txt udt-palette rendered.
+
+Usage: build [--selftest]
+
+Writes the PNG slices, the .pf2 fonts and a GRUB-readable copy of the
+current wallpaper next to theme.txt. Everything is staged in a temporary
+directory and swapped in at the end, so a failed run leaves the previous
+dist/udt/ untouched. Never touches /boot: root copies dist/udt/ there.
+"""
+import struct
+import sys
+import zlib
+
+PNG_SIG = b"\x89PNG\r\n\x1a\n"
+
+
+def chunk(tag, data):
+ return (struct.pack(">I", len(data)) + tag + data
+ + struct.pack(">I", zlib.crc32(tag + data)))
+
+
+def png(w, h, rgba):
+ """A w x h PNG of one colour: 8-bit RGBA, not interlaced, which GRUB reads."""
+ row = b"\0" + bytes(rgba) * w # filter byte 0, then the pixels
+ return (PNG_SIG
+ + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 6, 0, 0, 0))
+ + chunk(b"IDAT", zlib.compress(row * h))
+ + chunk(b"IEND", b""))
+
+
+def unpng(data):
+ """Decode what png() writes: (width, height, set of RGBA pixels)."""
+ assert data[:8] == PNG_SIG
+ pos, idat = 8, b""
+ while pos < len(data):
+ n, tag = struct.unpack(">I4s", data[pos:pos + 8])
+ body = data[pos + 8:pos + 8 + n]
+ assert struct.unpack(">I", data[pos + 8 + n:pos + 12 + n])[0] == zlib.crc32(tag + body)
+ if tag == b"IHDR":
+ w, h = struct.unpack(">II", body[:8])
+ elif tag == b"IDAT":
+ idat += body
+ pos += 12 + n
+ raw = zlib.decompress(idat)
+ stride = 1 + 4 * w
+ px = set()
+ for y in range(h):
+ row = raw[y * stride:(y + 1) * stride]
+ assert row[0] == 0
+ px |= {tuple(row[1 + 4 * x:5 + 4 * x]) for x in range(w)}
+ return w, h, px
+
+
+def selftest():
+ w, h, px = unpng(png(3, 2, (0x12, 0x34, 0x56, 0xd9)))
+ assert (w, h, px) == (3, 2, {(0x12, 0x34, 0x56, 0xd9)}), (w, h, px)
+ print("selftest ok")
+
+
+def main():
+ if sys.argv[1:] == ["--selftest"]:
+ return selftest()
+
+
+if __name__ == "__main__":
+ main()