Files
Panama/tests/quickshell/gtk-theme-contract
T
Gabriel Brown cb7c09d208 Give the desktop real themes, video wallpapers, and honest titlebars
Appearance now opens on Themes: light and dark side by side, each
remembering its own choice, over galleries of ten shipped themes —
Tokyo Moon and Day joined by Moon Rose, Catppuccin, Nord, Gruvbox and
Everforest in both modes. A theme is a complete palette: the catalog
lives in themes.json, Theme.qml reads every color token from the
active record, and one render pipeline carries it to kitty, tmux,
btop, GTK, Vicinae, Firefox's chrome, and the lock screen. The Theme
editor builds new ones from four wells — wheel, hex, or eyedropper —
with derived surfaces, a saturation slider, debounced fine-tune, and
effects that save with the theme. Custom edits finally keep GNOME's
accent, kitty's border, and hyprlock in sync.

Wallpapers can be video: mpvpaper per output, hardware-decoded, muted
and looped, supervised and respawned. Panama owns the pausing — games,
battery, and a bar pill for right now — because the compositor
rebuilds full-screen blur for every frame a video wallpaper draws.
The lock screen gets a still frame.

Titlebars stop lying. GNOME apps get close-only on your chosen side,
the maximize and double-click settings are gone, the Settings window
obeys the same rules, and its titlebar can be turned off entirely.
Typography becomes five labeled dropdowns instead of a wall of
samples.

Contracts updated and written throughout (165 now); per the redesign
workflow none were executed — the full sweep runs once at the end.

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
2026-08-23 23:39:04 -04:00

158 lines
7.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# Every GTK theme name Panama sets must be a theme that is actually installed.
#
# This exists because of a bug that was invisible for weeks. ColorScheme set
# gtk-theme to "Adwaita-dark" for dark and "Adwaita" for light. Neither is
# installed on Fedora 44 -- only adw-gtk3 and adw-gtk3-dark are -- and GTK
# responds to an unknown theme name by silently falling back to its built-in
# default, which is LIGHT.
#
# So light mode appeared to work, dark mode produced light windows, and nothing
# anywhere reported an error. Applications that take their cue from the GTK
# theme rather than the portal -- Chromium and Electron among them -- were stuck
# light with no way to diagnose it from inside the application.
#
# The failure is silent by construction, so it needs a test rather than a
# comment. Checks the compositor-facing setting and the generated GTK config
# agree, and that both name something real.
#
# It also covers the second half of GTK theming: settings.ini names a theme,
# gtk.css overrides that theme's colours. Those colours used to be baked in --
# #82aaff written out by hand, and the GTK4 "light" gtk.css a byte-for-byte
# copy of the dark one, so a light desktop drew dark GTK4 windows. They are now
# a marked block that panama-theme-apps rewrites from the active theme, with a
# thousand lines of vendored structural CSS around it that must survive
# untouched.
set -uo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
color_scheme="$repo_dir/config/dot/quickshell/services/ColorScheme.qml"
theme_apps="$repo_dir/config/dot/quickshell/scripts/panama-theme-apps"
fail() {
printf 'gtk theme contract: %s\n' "$1" >&2
exit 1
}
theme_installed() {
local name="$1" dir
for dir in /usr/share/themes "$HOME/.themes" "$HOME/.local/share/themes"; do
[[ -d "$dir/$name/gtk-3.0" ]] && return 0
done
return 1
}
# ── The names ColorScheme sets must exist ────────────────────────────────────
names="$(grep -oE 'root\.dark \? "[a-zA-Z0-9-]+" : "[a-zA-Z0-9-]+"' "$color_scheme" \
| grep -oE '"[a-zA-Z0-9-]+"' | tr -d '"' | grep -E '^adw-|^Adwaita' | sort -u)"
[[ -n "$names" ]] || fail 'could not find the GTK theme names in ColorScheme.qml -- this contract is not reading it correctly'
while read -r name; do
[[ -n "$name" ]] || continue
theme_installed "$name" \
|| fail "ColorScheme sets gtk-theme to \"$name\", which is not installed. GTK falls back to its light default when a theme is missing, so this produces light windows in dark mode with no error anywhere."
done <<<"$names"
# ── The generated GTK config must agree, in both directions ──────────────────
# Generated into a fixture rather than the live config, so running this cannot
# retheme the desktop it is running on.
fixture="$(mktemp -d /tmp/panama-gtk-theme.XXXXXX)"
trap 'rm -rf "$fixture"' EXIT
for version in 3.0 4.0; do
mkdir -p "$fixture/gtk-$version"
cp "$repo_dir/config/dot/gtk-$version/settings.ini.template" "$fixture/gtk-$version/" \
|| fail "gtk-$version has no settings.ini.template -- the generated file would never be produced"
done
for scheme in dark light; do
XDG_CONFIG_HOME="$fixture" "$theme_apps" "$scheme" >/dev/null 2>&1
for version in 3.0 4.0; do
generated="$fixture/gtk-$version/settings.ini"
[[ -r "$generated" ]] || fail "gtk-$version settings.ini was not generated for $scheme"
grep -q '@GTK_THEME@\|@PREFER_DARK@' "$generated" \
&& fail "gtk-$version settings.ini still contains an unsubstituted placeholder for $scheme"
theme="$(sed -n 's/^gtk-theme-name=//p' "$generated")"
prefer="$(sed -n 's/^gtk-application-prefer-dark-theme=//p' "$generated")"
theme_installed "$theme" \
|| fail "gtk-$version settings.ini names \"$theme\" for $scheme, which is not installed"
if [[ "$scheme" == "dark" ]]; then
[[ "$prefer" == "1" ]] || fail "gtk-$version asks for prefer-dark=$prefer in dark mode"
[[ "$theme" == *dark* ]] || fail "gtk-$version uses \"$theme\" in dark mode, which is not a dark theme"
else
[[ "$prefer" == "0" ]] || fail "gtk-$version asks for prefer-dark=$prefer in light mode"
[[ "$theme" != *dark* ]] || fail "gtk-$version uses \"$theme\" in light mode, which is a dark theme"
fi
done
done
# ── The generated colour block ───────────────────────────────────────────────
catalog="$repo_dir/config/dot/quickshell/config/themes.json"
gtk_css_files=(gtk-3.0/gtk.css gtk-4.0/gtk.css gtk-4.0/gtk-dark.css)
for relative in "${gtk_css_files[@]}"; do
tracked="$repo_dir/config/dot/$relative"
[[ -r "$tracked" ]] || fail "$relative is missing"
grep -q 'PANAMA THEME BEGIN' "$tracked" \
|| fail "$relative has no PANAMA THEME BEGIN marker, so the palette is never written into it and GTK keeps whatever colours the file shipped with"
grep -q 'PANAMA THEME END' "$tracked" \
|| fail "$relative has no PANAMA THEME END marker; the generator would rewrite the rest of the file"
# The shipped file has to be valid before it has ever been regenerated: a
# fresh checkout renders GTK windows before the first theme change.
grep -q '@define-color window_bg_color' "$tracked" \
|| fail "$relative ships without a window_bg_color, so a fresh checkout draws GTK windows in adw-gtk3's stock greys"
cp "$tracked" "$fixture/$relative"
done
mkdir -p "$fixture/panama"
# One dark theme and one light one, named through settings.json the way the
# shell names them.
for pair in "dark:$(jq -r .defaultDark "$catalog")" "light:$(jq -r .defaultLight "$catalog")"; do
scheme="${pair%%:*}"
theme_id="${pair#*:}"
want_bg="$(jq -r --arg id "$theme_id" '.themes[] | select(.id == $id) | .palette.bg' "$catalog")"
want_accent="$(jq -r --arg id "$theme_id" '.themes[] | select(.id == $id) | .accent' "$catalog")"
jq -n --arg id "$theme_id" --arg scheme "$scheme" \
'{colorScheme: $scheme, themeProfileId: $id}' >"$fixture/panama/settings.json"
XDG_CONFIG_HOME="$fixture" "$theme_apps" "$scheme" >/dev/null 2>&1
for relative in "${gtk_css_files[@]}"; do
generated="$fixture/$relative"
block="$(awk '/PANAMA THEME BEGIN/,/PANAMA THEME END/' "$generated")"
grep -qF "@define-color view_bg_color $want_bg;" <<<"$block" \
|| fail "$relative did not take \"$theme_id\"'s background ($want_bg) for $scheme -- the block is not being regenerated from the theme"
grep -qF "@define-color accent_bg_color $want_accent;" <<<"$block" \
|| fail "$relative did not take \"$theme_id\"'s accent ($want_accent) for $scheme"
# Nothing outside the markers may move. That is the entire reason for
# a marked block rather than a generated file.
diff <(sed '/PANAMA THEME BEGIN/,/PANAMA THEME END/d' "$repo_dir/config/dot/$relative") \
<(sed '/PANAMA THEME BEGIN/,/PANAMA THEME END/d' "$generated") >/dev/null \
|| fail "$relative changed OUTSIDE the markers -- the vendored theme's structural CSS is being rewritten"
done
done
# The last render above was light, so this reads the light result: gtk-4.0's
# gtk.css shipped as a byte-for-byte copy of the dark one, which is how a light
# desktop kept drawing dark GTK4 windows.
light_bg="$(sed -n 's/^@define-color view_bg_color #\([0-9a-fA-F]\{6\}\);.*/\1/p' \
"$fixture/gtk-4.0/gtk.css" | head -1)"
[[ -n "$light_bg" ]] || fail 'gtk-4.0/gtk.css has no view_bg_color after a light render'
(( 16#${light_bg:0:2} > 128 )) \
|| fail "gtk-4.0/gtk.css renders a DARK background (#$light_bg) in light mode -- the original bug"
printf 'gtk theme contract: PASS\n'