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
This commit is contained in:
@@ -1,38 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Propagates the color scheme to applications that do not read the desktop
|
||||
# Propagates the active THEME 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.
|
||||
# nothing from us for light-or-dark: 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.
|
||||
# A scheme is not a theme, though. The portal can say "dark"; it cannot say
|
||||
# "Catppuccin Mocha". Everything below is an application that carries its own
|
||||
# palette, and every one of them used to be handed a copy of a Tokyo Night file
|
||||
# -- which is why choosing any other theme restyled the shell and nothing else.
|
||||
# They are now RENDERED from the active theme's palette instead:
|
||||
#
|
||||
# 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.
|
||||
# kitty current-theme.conf, plus a live `set-colors` over its socket
|
||||
# tmux current-theme.conf, re-sourced into running servers
|
||||
# btop a generated panama.theme, named by btop.conf's color_theme
|
||||
# hyprlock the eight placeholders in hyprlock.conf.template
|
||||
# GTK 3/4 the @define-color block in gtk.css, between the markers
|
||||
# Vicinae panama-dark.toml / panama-light.toml in its data directory
|
||||
# Firefox an Edge-Frfox override sheet, both schemes in one file
|
||||
#
|
||||
# panama-theme-apps dark|light [accent]
|
||||
#
|
||||
# The two arguments are the desktop's scheme and its nearest curated accent
|
||||
# name. They remain the interface because that is what ColorScheme.qml knows
|
||||
# and what GNOME's accent-color enum needs -- but the palette no longer comes
|
||||
# from them. This script resolves the active theme itself, from settings.json
|
||||
# and config/themes.json, through scripts/panama-palette. panama-lock resolves
|
||||
# it exactly the same way, from the same function.
|
||||
#
|
||||
# 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
|
||||
# already open change now. Without the second, a theme 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. The lookup lives in scripts/panama-palette,
|
||||
# which reads config/palette.json -- the one place the eight accents are
|
||||
# written down for everything outside QML.
|
||||
# The tokyonight-* files under kitty/themes, tmux/themes and btop/themes are
|
||||
# still shipped -- a user may name one directly -- but nothing copies them any
|
||||
# more.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# The accents, from the one file that holds them.
|
||||
# The accents and the theme resolver, from the one file that holds them.
|
||||
# shellcheck source=./panama-palette
|
||||
source "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/panama-palette"
|
||||
script_dir="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
|
||||
source "$script_dir/panama-palette"
|
||||
|
||||
scheme="${1:-dark}"
|
||||
case "$scheme" in
|
||||
@@ -50,87 +64,153 @@ case "$accent" in
|
||||
*) 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.
|
||||
config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
|
||||
|
||||
# hyprlock wants "R, G, B" decimal, not hex.
|
||||
# ── The theme ────────────────────────────────────────────────────────────────
|
||||
# The active one for everything that renders a single palette, and BOTH schemes
|
||||
# for Firefox -- its stylesheet carries light-dark() pairs so the browser flips
|
||||
# on its own, without this script being re-run.
|
||||
eval "$(theme_vars "$(resolve_theme "$scheme")")"
|
||||
eval "$(theme_vars "$(resolve_theme dark)" dark_)"
|
||||
eval "$(theme_vars "$(resolve_theme light)" light_)"
|
||||
|
||||
accent_border_hex="$(accent_hex "$accent" "$scheme")"
|
||||
# The accent pair falls back to the curated accent named on the command line.
|
||||
# A stored custom theme is allowed to carry an accent and no palette; one that
|
||||
# carries neither is still a real selection and should not render an empty
|
||||
# colour into a config file.
|
||||
theme_accent="$(hex_or "${theme_accent:-}" "$(accent_hex "$accent" "$scheme")")"
|
||||
theme_secondary="$(hex_or "${theme_secondary:-}" "$(accent_secondary_hex "$accent" "$scheme")")"
|
||||
dark_theme_accent="$(hex_or "${dark_theme_accent:-}" "$(accent_hex "$accent" dark)")"
|
||||
light_theme_accent="$(hex_or "${light_theme_accent:-}" "$(accent_hex "$accent" light)")"
|
||||
|
||||
# Whether there is enough of a palette to render from at all. An unreadable
|
||||
# themes.json is the only way to get here, and the honest answer is then to
|
||||
# leave every application's existing colours alone rather than to write half a
|
||||
# theme over them.
|
||||
palette_ok=true
|
||||
for token in bg bgDark bgHighlight bgPanel bgPopover fg fgDim fgMuted gutter \
|
||||
accentAlt cyan teal green yellow orange red redDeep magenta pink; do
|
||||
name="pal_$token"
|
||||
[[ -n "${!name:-}" ]] || palette_ok=false
|
||||
done
|
||||
|
||||
ansi_ok=true
|
||||
for token in black red green yellow blue magenta cyan white \
|
||||
brightBlack brightRed brightGreen brightYellow brightBlue \
|
||||
brightMagenta brightCyan brightWhite; do
|
||||
name="ansi_$token"
|
||||
[[ -n "${!name:-}" ]] || ansi_ok=false
|
||||
done
|
||||
|
||||
both_schemes_ok=true
|
||||
for token in bg bgDark bgHighlight bgPanel bgPopover fg fgDim fgMuted gutter red; do
|
||||
for side in dark light; do
|
||||
name="${side}_pal_$token"
|
||||
[[ -n "${!name:-}" ]] || both_schemes_ok=false
|
||||
done
|
||||
done
|
||||
|
||||
# GTK and Firefox want rgba(), not hex, wherever a colour carries transparency.
|
||||
rgba_of() {
|
||||
printf 'rgba(%s, %s)' "$(hex_to_rgb "$1")" "$2"
|
||||
}
|
||||
|
||||
# Text on a filled accent, in this theme's own grounds rather than in black and
|
||||
# white: a pastel accent takes the theme's darkest surface, a saturated one
|
||||
# takes its lightest text.
|
||||
accent_fg="$(on_hex "$theme_accent" "${pal_bgDark:-}" "${pal_fg:-}")"
|
||||
|
||||
# Replace one file in place, but only when the result actually differs, and say
|
||||
# which happened: "written", "unchanged", or "failed". Several of these targets
|
||||
# are tracked files reached through the dotfile symlinks, and rewriting them
|
||||
# with identical bytes on every accent sample would show up as a dirty working
|
||||
# tree for no change at all.
|
||||
#
|
||||
# A word rather than an exit status because every caller is under `set -e`,
|
||||
# where a helper that reports by returning 2 aborts the script instead.
|
||||
replace_if_changed() {
|
||||
local destination="$1" temporary="$2"
|
||||
if [[ -e "$destination" ]] && cmp -s "$temporary" "$destination"; then
|
||||
rm -f "$temporary"
|
||||
printf 'unchanged'
|
||||
return 0
|
||||
fi
|
||||
if mv "$temporary" "$destination" 2>/dev/null; then
|
||||
printf 'written'
|
||||
else
|
||||
rm -f "$temporary"
|
||||
printf 'failed'
|
||||
fi
|
||||
}
|
||||
|
||||
# ── 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"
|
||||
# The substitutions themselves live in panama-palette's render_hyprlock: this
|
||||
# script, panama-lock and setup/scripts/link-dotfiles all need them, and when
|
||||
# each carried its own copy the lock screen was the one that silently kept the
|
||||
# old colours.
|
||||
lock_dir="$config_home/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
|
||||
if render_hyprlock "$scheme" "$lock_template" "$lock_dir/hyprlock.conf" "$accent"; 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"
|
||||
# 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="$config_home/tmux"
|
||||
|
||||
render_tmux() {
|
||||
printf '# Generated by Panama from the "%s" theme. Do not edit.\n' "${theme_id:-custom}"
|
||||
printf '#\n# tmux.conf sources this file; panama-theme-apps rewrites it on every theme\n'
|
||||
printf '# change. The tokyonight-* files beside it are shipped for anyone who wants\n'
|
||||
printf '# to source one directly, and are no longer copied here.\n\n'
|
||||
|
||||
printf 'set -g mode-style "fg=#%s,bg=#%s"\n\n' "$theme_accent" "$pal_gutter"
|
||||
|
||||
printf 'set -g message-style "fg=#%s,bg=#%s"\n' "$theme_accent" "$pal_gutter"
|
||||
printf 'set -g message-command-style "fg=#%s,bg=#%s"\n\n' "$theme_accent" "$pal_gutter"
|
||||
|
||||
printf 'set -g pane-border-style "fg=#%s"\n' "$theme_accent"
|
||||
printf 'set -g pane-active-border-style "fg=#%s"\n\n' "$theme_secondary"
|
||||
|
||||
printf 'set -g status-style "fg=#%s,bg=#%s"\n' "$theme_secondary" "$pal_gutter"
|
||||
printf 'set -g status-bg "#%s"\n\n' "$pal_bg"
|
||||
|
||||
printf 'set -g status-left "#[fg=#%s,bg=#%s,bold] #S #[fg=#%s,bg=#%s,nobold,nounderscore,noitalics]"\n' \
|
||||
"$ansi_black" "$theme_secondary" "$ansi_black" "$theme_secondary"
|
||||
printf 'set -g status-right "#[fg=#%s,bg=#%s,nobold,nounderscore,noitalics]#[fg=#%s,bg=#%s] #{prefix_highlight} #[fg=#%s,bg=#%s]#[fg=#%s,bg=#%s] %%Y/%%m/%%d %%I:%%M %%p #[fg=#%s,bg=#%s,nobold,nounderscore,noitalics]#[fg=#%s,bg=#%s,bold,italics] #h "\n\n' \
|
||||
"$ansi_black" "$theme_accent" "$theme_secondary" "$pal_gutter" \
|
||||
"$pal_gutter" "$pal_gutter" "$theme_secondary" "$pal_gutter" \
|
||||
"$theme_accent" "$pal_gutter" "$ansi_black" "$theme_secondary"
|
||||
|
||||
printf 'setw -g window-status-format "#[fg=#%s,bg=#%s,nobold,nounderscore,noitalics]#[fg=#%s,bg=#%s] #I #W #F #[fg=#%s,bg=#%s,nobold,nounderscore,noitalics]"\n' \
|
||||
"$ansi_black" "$pal_gutter" "$theme_accent" "$pal_gutter" "$ansi_black" "$pal_gutter"
|
||||
printf 'setw -g window-status-current-format "#[fg=#%s,bg=#%s,nobold,nounderscore,noitalics]#[fg=#%s,bg=#%s,bold] #I #W #F #[fg=#%s,bg=#%s,nobold,nounderscore,noitalics]"\n\n' \
|
||||
"$ansi_black" "$pal_gutter" "$theme_secondary" "$pal_gutter" "$pal_gutter" "$pal_gutter"
|
||||
|
||||
printf 'setw -g window-status-separator ""\n'
|
||||
}
|
||||
|
||||
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"
|
||||
if [[ -d "$tmux_dir" ]] && [[ "$palette_ok" == true ]] && [[ "$ansi_ok" == true ]]; then
|
||||
if render_tmux >"$tmux_dir/current-theme.conf.tmp" 2>/dev/null; then
|
||||
status_tmux="$(replace_if_changed "$tmux_dir/current-theme.conf" "$tmux_dir/current-theme.conf.tmp")"
|
||||
# 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
|
||||
if [[ "$status_tmux" != "failed" ]] \
|
||||
&& 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
|
||||
@@ -141,31 +221,98 @@ if [[ -r "$tmux_theme" ]]; then
|
||||
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.
|
||||
# The theme is generated into btop's own themes directory and btop.conf is
|
||||
# pointed at it by name. 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.
|
||||
#
|
||||
# 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"
|
||||
btop_conf="$config_home/btop/btop.conf"
|
||||
btop_theme_dir="$config_home/btop/themes"
|
||||
|
||||
render_btop() {
|
||||
printf '# Generated by Panama from the "%s" theme. Do not edit.\n' "${theme_id:-custom}"
|
||||
printf '#\n# The *_start/_mid/_end triples are gradients btop draws meters with: low,\n'
|
||||
printf '# middle, and high. They run cool -> warm -> hot so a saturated resource is\n'
|
||||
printf '# obvious at a glance without reading the number.\n\n'
|
||||
|
||||
printf 'theme[main_bg]="#%s"\n' "$pal_bg"
|
||||
printf 'theme[main_fg]="#%s"\n' "$pal_fg"
|
||||
printf 'theme[title]="#%s"\n' "$pal_fg"
|
||||
printf 'theme[hi_fg]="#%s"\n' "$theme_accent"
|
||||
printf 'theme[selected_bg]="#%s"\n' "$pal_gutter"
|
||||
printf 'theme[selected_fg]="#%s"\n' "$theme_accent"
|
||||
printf 'theme[inactive_fg]="#%s"\n' "$pal_fgMuted"
|
||||
printf 'theme[proc_misc]="#%s"\n\n' "$pal_green"
|
||||
|
||||
# The four boxes share one hairline, a step brighter than the gutter so the
|
||||
# frame reads as structure rather than as content.
|
||||
local box; box="$(mix_hex "$pal_gutter" "$pal_fgDim" 30)"
|
||||
printf 'theme[cpu_box]="#%s"\n' "$box"
|
||||
printf 'theme[mem_box]="#%s"\n' "$box"
|
||||
printf 'theme[net_box]="#%s"\n' "$box"
|
||||
printf 'theme[proc_box]="#%s"\n' "$box"
|
||||
printf 'theme[div_line]="#%s"\n\n' "$pal_gutter"
|
||||
|
||||
printf 'theme[temp_start]="#%s"\n' "$theme_accent"
|
||||
printf 'theme[temp_mid]="#%s"\n' "$pal_yellow"
|
||||
printf 'theme[temp_end]="#%s"\n\n' "$pal_red"
|
||||
|
||||
printf 'theme[cpu_start]="#%s"\n' "$theme_accent"
|
||||
printf 'theme[cpu_mid]="#%s"\n' "$pal_magenta"
|
||||
printf 'theme[cpu_end]="#%s"\n\n' "$pal_red"
|
||||
|
||||
printf 'theme[free_start]="#%s"\n' "$pal_gutter"
|
||||
printf 'theme[free_mid]="#%s"\n' "$pal_accentAlt"
|
||||
printf 'theme[free_end]="#%s"\n\n' "$pal_cyan"
|
||||
|
||||
printf 'theme[cached_start]="#%s"\n' "$pal_cyan"
|
||||
printf 'theme[cached_mid]="#%s"\n' "$theme_accent"
|
||||
printf 'theme[cached_end]="#%s"\n\n' "$pal_magenta"
|
||||
|
||||
printf 'theme[available_start]="#%s"\n' "$pal_yellow"
|
||||
printf 'theme[available_mid]="#%s"\n' "$pal_orange"
|
||||
printf 'theme[available_end]="#%s"\n\n' "$pal_red"
|
||||
|
||||
printf 'theme[used_start]="#%s"\n' "$pal_green"
|
||||
printf 'theme[used_mid]="#%s"\n' "$pal_yellow"
|
||||
printf 'theme[used_end]="#%s"\n\n' "$pal_red"
|
||||
|
||||
printf 'theme[download_start]="#%s"\n' "$pal_gutter"
|
||||
printf 'theme[download_mid]="#%s"\n' "$theme_accent"
|
||||
printf 'theme[download_end]="#%s"\n\n' "$pal_cyan"
|
||||
|
||||
printf 'theme[upload_start]="#%s"\n' "$pal_gutter"
|
||||
printf 'theme[upload_mid]="#%s"\n' "$pal_magenta"
|
||||
printf 'theme[upload_end]="#%s"\n' "$pal_pink"
|
||||
}
|
||||
|
||||
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"
|
||||
if [[ "$palette_ok" == true ]] && mkdir -p "$btop_theme_dir" 2>/dev/null; then
|
||||
if render_btop >"$btop_theme_dir/panama.theme.tmp" 2>/dev/null; then
|
||||
status_btop="$(replace_if_changed "$btop_theme_dir/panama.theme" "$btop_theme_dir/panama.theme.tmp")"
|
||||
else
|
||||
rm -f "$btop_theme_dir/panama.theme.tmp"
|
||||
status_btop="failed"
|
||||
fi
|
||||
|
||||
if [[ "$status_btop" != "failed" && -w "$btop_conf" ]]; then
|
||||
sed -i 's|^color_theme *=.*|color_theme = "panama"|' "$btop_conf" 2>/dev/null \
|
||||
|| status_btop="failed"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── GTK ──────────────────────────────────────────────────────────────────────
|
||||
# ── GTK settings.ini ─────────────────────────────────────────────────────────
|
||||
# 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.
|
||||
#
|
||||
# GTK3 is the reason this file is generated at all. 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.
|
||||
if [[ "$scheme" == "light" ]]; then
|
||||
gtk_theme="adw-gtk3"
|
||||
prefer_dark=0
|
||||
@@ -176,7 +323,7 @@ fi
|
||||
|
||||
status_gtk="skipped"
|
||||
for gtk_version in 3.0 4.0; do
|
||||
gtk_dir="${XDG_CONFIG_HOME:-$HOME/.config}/gtk-$gtk_version"
|
||||
gtk_dir="$config_home/gtk-$gtk_version"
|
||||
template="$gtk_dir/settings.ini.template"
|
||||
[[ -r "$template" ]] || continue
|
||||
|
||||
@@ -192,6 +339,138 @@ for gtk_version in 3.0 4.0; do
|
||||
fi
|
||||
done
|
||||
|
||||
# ── GTK gtk.css ──────────────────────────────────────────────────────────────
|
||||
# settings.ini names a theme; these are the colours that theme is overridden
|
||||
# with. libadwaita reads @define-color from the user stylesheet, which is how
|
||||
# Nautilus, Papers and every other GTK application end up wearing the Panama
|
||||
# palette rather than adw-gtk3's stock greys.
|
||||
#
|
||||
# Only the marked block is rewritten. Everything else in these files is the
|
||||
# structural CSS of a vendored theme -- a thousand lines of rounding, padding
|
||||
# and window controls that has nothing to do with colour, and that regenerating
|
||||
# the file wholesale would destroy.
|
||||
#
|
||||
# All three files get the ACTIVE theme rather than one being pinned light and
|
||||
# another dark. gtk-4.0/gtk.css was a byte-for-byte copy of the dark one, so a
|
||||
# light desktop drew dark GTK4 windows; rendering the active theme into each is
|
||||
# what actually fixes that, whichever of the two GTK decides to read.
|
||||
gtk_css_block() {
|
||||
local shade sidebar_line
|
||||
shade="$(rgba_of "$pal_bgDark" 0.25)"
|
||||
sidebar_line="$(rgba_of "$pal_gutter" 0.36)"
|
||||
|
||||
printf '/* Generated from the "%s" theme by panama-theme-apps. Edit the\n' "${theme_id:-custom}"
|
||||
printf ' * theme, not this block -- the next theme change overwrites it. */\n'
|
||||
|
||||
printf '@define-color accent_bg_color #%s;\n' "$theme_accent"
|
||||
printf '@define-color accent_color #%s;\n' "$pal_accentAlt"
|
||||
printf '@define-color accent_fg_color #%s;\n' "$accent_fg"
|
||||
printf '@define-color destructive_bg_color #%s;\n' "$pal_redDeep"
|
||||
printf '@define-color destructive_color #%s;\n' "$pal_red"
|
||||
printf '@define-color destructive_fg_color #%s;\n' "$(on_hex "$pal_redDeep" "$pal_bgDark" "$pal_fg")"
|
||||
printf '@define-color success_bg_color #%s;\n' "$pal_green"
|
||||
printf '@define-color success_color #%s;\n' "$pal_green"
|
||||
printf '@define-color success_fg_color #%s;\n' "$(on_hex "$pal_green" "$pal_bgDark" "$pal_fg")"
|
||||
printf '@define-color warning_bg_color #%s;\n' "$pal_yellow"
|
||||
printf '@define-color warning_color #%s;\n' "$pal_yellow"
|
||||
printf '@define-color warning_fg_color #%s;\n' "$(on_hex "$pal_yellow" "$pal_bgDark" "$pal_fg")"
|
||||
printf '@define-color error_bg_color #%s;\n' "$pal_redDeep"
|
||||
printf '@define-color error_color #%s;\n' "$pal_red"
|
||||
printf '@define-color error_fg_color #%s;\n' "$(on_hex "$pal_redDeep" "$pal_bgDark" "$pal_fg")"
|
||||
|
||||
printf '@define-color window_bg_color #%s;\n' "$pal_bgPanel"
|
||||
printf '@define-color window_fg_color #%s;\n' "$pal_fg"
|
||||
printf '@define-color view_bg_color #%s;\n' "$pal_bg"
|
||||
printf '@define-color view_fg_color #%s;\n' "$pal_fg"
|
||||
printf '@define-color headerbar_bg_color #%s;\n' "$pal_bgPanel"
|
||||
printf '@define-color headerbar_fg_color #%s;\n' "$pal_fg"
|
||||
printf '@define-color headerbar_border_color #%s;\n' "$pal_gutter"
|
||||
printf '@define-color headerbar_backdrop_color #%s;\n' "$pal_bgDark"
|
||||
printf '@define-color headerbar_shade_color %s;\n' "$(rgba_of "$pal_bgDark" 0.08)"
|
||||
printf '@define-color headerbar_darker_shade_color %s;\n' "$(rgba_of "$pal_bgDark" 0.9)"
|
||||
|
||||
printf '@define-color sidebar_bg_color #%s;\n' "$pal_bgDark"
|
||||
printf '@define-color sidebar_fg_color #%s;\n' "$pal_fg"
|
||||
printf '@define-color sidebar_backdrop_color #%s;\n' "$pal_bgDark"
|
||||
printf '@define-color sidebar_shade_color %s;\n' "$(rgba_of "$pal_gutter" 0.08)"
|
||||
printf '@define-color sidebar_border_color %s;\n' "$sidebar_line"
|
||||
printf '@define-color secondary_sidebar_bg_color #%s;\n' "$pal_bgDark"
|
||||
printf '@define-color secondary_sidebar_fg_color #%s;\n' "$pal_fg"
|
||||
printf '@define-color secondary_sidebar_backdrop_color #%s;\n' "$pal_bgDark"
|
||||
printf '@define-color secondary_sidebar_shade_color %s;\n' "$shade"
|
||||
printf '@define-color secondary_sidebar_border_color %s;\n' "$sidebar_line"
|
||||
|
||||
printf '@define-color card_bg_color #%s;\n' "$pal_bgHighlight"
|
||||
printf '@define-color card_fg_color #%s;\n' "$pal_fg"
|
||||
printf '@define-color card_shade_color %s;\n' "$shade"
|
||||
printf '@define-color dialog_bg_color #%s;\n' "$pal_bgPopover"
|
||||
printf '@define-color dialog_fg_color #%s;\n' "$pal_fg"
|
||||
printf '@define-color popover_bg_color #%s;\n' "$pal_bgPopover"
|
||||
printf '@define-color popover_fg_color #%s;\n' "$pal_fg"
|
||||
printf '@define-color popover_shade_color %s;\n' "$shade"
|
||||
printf '@define-color thumbnail_bg_color #%s;\n' "$pal_bgHighlight"
|
||||
printf '@define-color thumbnail_fg_color #%s;\n' "$pal_fg"
|
||||
printf '@define-color shade_color %s;\n' "$shade"
|
||||
printf '@define-color scrollbar_outline_color %s;\n' "$(rgba_of "$pal_bgDark" 0.5)"
|
||||
}
|
||||
|
||||
# Substitutes the block between the markers, leaving every other byte alone.
|
||||
# The block is handed over in a file rather than through `awk -v`, which
|
||||
# processes backslash escapes in the value it is given.
|
||||
write_gtk_block() {
|
||||
local target="$1" block_file="$2" temporary="$1.tmp"
|
||||
if ! grep -q 'PANAMA THEME BEGIN' "$target" 2>/dev/null \
|
||||
|| ! grep -q 'PANAMA THEME END' "$target" 2>/dev/null; then
|
||||
printf 'no marker'
|
||||
return 0
|
||||
fi
|
||||
|
||||
awk -v block="$block_file" '
|
||||
index($0, "PANAMA THEME BEGIN") {
|
||||
print
|
||||
while ((getline line < block) > 0) print line
|
||||
close(block)
|
||||
skipping = 1
|
||||
next
|
||||
}
|
||||
index($0, "PANAMA THEME END") { skipping = 0; print; next }
|
||||
!skipping { print }
|
||||
' "$target" >"$temporary" 2>/dev/null || true
|
||||
|
||||
if [[ ! -s "$temporary" ]]; then
|
||||
rm -f "$temporary"
|
||||
printf 'failed'
|
||||
return 0
|
||||
fi
|
||||
replace_if_changed "$target" "$temporary"
|
||||
}
|
||||
|
||||
status_gtkcss="skipped"
|
||||
if [[ "$palette_ok" == true ]]; then
|
||||
gtk_block_file="$(mktemp "${TMPDIR:-/tmp}/panama-gtk-block.XXXXXX")"
|
||||
if gtk_css_block >"$gtk_block_file" 2>/dev/null; then
|
||||
for gtk_css in "$config_home/gtk-3.0/gtk.css" \
|
||||
"$config_home/gtk-4.0/gtk.css" \
|
||||
"$config_home/gtk-4.0/gtk-dark.css"; do
|
||||
[[ -w "$gtk_css" ]] || continue
|
||||
gtk_css_result="$(write_gtk_block "$gtk_css" "$gtk_block_file")"
|
||||
# The worst outcome across the three files is the one worth
|
||||
# reporting: two written and one missing its markers is not
|
||||
# "written".
|
||||
case "$gtk_css_result" in
|
||||
failed) status_gtkcss="failed" ;;
|
||||
"no marker") [[ "$status_gtkcss" == "failed" ]] || status_gtkcss="no marker" ;;
|
||||
unchanged) [[ "$status_gtkcss" == "skipped" ]] && status_gtkcss="unchanged" || true ;;
|
||||
written) [[ "$status_gtkcss" == "skipped" || "$status_gtkcss" == "unchanged" ]] \
|
||||
&& status_gtkcss="written" || true ;;
|
||||
esac
|
||||
done
|
||||
else
|
||||
status_gtkcss="failed"
|
||||
fi
|
||||
rm -f "$gtk_block_file"
|
||||
fi
|
||||
|
||||
# ── libadwaita accent ────────────────────────────────────────────────────────
|
||||
# Files, Papers, Loupe and every other libadwaita application read their accent
|
||||
# from the Settings portal, not from anything Panama draws, so without this they
|
||||
@@ -199,10 +478,9 @@ done
|
||||
#
|
||||
# GNOME's accent-color is a FIXED enum of nine names -- there is no hex here to
|
||||
# match, only a nearest member to pick. Which one each Panama accent maps to is
|
||||
# recorded in config/palette.json alongside its colours, for the same reason
|
||||
# accent lookup once was: there was no shared source between QML and shell.
|
||||
# There is now -- config/palette.json -- and palette-contract fails when the
|
||||
# QML table and it disagree.
|
||||
# recorded in config/palette.json alongside its colours, and this is the one
|
||||
# place the command line's accent NAME is still the input rather than the
|
||||
# theme's own hex: an enum cannot take a colour.
|
||||
#
|
||||
# The portal must route Settings to the gnome backend for this to reach
|
||||
# anything; xdg-desktop-portal-gtk cannot serve accent-color at all. See
|
||||
@@ -222,41 +500,350 @@ if command -v gsettings >/dev/null 2>&1; then
|
||||
fi
|
||||
fi
|
||||
|
||||
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"
|
||||
# ── Vicinae ──────────────────────────────────────────────────────────────────
|
||||
# The launcher selects a theme per system appearance, so both schemes are
|
||||
# rendered every time and vicinae.json names them permanently. They are written
|
||||
# straight into Vicinae's data directory rather than into the repository's
|
||||
# themes folder: that folder is symlinked into ~/.local/share/vicinae/themes,
|
||||
# and a generated file inside it would be a machine's colours showing up as a
|
||||
# change to the repository.
|
||||
vicinae_dir="$data_home/vicinae/themes"
|
||||
|
||||
render_vicinae() {
|
||||
(
|
||||
set -euo pipefail
|
||||
eval "$(theme_vars "$(resolve_theme "$1")")"
|
||||
local variant="$1"
|
||||
local core_accent core_secondary selection secondary_selection tile on_accent
|
||||
core_accent="$(hex_or "${theme_accent:-}" "$(accent_hex "$2" "$1")")"
|
||||
core_secondary="$(hex_or "${theme_secondary:-}" "$(accent_secondary_hex "$2" "$1")")"
|
||||
on_accent="$(on_hex "$core_accent" "$pal_bgDark" "$pal_fg")"
|
||||
# The two halves of the Prism selection, mixed into the highlight
|
||||
# surface rather than named as tokens -- no palette key describes
|
||||
# "a row that is selected".
|
||||
selection="$(mix_hex "$pal_bgHighlight" "$core_accent" 30)"
|
||||
secondary_selection="$(mix_hex "$pal_bgHighlight" "$core_secondary" 30)"
|
||||
tile="$(mix_hex "$pal_bg" "$pal_bgHighlight" 50)"
|
||||
|
||||
printf '# Generated by Panama from the "%s" theme. Do not edit.\n' "${theme_id:-custom}"
|
||||
printf '# panama-theme-apps rewrites this file on every theme change.\n\n'
|
||||
|
||||
printf '[meta]\nversion = 1\n'
|
||||
printf 'name = "Panama %s"\n' "$variant"
|
||||
printf 'description = "The active Panama theme, rendered for Vicinae."\n'
|
||||
printf 'variant = "%s"\n\n' "$variant"
|
||||
|
||||
printf '[colors.core]\n'
|
||||
printf 'background = "#%s"\n' "$pal_bg"
|
||||
printf 'foreground = "#%s"\n' "$pal_fg"
|
||||
printf 'secondary_background = "#%s"\n' "$pal_bgDark"
|
||||
printf 'border = "#%s"\n' "$pal_gutter"
|
||||
printf 'accent = "#%s"\n' "$core_accent"
|
||||
printf 'accent_foreground = "#%s"\n\n' "$on_accent"
|
||||
|
||||
printf '[colors.accents]\n'
|
||||
# The terminal blue, not accentAlt: on a light theme accentAlt is often
|
||||
# the same cyan the next line already names.
|
||||
printf 'blue = "#%s"\n' "$(hex_or "${ansi_blue:-}" "$pal_accentAlt")"
|
||||
printf 'green = "#%s"\n' "$pal_green"
|
||||
printf 'magenta = "#%s"\n' "$pal_magenta"
|
||||
printf 'orange = "#%s"\n' "$pal_orange"
|
||||
printf 'purple = "#%s"\n' "$pal_pink"
|
||||
printf 'red = "#%s"\n' "$pal_red"
|
||||
printf 'yellow = "#%s"\n' "$pal_yellow"
|
||||
printf 'cyan = "#%s"\n\n' "$pal_cyan"
|
||||
|
||||
printf '[colors.main_window]\n'
|
||||
printf 'border = "#%s"\n' "$pal_gutter"
|
||||
printf 'footer = { background = "colors.core.secondary_background" }\n\n'
|
||||
|
||||
printf '[colors.settings_window]\n'
|
||||
printf 'border = "#%s"\n\n' "$pal_gutter"
|
||||
|
||||
printf '[colors.shortcut]\n'
|
||||
printf 'border = "colors.core.border"\n\n'
|
||||
|
||||
printf '[colors.text]\n'
|
||||
printf 'default = "colors.core.foreground"\n'
|
||||
printf 'muted = "#%s"\n' "$pal_fgDim"
|
||||
printf 'danger = "#%s"\n' "$pal_red"
|
||||
printf 'success = "#%s"\n' "$pal_green"
|
||||
printf 'placeholder = "#%s"\n' "$pal_fgMuted"
|
||||
printf 'selection = { background = "#%s", foreground = "#%s" }\n\n' "$core_accent" "$on_accent"
|
||||
|
||||
printf '[colors.text.links]\n'
|
||||
printf 'default = "#%s"\n' "$core_accent"
|
||||
printf 'visited = "#%s"\n\n' "$pal_magenta"
|
||||
|
||||
printf '[colors.input]\n'
|
||||
printf 'border = "#%s"\n' "$pal_gutter"
|
||||
printf 'border_focus = "#%s"\n' "$core_accent"
|
||||
printf 'border_error = "#%s"\n\n' "$pal_red"
|
||||
|
||||
printf '[colors.button.primary]\n'
|
||||
printf 'background = "#%s"\n' "$pal_bgHighlight"
|
||||
printf 'foreground = "#%s"\n' "$pal_fg"
|
||||
printf 'hover = { background = "#%s" }\n' "$pal_gutter"
|
||||
printf 'focus = { outline = "colors.core.accent" }\n\n'
|
||||
|
||||
printf '[colors.list.item.hover]\n'
|
||||
printf 'foreground = "#%s"\n' "$pal_fg"
|
||||
printf 'secondary_foreground = "#%s"\n\n' "$pal_fgDim"
|
||||
|
||||
printf '[colors.list.item.selection]\n'
|
||||
printf 'background = "#%s"\n' "$selection"
|
||||
printf 'foreground = "#%s"\n' "$pal_fg"
|
||||
printf 'secondary_background = "#%s"\n' "$secondary_selection"
|
||||
printf 'secondary_foreground = "#%s"\n\n' "$pal_fg"
|
||||
|
||||
printf '[colors.grid.item]\n'
|
||||
printf 'background = "#%s"\n' "$tile"
|
||||
printf 'hover = { outline = "#%s" }\n' "$core_accent"
|
||||
printf 'selection = { outline = "#%s" }\n\n' "$core_secondary"
|
||||
|
||||
printf '[colors.scrollbars]\n'
|
||||
printf 'background = "#%s"\n\n' "$pal_gutter"
|
||||
|
||||
printf '[colors.loading]\n'
|
||||
printf 'bar = "#%s"\n' "$core_accent"
|
||||
printf 'spinner = "#%s"\n' "$pal_fg"
|
||||
)
|
||||
}
|
||||
|
||||
status_vicinae="skipped"
|
||||
if [[ "$both_schemes_ok" == true ]] && mkdir -p "$vicinae_dir" 2>/dev/null; then
|
||||
status_vicinae="written"
|
||||
for vicinae_scheme in dark light; do
|
||||
target="$vicinae_dir/panama-$vicinae_scheme.toml"
|
||||
if render_vicinae "$vicinae_scheme" "$accent" >"$target.tmp" 2>/dev/null; then
|
||||
[[ "$(replace_if_changed "$target" "$target.tmp")" == "failed" ]] \
|
||||
&& status_vicinae="failed" || true
|
||||
else
|
||||
rm -f "$target.tmp"
|
||||
status_vicinae="failed"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# ── Firefox ──────────────────────────────────────────────────────────────────
|
||||
# Edge-Frfox, the vendored chrome theme, takes its colours from GTK system
|
||||
# colours on Linux -- AccentColor, -moz-dialog, Field. Those follow the portal,
|
||||
# which knows light from dark and nothing about which theme is on. So Firefox
|
||||
# was the one surface that stayed grey-blue whatever Panama was wearing.
|
||||
#
|
||||
# This file is generated beside custom.css and imported by it, rather than being
|
||||
# custom.css itself: custom.css is the seam a person edits, and regenerating it
|
||||
# on every theme change would eat their overrides. It carries BOTH schemes as
|
||||
# light-dark() pairs, so Firefox flips with the portal without this script
|
||||
# running again -- and so a private window, which forces its own dark scheme,
|
||||
# is themed too.
|
||||
#
|
||||
# Chrome only. about: pages are styled from userContent.css, which is an
|
||||
# upstream file and does not import this one.
|
||||
firefox_chrome="${PANAMA_FIREFOX_CHROME:-$(cd "$script_dir/../../../.." 2>/dev/null && pwd)/config/firefox/chrome}"
|
||||
|
||||
render_firefox() {
|
||||
# light-dark() pairs, built from the two resolved palettes. Firefox picks
|
||||
# the half that matches the chrome's own color-scheme.
|
||||
pair() {
|
||||
local light="light_pal_$1" dark="dark_pal_$1"
|
||||
printf 'light-dark(#%s, #%s)' "${!light}" "${!dark}"
|
||||
}
|
||||
local accents on_light on_dark pressed
|
||||
accents="light-dark(#$light_theme_accent, #$dark_theme_accent)"
|
||||
on_light="$(on_hex "$light_theme_accent" "$light_pal_bgDark" "$light_pal_fg")"
|
||||
on_dark="$(on_hex "$dark_theme_accent" "$dark_pal_bgDark" "$dark_pal_fg")"
|
||||
# A pressed button has to read as a step past hover, and no palette token
|
||||
# sits between the gutter and the text -- so it is mixed.
|
||||
pressed="light-dark(#$(mix_hex "$light_pal_gutter" "$light_pal_fg" 15), #$(mix_hex "$dark_pal_gutter" "$dark_pal_fg" 15))"
|
||||
|
||||
printf '/* Generated by panama-theme-apps. Do not edit -- custom.css imports this,\n'
|
||||
printf ' * and the next theme change overwrites it. Local Firefox tweaks belong in\n'
|
||||
printf ' * custom.css, which is never regenerated.\n'
|
||||
printf ' *\n'
|
||||
printf ' * Both schemes are here as light-dark() pairs so the browser follows the\n'
|
||||
printf ' * portal on its own. :root:root out-specifies Edge-Frfox colors.css, which\n'
|
||||
printf ' * uses :root:not([lwtheme]) -- a plain :root would lose the tie. */\n\n'
|
||||
|
||||
printf ':root:root:not([lwtheme]),\n'
|
||||
printf ':root:root[privatebrowsingmode="temporary"] {\n'
|
||||
|
||||
printf ' /* Toolbars */\n'
|
||||
printf ' --lwt-accent-color: %s !important;\n' "$(pair bg)"
|
||||
printf ' --lwt-accent-color-inactive: %s !important;\n' "$(pair bgDark)"
|
||||
printf ' --lwt-text-color: %s !important;\n' "$(pair fg)"
|
||||
printf ' --lwt-text-color-inactive: %s !important;\n' "$(pair fgDim)"
|
||||
printf ' --toolbar-bgcolor: %s !important;\n' "$(pair bgPanel)"
|
||||
printf ' --toolbar-non-lwt-bgcolor: var(--toolbar-bgcolor) !important;\n'
|
||||
printf ' --toolbar-color: %s !important;\n' "$(pair fg)"
|
||||
printf ' --toolbarbutton-icon-fill: var(--toolbar-color) !important;\n'
|
||||
printf ' --toolbarbutton-icon-fill-attention: %s !important;\n' "$accents"
|
||||
printf ' --chrome-content-separator-color: %s !important;\n' "$(pair gutter)"
|
||||
printf ' --newtab-background-color: %s !important;\n' "$(pair bg)"
|
||||
|
||||
printf ' /* Tabs */\n'
|
||||
printf ' --tab-selected-bgcolor: var(--toolbar-bgcolor) !important;\n'
|
||||
printf ' --tab-selected-textcolor: %s !important;\n' "$(pair fg)"
|
||||
printf ' --tab-selected-textcolor-inactive: %s !important;\n' "$(pair fgDim)"
|
||||
printf ' --tab-attention-icon-color: %s !important;\n' "$accents"
|
||||
|
||||
printf ' /* URL bar */\n'
|
||||
printf ' --toolbar-field-background-color: %s !important;\n' "$(pair bgHighlight)"
|
||||
printf ' --toolbar-field-color: %s !important;\n' "$(pair fg)"
|
||||
printf ' --toolbar-field-border-color: transparent !important;\n'
|
||||
printf ' --toolbar-field-focus-background-color: %s !important;\n' "$(pair bgHighlight)"
|
||||
printf ' --toolbar-field-focus-color: %s !important;\n' "$(pair fg)"
|
||||
printf ' --toolbar-field-focus-border-color: %s !important;\n' "$accents"
|
||||
printf ' --urlbar-box-bgcolor: %s !important;\n' "$(pair bgPopover)"
|
||||
printf ' --urlbar-box-hover-bgcolor: %s !important;\n' "$(pair bgHighlight)"
|
||||
printf ' --urlbar-box-active-bgcolor: %s !important;\n' "$(pair gutter)"
|
||||
printf ' --urlbar-box-focus-bgcolor: var(--urlbar-box-hover-bgcolor) !important;\n'
|
||||
printf ' --link-color: %s !important;\n' "$accents"
|
||||
printf ' --uc-urlbarView-accent-color: %s !important;\n' "$accents"
|
||||
printf ' --urlbarView-highlight-background: %s !important;\n' "$(pair bgHighlight)"
|
||||
printf ' --urlbarView-highlight-color: %s !important;\n' "$(pair fg)"
|
||||
printf ' --urlbarView-hover-background: %s !important;\n' "$(pair gutter)"
|
||||
printf ' --urlbarView-separator-color: %s !important;\n' "$(pair gutter)"
|
||||
|
||||
printf ' /* Menus and panels */\n'
|
||||
printf ' --arrowpanel-background: %s !important;\n' "$(pair bgPopover)"
|
||||
printf ' --arrowpanel-color: %s !important;\n' "$(pair fg)"
|
||||
printf ' --arrowpanel-border-color: %s !important;\n' "$(pair gutter)"
|
||||
printf ' --panel-separator-color: %s !important;\n' "$(pair gutter)"
|
||||
printf ' --panel-description-color: %s !important;\n' "$(pair fgDim)"
|
||||
printf ' --panel-disabled-color: %s !important;\n' "$(pair fgMuted)"
|
||||
printf ' --panel-item-hover-bgcolor: %s !important;\n' "$(pair bgHighlight)"
|
||||
printf ' --panel-item-active-bgcolor: %s !important;\n' "$(pair gutter)"
|
||||
|
||||
printf ' /* Buttons */\n'
|
||||
printf ' --button-primary-bgcolor: %s !important;\n' "$accents"
|
||||
printf ' --button-primary-color: light-dark(#%s, #%s) !important;\n' "$on_light" "$on_dark"
|
||||
printf ' --button-bgcolor: %s !important;\n' "$(pair bgHighlight)"
|
||||
printf ' --button-color: %s !important;\n' "$(pair fg)"
|
||||
printf ' --button-hover-bgcolor: %s !important;\n' "$(pair gutter)"
|
||||
printf ' --button-active-bgcolor: %s !important;\n' "$pressed"
|
||||
|
||||
printf ' /* Inputs */\n'
|
||||
printf ' --focus-outline-color: %s !important;\n' "$accents"
|
||||
printf ' --input-bgcolor: %s !important;\n' "$(pair bgPopover)"
|
||||
printf ' --input-color: %s !important;\n' "$(pair fg)"
|
||||
printf ' --input-border-color: %s !important;\n' "$(pair gutter)"
|
||||
printf ' --error-text-color: %s !important;\n' "$(pair red)"
|
||||
printf ' --input-error-border-color: %s !important;\n' "$(pair red)"
|
||||
|
||||
printf ' /* Sidebar */\n'
|
||||
printf ' --sidebar-background-color: %s !important;\n' "$(pair bgPanel)"
|
||||
printf ' --sidebar-text-color: %s !important;\n' "$(pair fg)"
|
||||
printf ' --sidebar-border-color: %s !important;\n' "$(pair gutter)"
|
||||
|
||||
# Chrome-level dialogs -- the bookmark editor, the print sheet. Edge-Frfox
|
||||
# points these at the panel colours; the in-content names are what the
|
||||
# dialog itself reads.
|
||||
printf '\n &[dialogroot] {\n'
|
||||
printf ' --in-content-page-background: %s !important;\n' "$(pair bgPopover)"
|
||||
printf ' --in-content-page-color: %s !important;\n' "$(pair fg)"
|
||||
printf ' --in-content-accent-color: %s !important;\n' "$accents"
|
||||
printf ' --in-content-primary-button-background: %s !important;\n' "$accents"
|
||||
printf ' --in-content-primary-button-text-color: light-dark(#%s, #%s) !important;\n' \
|
||||
"$on_light" "$on_dark"
|
||||
printf ' --in-content-button-background: %s !important;\n' "$(pair bgHighlight)"
|
||||
printf ' --in-content-button-text-color: %s !important;\n' "$(pair fg)"
|
||||
printf ' --in-content-box-background: %s !important;\n' "$(pair bg)"
|
||||
printf ' --in-content-box-border-color: %s !important;\n' "$(pair gutter)"
|
||||
printf ' }\n'
|
||||
|
||||
printf '}\n'
|
||||
}
|
||||
|
||||
status_firefox="skipped"
|
||||
if [[ "$both_schemes_ok" == true && -d "$firefox_chrome" && -w "$firefox_chrome" ]]; then
|
||||
firefox_target="$firefox_chrome/panama-theme.css"
|
||||
if render_firefox >"$firefox_target.tmp" 2>/dev/null; then
|
||||
status_firefox="$(replace_if_changed "$firefox_target" "$firefox_target.tmp")"
|
||||
else
|
||||
rm -f "$firefox_target.tmp"
|
||||
status_firefox="failed"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── kitty ────────────────────────────────────────────────────────────────────
|
||||
# kitty.conf ends with `include current-theme.conf`; that file is machine state
|
||||
# and is rendered here rather than copied from a per-scheme file, so any theme
|
||||
# reaches the terminal rather than only the two Tokyo Nights.
|
||||
kitty_dir="$config_home/kitty"
|
||||
kitty_theme="$kitty_dir/current-theme.conf"
|
||||
|
||||
render_kitty() {
|
||||
printf '# Generated by Panama from the "%s" theme. Do not edit.\n' "${theme_id:-custom}"
|
||||
printf '#\n# kitty.conf includes this file; panama-theme-apps rewrites it on every theme\n'
|
||||
printf '# change and pushes the same colours to running terminals over the kitty\n'
|
||||
printf '# control socket. The tokyonight-* files beside it are shipped for anyone who\n'
|
||||
printf '# wants to include one directly, and are no longer copied here.\n\n'
|
||||
|
||||
printf 'foreground #%s\n' "$pal_fg"
|
||||
printf 'background #%s\n' "$pal_bg"
|
||||
# The classic terminal inversion: selected text wears the foreground as its
|
||||
# ground. Derived rather than named -- no palette token is "selection".
|
||||
printf 'selection_foreground #%s\n' "$pal_bg"
|
||||
printf 'selection_background #%s\n' "$pal_fg"
|
||||
printf 'cursor #%s\n' "$pal_fg"
|
||||
printf 'cursor_text_color #%s\n' "$pal_bg"
|
||||
printf 'url_color #%s\n\n' "$pal_teal"
|
||||
|
||||
# The focused border is the accent role; the unfocused one is the gutter,
|
||||
# the palette's neutral contrast surface.
|
||||
printf 'active_border_color #%s\n' "$theme_accent"
|
||||
printf 'inactive_border_color #%s\n\n' "$pal_gutter"
|
||||
|
||||
printf 'tab_bar_background #%s\n' "$pal_bg"
|
||||
printf 'active_tab_foreground #%s\n' "$pal_bgDark"
|
||||
printf 'active_tab_background #%s\n' "$theme_accent"
|
||||
printf 'inactive_tab_foreground #%s\n' "$pal_fgDim"
|
||||
printf 'inactive_tab_background #%s\n\n' "$pal_bgHighlight"
|
||||
|
||||
printf 'color0 #%s\n' "$ansi_black"
|
||||
printf 'color8 #%s\n' "$ansi_brightBlack"
|
||||
printf 'color1 #%s\n' "$ansi_red"
|
||||
printf 'color9 #%s\n' "$ansi_brightRed"
|
||||
printf 'color2 #%s\n' "$ansi_green"
|
||||
printf 'color10 #%s\n' "$ansi_brightGreen"
|
||||
printf 'color3 #%s\n' "$ansi_yellow"
|
||||
printf 'color11 #%s\n' "$ansi_brightYellow"
|
||||
printf 'color4 #%s\n' "$ansi_blue"
|
||||
printf 'color12 #%s\n' "$ansi_brightBlue"
|
||||
printf 'color5 #%s\n' "$ansi_magenta"
|
||||
printf 'color13 #%s\n' "$ansi_brightMagenta"
|
||||
printf 'color6 #%s\n' "$ansi_cyan"
|
||||
printf 'color14 #%s\n' "$ansi_brightCyan"
|
||||
printf 'color7 #%s\n' "$ansi_white"
|
||||
printf 'color15 #%s\n' "$ansi_brightWhite"
|
||||
# 16 and 17 are the extended slots tokyonight uses for orange and a deep
|
||||
# red; some tools (delta, bat) reference them.
|
||||
printf 'color16 #%s\n' "$pal_orange"
|
||||
printf 'color17 #%s\n' "$pal_redDeep"
|
||||
}
|
||||
|
||||
status_kitty="skipped"
|
||||
if [[ -r "$theme_file" ]]; then
|
||||
if [[ -d "$kitty_dir" ]] && [[ "$palette_ok" == true ]] && [[ "$ansi_ok" == true ]]; 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 \
|
||||
&& # Anything else on this machine that wants to know. Runs after every generator
|
||||
# above, so a hook sees the finished state rather than a half-applied one, and
|
||||
# a broken hook cannot cost you a theme change -- panama-hook reports and steps
|
||||
# over. See bin/panama-hook.
|
||||
"${PANAMA_PATH:-$HOME/.local/share/Panama}/bin/panama-hook" theme-set "$scheme" "$accent" || true
|
||||
|
||||
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"
|
||||
if render_kitty >"$kitty_theme.tmp" 2>/dev/null; then
|
||||
status_kitty="$(replace_if_changed "$kitty_theme" "$kitty_theme.tmp")"
|
||||
else
|
||||
rm -f "$kitty_dir/current-theme.conf.tmp"
|
||||
rm -f "$kitty_theme.tmp"
|
||||
status_kitty="failed"
|
||||
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
|
||||
if [[ "$status_kitty" != "failed" ]] \
|
||||
&& 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
|
||||
if kitty @ --to "unix:@${socket#@}" set-colors --all --configured "$kitty_theme" >/dev/null 2>&1 \
|
||||
&& kitty @ --to "unix:@${socket#@}" set-colors --override "active_border_color=#$theme_accent" >/dev/null 2>&1; then
|
||||
applied=$((applied + 1))
|
||||
fi
|
||||
done < <(ss -xl 2>/dev/null | grep -oE '@mykitty[^[:space:]]*' | sort -u)
|
||||
@@ -264,15 +851,31 @@ printf 'active_border_color #%s\n' "$accent_border_hex" >>"$kitty_dir/current-th
|
||||
fi
|
||||
fi
|
||||
|
||||
# Anything else on this machine that wants to know. Runs after every generator
|
||||
# above, so a hook sees the finished state rather than a half-applied one, and
|
||||
# a broken hook cannot cost you a theme change -- panama-hook reports and steps
|
||||
# over. See bin/panama-hook.
|
||||
#
|
||||
# It used to sit INSIDE the kitty branch's if-condition, which meant it fired
|
||||
# in the middle of writing kitty's theme file and not at all on a machine
|
||||
# without a kitty theme to write.
|
||||
"${PANAMA_PATH:-$HOME/.local/share/Panama}/bin/panama-hook" theme-set "$scheme" "$accent" || true
|
||||
|
||||
# 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 theme "${theme_id:-}" \
|
||||
--arg kitty "$status_kitty" \
|
||||
--arg gtk "$status_gtk" \
|
||||
--arg gtkcss "$status_gtkcss" \
|
||||
--arg btop "$status_btop" \
|
||||
--arg tmux "$status_tmux" \
|
||||
--arg hyprlock "$status_hyprlock" \
|
||||
--arg adwaita "$status_adwaita" \
|
||||
'{scheme: $scheme, kitty: $kitty, gtk: $gtk, btop: $btop, tmux: $tmux, hyprlock: $hyprlock, adwaita: $adwaita}'
|
||||
--arg vicinae "$status_vicinae" \
|
||||
--arg firefox "$status_firefox" \
|
||||
'{scheme: $scheme, theme: $theme, kitty: $kitty, gtk: $gtk, gtkcss: $gtkcss,
|
||||
btop: $btop, tmux: $tmux, hyprlock: $hyprlock, adwaita: $adwaita,
|
||||
vicinae: $vicinae, firefox: $firefox}'
|
||||
|
||||
Reference in New Issue
Block a user