#!/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'