#!/usr/bin/env bash # Propagates the colour 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 # # 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. set -euo pipefail scheme="${1:-dark}" case "$scheme" in dark|light) ;; *) printf 'usage: panama-theme-apps [dark|light]\n' >&2; exit 2 ;; esac # ── 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. if cp "$theme_file" "$kitty_dir/current-theme.conf.tmp" 2>/dev/null \ && mv "$kitty_dir/current-theme.conf.tmp" "$kitty_dir/current-theme.conf" 2>/dev/null; then status_kitty="written" 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; 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 printf '{"scheme":"%s","kitty":"%s"}\n' "$scheme" "$status_kitty"