Dotfiles/apply-theme.sh

235 lines
8.9 KiB
Bash
Executable File

#!/bin/bash
# Apply theme colors from ~/.config/colors.conf to all deployed dotfile configs.
#
# Guards:
# - Refuses if any deployed config is symlinked back to ~/Dotfiles (old-style
# install), to prevent theme changes from propagating into the git repo.
# - When configs are copied into ~/.config (new-style install), overwrites
# those copies in-place.
#
# Tracks last-applied state in ~/.config/colors.state so only changed colors
# trigger replacements. Run again after re-install to re-apply your theme.
#
# Usage: ~/apply-theme.sh [/path/to/colors.conf]
set -euo pipefail
DOTFILES="${DOTFILES:-$HOME/Dotfiles}"
COLORS_CONF="${1:-$HOME/.config/colors.conf}"
STATE_FILE="$HOME/.config/colors.state"
# Hardcoded defaults — the values baked into every source file in the repo.
# Used to bootstrap colors.state on first run after a fresh install.
BOOT_TEXT="D6ABAB"
BOOT_BG="1A1A1A"
BOOT_HIGHLIGHT="E40046"
BOOT_DARK="5018DD"
BOOT_RED="F50505"
# "dotfiles_relative_path|absolute_deployed_path"
USER_FILES=(
"starship.toml|$HOME/.config/starship.toml"
"yazi/theme.toml|$HOME/.config/yazi/theme.toml"
"desktopenvs/hyprland/hypr/hyprland.conf|$HOME/.config/hypr/hyprland.conf"
"desktopenvs/hyprland/hypr/hyprtoolkit.conf|$HOME/.config/hypr/hyprtoolkit.conf"
"desktopenvs/hyprland/hypr/hyprlock.conf|$HOME/.config/hypr/hyprlock.conf"
"desktopenvs/hyprland/kitty/current-theme.conf|$HOME/.config/kitty/current-theme.conf"
"desktopenvs/hyprland/kitty/kitty.conf|$HOME/.config/kitty/kitty.conf"
"desktopenvs/hyprland/kitty/themes/cyberqueer.conf|$HOME/.config/kitty/themes/cyberqueer.conf"
"desktopenvs/hyprland/waybar/style.css|$HOME/.config/waybar/style.css"
"desktopenvs/hyprland/wofi/style.css|$HOME/.config/wofi/style.css"
"desktopenvs/hyprland/walker/themes/cyberqueer.css|$HOME/.config/walker/themes/cyberqueer.css"
"desktopenvs/hyprland/nwg-dock-hyprland/style.css|$HOME/.config/nwg-dock-hyprland/style.css"
"desktopenvs/hyprland/nwg-drawer/drawer.css|$HOME/.config/nwg-drawer/drawer.css"
"desktopenvs/hyprland/nwg-panel/menu-start.css|$HOME/.config/nwg-panel/menu-start.css"
"desktopenvs/hyprland/vicinae/cyberqueer.toml|$HOME/.config/vicinae/cyberqueer.toml"
"desktopenvs/hyprland/scripts/onscreenkb.sh|$HOME/.config/scripts/onscreenkb.sh"
"desktopenvs/hyprland/spicetify/Themes/cli-cyberqueer/color.ini|$HOME/.config/spicetify/Themes/cli-cyberqueer/color.ini"
"desktopenvs/hyprland/spicetify/Themes/matte-cyberqueer/color.ini|$HOME/.config/spicetify/Themes/matte-cyberqueer/color.ini"
"desktopenvs/hyprland/ulauncher/user-themes/cyberqueer/manifest.json|$HOME/.config/ulauncher/user-themes/cyberqueer/manifest.json"
"desktopenvs/hyprland/ulauncher/user-themes/cyberqueer/theme.css|$HOME/.config/ulauncher/user-themes/cyberqueer/theme.css"
"desktopenvs/hyprland/ulauncher/user-themes/cyberqueer/generated.css|$HOME/.config/ulauncher/user-themes/cyberqueer/generated.css"
"desktopenvs/hyprland/Vencord/themes/cyberqueer.theme.css|$HOME/.config/Vencord/themes/cyberqueer.theme.css"
"desktopenvs/hyprland/Vencord/themes/system24/theme/flavors/cyberqueer.theme.css|$HOME/.config/Vencord/themes/system24/theme/flavors/cyberqueer.theme.css"
"qt-themes/cyberqueer/style.qss|$HOME/.config/qt6ct/qss/style.qss"
"qt-themes/cyberqueer/cyberqueer.conf|$HOME/.config/qt6ct/colors/cyberqueer.conf"
"qt-themes/cyberqueer/style-plugin/CyberQueerStyle.cpp|$DOTFILES/qt-themes/cyberqueer/style-plugin/CyberQueerStyle.cpp"
"qt-themes/cyberqueer/hyprpolkitagent-qt.conf|$HOME/.config/systemd/user/hyprpolkitagent.service.d/qt-theme.conf"
)
# System-owned paths — sed is run via sudo
SYS_FILES=(
"etc-ly-config.ini|/etc/ly/config.ini"
"gtk-themes/cyberqueer/gtk-3.0/gtk.css|/usr/share/themes/cyberqueer/gtk-3.0/gtk.css"
"gtk-themes/cyberqueer/gtk-4.0/gtk.css|/usr/share/themes/cyberqueer/gtk-4.0/gtk.css"
)
# ---------------------------------------------------------------------------
die() { echo "error: $*" >&2; exit 1; }
parse_colors() {
local file="$1"
[[ -f "$file" ]] || die "not found: $file"
while IFS='=' read -r key val; do
key="${key%%[[:space:]]*}"
[[ "$key" =~ ^[[:space:]]*# || -z "$key" ]] && continue
val="${val%%#*}"
val="${val//[[:space:]]/}"
val="${val^^}"
printf '%s=%s\n' "$key" "$val"
done < "$file"
}
# Returns 0 if any path component of $1 is a symlink pointing into DOTFILES
links_into_dotfiles() {
local path="$1"
while [[ "${#path}" -gt 1 ]]; do
if [[ -L "$path" ]]; then
local target
target=$(readlink -f "$path" 2>/dev/null) || break
[[ "$target" == "$DOTFILES"/* || "$target" == "$DOTFILES" ]] && return 0
fi
[[ "$path" == "$HOME" ]] && break
path=$(dirname "$path")
done
return 1
}
# Run sed -i with optional command prefix (e.g. "sudo").
# Called as: do_replace <file> [sudo]
do_replace() {
local file="$1"
shift # remaining "$@" is either empty or "sudo"
for key in "${CHANGED_KEYS[@]}"; do
local old="${OLD[$key]:-}"
local new="${NEW[$key]}"
[[ -z "$old" || "$old" == "$new" ]] && continue
"$@" sed -i "s/${old}/${new}/gI" "$file"
done
}
# ---------------------------------------------------------------------------
# Bootstrap state on fresh install (deployed files still have default colors)
if [[ ! -f "$STATE_FILE" ]]; then
cat > "$STATE_FILE" <<EOF
COLOR_TEXT=$BOOT_TEXT
COLOR_BG=$BOOT_BG
COLOR_HIGHLIGHT=$BOOT_HIGHLIGHT
COLOR_DARK=$BOOT_DARK
COLOR_RED=$BOOT_RED
EOF
echo "Created $STATE_FILE with default CyberQueer colors"
fi
# ---------------------------------------------------------------------------
# Symlink guard — refuse if any deployed path traces back to the git repo
SYMLINKED=()
for entry in "${USER_FILES[@]}"; do
deployed="${entry#*|}"
[[ -e "$deployed" ]] || continue
links_into_dotfiles "$deployed" && SYMLINKED+=("$deployed")
done
if [[ ${#SYMLINKED[@]} -gt 0 ]]; then
echo "error: the following deployed configs are symlinked back to $DOTFILES:"
printf ' %s\n' "${SYMLINKED[@]}"
cat <<'EOF'
Modifying these would write theme changes directly into the git repo.
Re-install using setup/modules/Desktop-Enviroments/hyprland-new.sh,
which copies configs instead of symlinking them.
To convert a single directory manually:
cp -rL ~/.config/hypr /tmp/hypr && rm -rf ~/.config/hypr && mv /tmp/hypr ~/.config/hypr
EOF
exit 1
fi
# ---------------------------------------------------------------------------
# Load and diff colors
declare -A OLD NEW
while IFS='=' read -r k v; do OLD[$k]="$v"; done < <(parse_colors "$STATE_FILE")
while IFS='=' read -r k v; do NEW[$k]="$v"; done < <(parse_colors "$COLORS_CONF")
CHANGED_KEYS=()
for key in "${!NEW[@]}"; do
[[ "${OLD[$key]:-}" != "${NEW[$key]}" ]] && CHANGED_KEYS+=("$key")
done
if [[ ${#CHANGED_KEYS[@]} -eq 0 ]]; then
echo "No color changes — nothing to do."
exit 0
fi
echo "Changes to apply:"
for key in "${CHANGED_KEYS[@]}"; do
printf ' %-20s #%s → #%s\n' "$key" "${OLD[$key]:-?}" "${NEW[$key]}"
done
echo
# ---------------------------------------------------------------------------
# Apply to user files
u_ok=0; u_skip=0
for entry in "${USER_FILES[@]}"; do
deployed="${entry#*|}"
if [[ ! -f "$deployed" ]]; then
u_skip=$((u_skip + 1))
continue
fi
do_replace "$deployed"
u_ok=$((u_ok + 1))
done
# ---------------------------------------------------------------------------
# Apply to system files (sudo)
s_ok=0; s_skip=0
for entry in "${SYS_FILES[@]}"; do
deployed="${entry#*|}"
if [[ ! -f "$deployed" ]]; then
s_skip=$((s_skip + 1))
continue
fi
if do_replace "$deployed" sudo; then
s_ok=$((s_ok + 1))
else
echo " SKIP (sudo failed): $deployed"
s_skip=$((s_skip + 1))
fi
done
# ---------------------------------------------------------------------------
# Reload services that read their config once at start
POLKIT_DROP="$HOME/.config/systemd/user/hyprpolkitagent.service.d/qt-theme.conf"
if [[ -f "$POLKIT_DROP" ]] && systemctl --user is-active --quiet hyprpolkitagent 2>/dev/null; then
systemctl --user daemon-reload
systemctl --user restart hyprpolkitagent
echo "Restarted hyprpolkitagent with updated colors."
fi
# Rebuild Qt style plugin if its source was updated
PLUGIN_BUILD="$DOTFILES/qt-themes/cyberqueer/style-plugin/build.sh"
if [[ -f "$PLUGIN_BUILD" ]]; then
if bash "$PLUGIN_BUILD"; then
echo "Rebuilt cyberqueer Qt style plugin."
else
echo "Warning: Qt style plugin rebuild failed — relaunch apps will still use old colors."
fi
fi
# ---------------------------------------------------------------------------
# Persist state
cp "$COLORS_CONF" "$STATE_FILE"
echo "User files: $u_ok updated, $u_skip not installed."
[[ $((s_ok + s_skip)) -gt 0 ]] && echo "System files: $s_ok updated, $s_skip not installed."
echo "State saved → $STATE_FILE"