feat(clients): the OS marks tell gaming distros apart, and Windows looks current

Three things were wrong with the host-card OS icons.

The Windows mark was Font Awesome 5's, which is still the Windows 8/10 flag
with the perspective skew — dated next to the flat four-pane mark Microsoft
has shipped since Windows 11. No icon set has the current one (Simple Icons
carries no windows/microsoft slug at all), so it is drawn here: four equal
squares at the authentic 11.377 + 1.246 proportion. The Decky plugin was
pulling FaWindows straight from react-icons, so it now inlines the masters
like the web console does, or it would have kept the old flag regardless.

Bazzite, CachyOS and Nobara collapsed onto their family's mark. The host
already advertises the full chain, so this is purely missing art: all three
now ship a leaf mark, because "a Bazzite box" and "a Fedora box" are
different machines to the person reading the card. CachyOS and Nobara come
from Simple Icons; Bazzite has no icon anywhere, so its "b" is lifted out of
the project's own badge (Apache-2.0, attributed).

On Android every non-square mark was stretched. A VectorPainter maps the
viewport onto the ImageVector's default size with independent x and y
scales, so declaring a 448x512 Tux as 24x24 dp squashed it — silently, no
crash, no warning. The longest viewport edge now sets the 24 dp box and the
other follows the ratio, which is what Icon()'s ContentScale.Fit expects.
A unit test pins the invariant; every other client was already correct.

Also: scripts/gen-os-icons.sh replaces the undocumented hand-run pipeline
that turns a master into the GTK symbolic SVG, the Windows PNG and the Apple
template PDF. It reproduces the committed artifacts byte-identically.

Verified: web and Decky typecheck, Decky bundles, Android compiles and its
tests pass, PunktfunkKit builds, osinfo's tests pass. Not verified on glass,
and the GTK/Windows client crates do not build on macOS.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 14:17:02 +02:00
co-authored by Claude Opus 5
parent f4f83202cb
commit 5520167958
37 changed files with 500 additions and 87 deletions
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
# Derive the per-client host-OS-icon assets from the assets/os-icons masters.
#
# The masters are monochrome `fill="currentColor"` SVGs, one per icon token of the host's
# OS-identity chain (see assets/os-icons/README.md). Three clients need a baked derivative
# because they cannot consume the master directly:
#
# GTK shell symbolic SVG, black fill -> clients/linux/data/icons/scalable/actions/
# Windows shell PNG, h=32, mid-grey -> clients/windows/assets/os/
# Apple clients vector PDF, black fill -> clients/apple/.../OsIcons.xcassets/
#
# The web console, the Decky plugin and the Android client transcribe the master's path
# data inline instead — those are hand-kept, and this script prints them at the end so a
# new token can be pasted straight in.
#
# Idempotent. Usage: bash scripts/gen-os-icons.sh [token ...] (default: every master)
set -euo pipefail
cd "$(dirname "$0")/.."
MASTERS=assets/os-icons
GTK=clients/linux/data/icons/scalable/actions
WIN=clients/windows/assets/os
APPLE=clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets
# The Windows shell has no vector element and no theme-aware tint, so its PNGs are baked in
# one mid-grey that stays legible on both the light and the dark WinUI theme.
WIN_GREY='#8A8F98'
WIN_HEIGHT=32
log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; }
command -v rsvg-convert >/dev/null 2>&1 || {
echo "rsvg-convert not found (brew install librsvg / apt install librsvg2-bin)" >&2
exit 1
}
tokens=("$@")
if [ ${#tokens[@]} -eq 0 ]; then
for f in "$MASTERS"/*.svg; do tokens+=("$(basename "$f" .svg)"); done
fi
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
for t in "${tokens[@]}"; do
src="$MASTERS/$t.svg"
[ -f "$src" ] || { echo "no master for token '$t' ($src)" >&2; exit 1; }
log "$t"
# GTK: the master with the fill resolved to black — Adwaita recolors a `-symbolic` icon
# from the fill it finds, so the value only has to be a real colour, not the final one.
sed 's/currentColor/#000000/' "$src" > "$GTK/pf-os-$t-symbolic.svg"
# Windows: same black-to-grey substitution, rasterized at a fixed height so every mark
# shares an optical size and keeps its own aspect ratio.
sed "s/currentColor/$WIN_GREY/" "$src" > "$tmp/$t.grey.svg"
rsvg-convert -h "$WIN_HEIGHT" -f png -o "$WIN/$t.png" "$tmp/$t.grey.svg"
# Apple: a vector PDF at the master's natural size, in a template imageset — SwiftUI
# tints it from foregroundStyle, so the baked colour is irrelevant.
sed 's/currentColor/#000000/' "$src" > "$tmp/$t.black.svg"
mkdir -p "$APPLE/os-$t.imageset"
rsvg-convert -f pdf -o "$APPLE/os-$t.imageset/$t.pdf" "$tmp/$t.black.svg"
cat > "$APPLE/os-$t.imageset/Contents.json" <<JSON
{
"images" : [
{ "filename" : "$t.pdf", "idiom" : "universal" }
],
"info" : { "author" : "xcode", "version" : 1 },
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
JSON
done
echo
log "Inline path data (web/src/components/os-icon.tsx, clients/decky/src/os-icon.tsx,"
log " clients/android/.../components/OsIcons.kt — hand-kept, paste from here)"
for t in "${tokens[@]}"; do
python3 - "$MASTERS/$t.svg" "$t" <<'PY'
import re, sys
svg = open(sys.argv[1]).read()
box = re.search(r'viewBox="([^"]+)"', svg).group(1)
d = re.search(r'<path[^>]* d="([^"]+)"', svg).group(1)
w, h = box.split()[2:]
print(f'\n {sys.argv[2]}: viewBox "{box}" (viewport {w} x {h})\n {d}')
PY
done
echo
log "Remember: a NEW token also has to be added to each client's shipped-token list —"
log " clients/linux/src/ui_hosts.rs, clients/linux/data/resources.gresource.xml,"
log " clients/windows/src/app/os_icons.rs, clients/apple/.../PunktfunkKit/OsIcon.swift,"
log " plus the three inline registries above."
+5 -2
View File
@@ -68,14 +68,17 @@ VENDORED_TREES = [
("Vulkan-Headers (vendored, crates/pyrowave-sys)",
"crates/pyrowave-sys/vendor/pyrowave/Granite/third_party/khronos/vulkan-headers/LICENSE.md",
"https://github.com/KhronosGroup/Vulkan-Headers"),
# OS brand marks for the host-card OS icon (assets/os-icons/, CC BY 4.0 / CC0
# see assets/os-icons/README.md; clients embed per-platform derivatives).
# OS brand marks for the host-card OS icon (assets/os-icons/, CC BY 4.0 / CC0 /
# Apache-2.0 — see assets/os-icons/README.md; clients embed per-platform derivatives).
("Font Awesome Free brand icons (vendored, assets/os-icons)",
"assets/os-icons/LICENSES/font-awesome-brands.txt",
"https://fontawesome.com"),
("Simple Icons (vendored, assets/os-icons)",
"assets/os-icons/LICENSES/simple-icons.txt",
"https://simpleicons.org"),
("Bazzite logo (vendored, assets/os-icons)",
"assets/os-icons/LICENSES/bazzite.txt",
"https://github.com/ublue-os/bazzite"),
]