Files
Panama/config/dot/quickshell/scripts/panama-theme-apps
T
Gabriel Brown d96863b687 Convert British spellings to American across the repo
colour -> color, behaviour -> behavior, centre -> center, favourite ->
favorite, and about twenty other pairs, applied consistently across
comments, docs, error/UI copy, and a handful of QML identifiers that
used the British spelling as their actual name: SystemSettings'
serialiseValue/serialiseTable/normaliseGradient, Displays'
normaliseModes, Wallpaper's normalisePolicy, SettingsBackup's
serialiseHomeState, DateTime's ntpSynchronised property, Clipboard's
_normalise helper, and ShortcutCapture's cancelled signal (with its
onCancelled handler in ShortcutsPage.qml). Every call site and the two
tests that assert on the literal source text (settings-ownership and
settings-backup-live contracts) were updated in lockstep.

Left untouched: config/dot/espanso/match/packages/misspell-en/ is a
vendored third-party autocorrect dictionary -- its entries are typo
corrections, not our prose, and rewriting them would fight the
package's own purpose (and any future re-sync from upstream).

The already-American `favorites` property (Home page pinned
accessories) was never actually misspelled -- only nearby comments and
error strings said "favourites" -- so no data migration was needed
there.

Claude-Session: https://claude.ai/code/session_01E6TJUAh41HaP25MVHWkhRZ
2026-08-19 08:07:55 -04:00

254 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
# Propagates the color scheme to applications that do not read the desktop
# portal.
#
# Most modern applications DO follow org.freedesktop.portal.Settings and need
# nothing from us: GTK4/libadwaita, Qt6, Chromium and Electron all read
# org.freedesktop.appearance color-scheme, which xdg-desktop-portal-gtk serves
# from gsettings. Panama sets that, so those follow automatically.
#
# Terminals are the notable exception -- they predate the standard and carry
# their own palettes. kitty is handled here.
#
# GTK3 is the other one. Under GNOME, gnome-settings-daemon publishes the theme
# over XSETTINGS; under Hyprland nothing does, so ~/.config/gtk-3.0/settings.ini
# is authoritative for GTK3 applications. Pinned to dark, it contradicted the
# scheme in light mode, so it is generated from a template here instead.
#
# panama-theme-apps dark|light [accent]
#
# kitty gets it twice: the generated include file so terminals opened later
# start correct, and a live `set-colors` over its control socket so terminals
# already open change now. Without the second, a scheme change appears to do
# nothing until you open a new window.
#
# kitty's active_border_color and hyprlock's focus ring are also the accent
# role, not just the scheme -- see accent_hex() below, which mirrors the same
# lookup hypr/looks.lua and scripts/panama-lock carry for the same reason.
set -euo pipefail
scheme="${1:-dark}"
case "$scheme" in
dark|light) ;;
*) printf 'usage: panama-theme-apps [dark|light] [accent]\n' >&2; exit 2 ;;
esac
# Falls back to blue for an unknown or omitted name, matching Theme.qml's own
# accentPair fallback -- so a caller that only ever knew about scheme (a stale
# ColorScheme.qml, a manual invocation) still gets a real accent instead of an
# empty string reaching sed.
accent="${2:-blue}"
case "$accent" in
blue|orchid|teal|green|amber|orange|rose|slate) ;;
*) accent=blue ;;
esac
# Same 8 named accents as config/Theme.qml's `accents` map (kept in sync by
# hand -- there is no shared source between QML and a shell script), primary
# hue per scheme only: both consumers below draw a flat color, not the
# two-stop gradient the window border does.
accent_hex() {
local name="$1" scheme="$2"
case "$name" in
orchid) [[ "$scheme" == light ]] && printf '7847bd' || printf 'c099ff' ;;
teal) [[ "$scheme" == light ]] && printf '007197' || printf '86e1fc' ;;
green) [[ "$scheme" == light ]] && printf '587539' || printf 'c3e88d' ;;
amber) [[ "$scheme" == light ]] && printf '8c6c3e' || printf 'ffc777' ;;
orange) [[ "$scheme" == light ]] && printf 'b15c00' || printf 'ff966c' ;;
rose) [[ "$scheme" == light ]] && printf 'f52a65' || printf 'ff757f' ;;
slate) [[ "$scheme" == light ]] && printf '6172b0' || printf '828bb8' ;;
*) [[ "$scheme" == light ]] && printf '2e7de9' || printf '82aaff' ;;
esac
}
# hyprlock wants "R, G, B" decimal, not hex.
hex_to_rgb() {
local hex="$1"
printf '%d, %d, %d' "0x${hex:0:2}" "0x${hex:2:2}" "0x${hex:4:2}"
}
accent_border_hex="$(accent_hex "$accent" "$scheme")"
# ── hyprlock ─────────────────────────────────────────────────────────────────
# The lock screen. hyprlock is launched fresh on every lock (`pidof hyprlock ||
# hyprlock`), so it reads this file each time and needs no restart.
#
# It takes rgba(r, g, b, a) in DECIMAL, not hex, so the palette is expressed as
# "R, G, B" triples here rather than the hex used everywhere else. The two
# _HEX values are the exception: they sit inside Pango markup, where hyprlock
# wants ##rrggbb.
lock_dir="${XDG_CONFIG_HOME:-$HOME/.config}/hypr"
lock_template="$lock_dir/hyprlock.conf.template"
if [[ "$scheme" == "light" ]]; then
lock_fg="55, 96, 191" # #3760bf
lock_muted="97, 114, 176" # #6172b0
lock_error="245, 42, 101" # #f52a65
lock_bg="225, 226, 231" # #e1e2e7
lock_field="208, 213, 227" # #d0d5e3
lock_muted_hex="6172b0"
lock_error_hex="f52a65"
else
lock_fg="200, 211, 245" # #c8d3f5
lock_muted="130, 139, 184" # #828bb8
lock_error="255, 117, 127" # #ff757f
lock_bg="34, 36, 54" # #222436
lock_field="46, 47, 61" # #2e2f3d
lock_muted_hex="828bb8"
lock_error_hex="ff757f"
fi
# The focus ring is the accent role, not a scheme-relative one -- follows
# accentName the same way scripts/panama-lock's own generator does.
lock_accent="$(hex_to_rgb "$accent_border_hex")"
status_hyprlock="skipped"
if [[ -r "$lock_template" ]]; then
# Written atomically: a lock triggered mid-write would otherwise read a
# truncated config and fall back to hyprlock's own defaults, which is a
# bright gray screen with none of this desktop's identity.
if sed -e "s/@FG@/$lock_fg/g" \
-e "s/@MUTED@/$lock_muted/g" \
-e "s/@ACCENT@/$lock_accent/g" \
-e "s/@ERROR@/$lock_error/g" \
-e "s/@BG@/$lock_bg/g" \
-e "s/@FIELD@/$lock_field/g" \
-e "s/@MUTED_HEX@/$lock_muted_hex/g" \
-e "s/@ERROR_HEX@/$lock_error_hex/g" \
"$lock_template" >"$lock_dir/hyprlock.conf.tmp" 2>/dev/null \
&& mv "$lock_dir/hyprlock.conf.tmp" "$lock_dir/hyprlock.conf" 2>/dev/null; then
status_hyprlock="written"
else
rm -f "$lock_dir/hyprlock.conf.tmp"
status_hyprlock="failed"
fi
fi
# ── tmux ─────────────────────────────────────────────────────────────────────
# Generated like kitty's: tmux.conf sources current-theme.conf, and that file is
# machine state rather than configuration. Running servers are re-sourced so an
# open session changes now instead of at next launch -- tmux applies a
# source-file to every attached client immediately.
tmux_dir="${XDG_CONFIG_HOME:-$HOME/.config}/tmux"
tmux_theme="$tmux_dir/themes/tokyonight-moon.conf"
[[ "$scheme" == "light" ]] && tmux_theme="$tmux_dir/themes/tokyonight-day.conf"
status_tmux="skipped"
if [[ -r "$tmux_theme" ]]; then
if cp "$tmux_theme" "$tmux_dir/current-theme.conf.tmp" 2>/dev/null \
&& mv "$tmux_dir/current-theme.conf.tmp" "$tmux_dir/current-theme.conf" 2>/dev/null; then
status_tmux="written"
# Only if a server is actually running; `tmux source-file` would
# otherwise start one just to theme it.
if command -v tmux >/dev/null 2>&1 && tmux has-session 2>/dev/null; then
tmux source-file "$tmux_dir/current-theme.conf" 2>/dev/null \
&& status_tmux="applied to running sessions"
fi
else
rm -f "$tmux_dir/current-theme.conf.tmp"
status_tmux="failed"
fi
fi
# ── btop ─────────────────────────────────────────────────────────────────────
# Only the color_theme line is rewritten, in place. btop OWNS btop.conf -- it
# rewrites the whole file on exit -- so the config is not symlinked into Panama
# and not replaced wholesale here; just this one value is edited, and btop keeps
# it on the next write.
#
# btop reads its theme once at startup, so a running instance keeps the old
# colors until it is restarted. That is acceptable for a monitor you open when
# you want it, and forcing a restart would kill a process the user is watching.
btop_conf="${XDG_CONFIG_HOME:-$HOME/.config}/btop/btop.conf"
btop_theme="tokyonight-moon"
[[ "$scheme" == "light" ]] && btop_theme="tokyonight-day"
status_btop="skipped"
if [[ -w "$btop_conf" ]]; then
if sed -i "s|^color_theme *=.*|color_theme = \"$btop_theme\"|" "$btop_conf" 2>/dev/null; then
status_btop="written"
else
status_btop="failed"
fi
fi
# ── GTK ──────────────────────────────────────────────────────────────────────
# adw-gtk3, not Adwaita: no Adwaita GTK theme is installed on Fedora 44, and
# naming a theme that does not exist makes GTK fall back to its light default --
# which made dark mode silently produce light windows.
if [[ "$scheme" == "light" ]]; then
gtk_theme="adw-gtk3"
prefer_dark=0
else
gtk_theme="adw-gtk3-dark"
prefer_dark=1
fi
status_gtk="skipped"
for gtk_version in 3.0 4.0; do
gtk_dir="${XDG_CONFIG_HOME:-$HOME/.config}/gtk-$gtk_version"
template="$gtk_dir/settings.ini.template"
[[ -r "$template" ]] || continue
# Written atomically: a GTK application starting mid-write would otherwise
# read a truncated file and fall back to defaults.
if sed -e "s/@GTK_THEME@/$gtk_theme/" -e "s/@PREFER_DARK@/$prefer_dark/" "$template" \
>"$gtk_dir/settings.ini.tmp" 2>/dev/null \
&& mv "$gtk_dir/settings.ini.tmp" "$gtk_dir/settings.ini" 2>/dev/null; then
status_gtk="written"
else
rm -f "$gtk_dir/settings.ini.tmp"
status_gtk="failed"
fi
done
kitty_dir="${XDG_CONFIG_HOME:-$HOME/.config}/kitty"
theme_file="$kitty_dir/themes/tokyonight-moon.conf"
[[ "$scheme" == "light" ]] && theme_file="$kitty_dir/themes/tokyonight-day.conf"
status_kitty="skipped"
if [[ -r "$theme_file" ]]; then
# Written atomically: kitty may read this while a new window is starting.
# The accent override is appended after the copied theme rather than
# edited into the tracked theme file: kitty applies settings top to
# bottom, so a later active_border_color line wins, and this file is
# itself generated -- the tracked per-scheme theme stays scheme-only.
if cp "$theme_file" "$kitty_dir/current-theme.conf.tmp" 2>/dev/null \
&& printf 'active_border_color #%s\n' "$accent_border_hex" >>"$kitty_dir/current-theme.conf.tmp" \
&& mv "$kitty_dir/current-theme.conf.tmp" "$kitty_dir/current-theme.conf" 2>/dev/null; then
status_kitty="written"
else
rm -f "$kitty_dir/current-theme.conf.tmp"
fi
# Live-apply to running terminals. kitty appends its PID to the socket name
# from listen_on, so there is one socket per instance -- "unix:@mykitty"
# alone reaches nothing, which is exactly how this looked like remote
# control being disabled when it was not.
if command -v kitty >/dev/null 2>&1 && command -v ss >/dev/null 2>&1; then
applied=0
while read -r socket; do
[[ -n "$socket" ]] || continue
if kitty @ --to "unix:@${socket#@}" set-colors --all --configured "$theme_file" >/dev/null 2>&1 \
&& kitty @ --to "unix:@${socket#@}" set-colors --override "active_border_color=#$accent_border_hex" >/dev/null 2>&1; then
applied=$((applied + 1))
fi
done < <(ss -xl 2>/dev/null | grep -oE '@mykitty[^[:space:]]*' | sort -u)
(( applied > 0 )) && status_kitty="applied to $applied"
fi
fi
# Every target reports what actually happened. A helper that says only
# "kitty: applied" while silently skipping three other applications is how a
# half-applied theme goes unnoticed.
jq -cn \
--arg scheme "$scheme" \
--arg kitty "$status_kitty" \
--arg gtk "$status_gtk" \
--arg btop "$status_btop" \
--arg tmux "$status_tmux" \
--arg hyprlock "$status_hyprlock" \
'{scheme: $scheme, kitty: $kitty, gtk: $gtk, btop: $btop, tmux: $tmux, hyprlock: $hyprlock}'