.local-bin/scripts/update_app_theme.sh
2026-02-09 13:52:33 -05:00

283 lines
9 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
# Simple theme switcher for WezTerm integration and other apps.
# Usage: theme-switcher [dark|light|toggle]
# --- Configuration & Initialization ---
CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}"
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}"
CACHE_FILE="$CACHE_DIR/current_theme"
mkdir -p "$CACHE_DIR" 2>/dev/null || true
# --- Helper Functions ---
# Resolve symlinks to actual files
resolve_symlink() {
local file="$1"
[[ ! -L "$file" ]] && echo "$file" && return
# Use 'realpath' if available for reliability
if command -v realpath >/dev/null 2>&1; then
realpath "$file"
# Fallback for macOS and others
elif [[ "$OSTYPE" == "darwin"* ]]; then
local target
target=$(readlink "$file")
[[ "$target" != /* ]] && target="$(dirname "$file")/$target"
# Recurse if the link target is also a link
if [[ -L "$target" ]]; then
resolve_symlink "$target"
else
echo "$target"
fi
# General fallback for Linux that might not have 'readlink -f'
else
readlink -f "$file" 2>/dev/null || echo "$file"
fi
}
# Detect system theme (macOS/Gnome)
detect_theme() {
if [[ "$OSTYPE" == "darwin"* ]]; then
# Check for AppleInterfaceStyle: Dark or Light
defaults read -g AppleInterfaceStyle 2>/dev/null | grep -qi "Dark" && echo "dark" || echo "light"
elif command -v gsettings >/dev/null 2>&1; then
# Check Gnome color-scheme
gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null | grep -qi 'dark' && echo "dark" || echo "light"
else
# Default to dark on unknown systems/environments
echo "dark"
fi
}
# Get current mode (cached or detected)
get_mode() {
if [[ -f "$CACHE_FILE" ]]; then
local cached
# Read file, trim whitespace
cached=$(<"$CACHE_FILE")
cached="${cached//[[:space:]]/}"
# Validate content before returning
[[ "$cached" =~ ^(dark|light)$ ]] && echo "$cached" && return
fi
detect_theme
}
# Update a config file using a regex pattern
update_config() {
local config_file="$1"
local pattern_regex="$2" # The pattern to search for (e.g., 'key = ".*"')
local dark_val="$3" # The replacement string for dark mode
local light_val="$4" # The replacement string for light mode
[[ ! -f "$config_file" ]] && return 0
local mode="${TARGET_MODE:-$(get_mode)}"
local replacement="$dark_val"
[[ "$mode" != "dark" ]] && replacement="$light_val"
local target_file
target_file=$(resolve_symlink "$config_file")
# Check if the target file exists and is writable
[[ ! -f "$target_file" || ! -w "$target_file" ]] && return 0
# Check if the pattern exists in the file before trying to replace
# NOTE: Using 'grep -q' with the search pattern regex to verify existence
if grep -qE "$pattern_regex" "$target_file" 2>/dev/null; then
# Use a safe delimiter (e.g., '#') for sed to avoid conflicts with paths/values
local sed_command="s|${pattern_regex}|${replacement}|g"
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS sed requires an empty string for the backup extension
sed -i '' "$sed_command" "$target_file" 2>/dev/null
else
# Linux sed (GNU sed)
sed -i "$sed_command" "$target_file" 2>/dev/null
fi
# Only echo success if sed was run and likely succeeded
echo "${config_file##*/}"
fi
}
# Update zoxide FZF colors in shell config
update_zoxide() {
command -v zoxide >/dev/null 2>&1 || return 0
local mode="${TARGET_MODE:-$(get_mode)}"
local shell_configs=()
# Detect all standard shell config files
[[ -f "$HOME/.zshrc" ]] && shell_configs+=("$HOME/.zshrc")
[[ -f "$HOME/.bashrc" ]] && shell_configs+=("$HOME/.bashrc")
[[ -f "$HOME/.bash_profile" ]] && shell_configs+=("$HOME/.bash_profile")
[[ ${#shell_configs[@]} -eq 0 ]] && return 0
# Define FZF opts based on mode (quotes are tricky, use a function for safety)
local fzf_opts
if [[ "$mode" == "dark" ]]; then
# Corrected for easier reading/maintenance
fzf_opts="--height 40% --layout=reverse --border --preview-window=down:3:wrap --color=fg:#f8f8f2,bg:#2a2f3b,hl:#f92672,fg+:#f8f8f2,bg+:#49483e,hl+:#f92672,info:#a6e22e,prompt:#66d9ef,pointer:#f92672,marker:#a6e22e,spinner:#a6e22e,header:#66d9ef"
else
fzf_opts="--height 40% --layout=reverse --border --preview-window=down:3:wrap --color=fg:#586e75,bg:#fdf6e3,hl:#268bd2,fg+:#073642,bg+:#eee8d5,hl+:#268bd2,info:#859900,prompt:#268bd2,pointer:#cb4b16,marker:#859900,spinner:#859900,header:#268bd2"
fi
# The replacement line to insert/update
local export_line="export _ZO_FZF_OPTS=\"$fzf_opts\""
for shell_config in "${shell_configs[@]}"; do
# 1. Update existing line (handles potential existing spacing/quotes)
local pattern_regex="^export[[:space:]]+_ZO_FZF_OPTS=.*"
if grep -qE "$pattern_regex" "$shell_config" 2>/dev/null; then
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "s|${pattern_regex}|${export_line}|" "$shell_config"
else
sed -i "s|${pattern_regex}|${export_line}|" "$shell_config"
fi
# 2. Add new line (prefer insertion before zoxide init for functionality)
elif grep -q "zoxide init" "$shell_config" 2>/dev/null; then
# Use sed to insert the line before the first 'zoxide init'
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS sed is tricky; this might not work perfectly on all versions
sed -i '' "/zoxide init/i\\
${export_line}" "$shell_config"
else
# GNU sed: /pattern/i insert_text
sed -i "/zoxide init/i\\$export_line" "$shell_config"
fi
# 3. Fallback: Append to the end
else
echo "" >>"$shell_config"
echo "# Zoxide FZF theme colors (managed by theme-switcher)" >>"$shell_config"
echo "$export_line" >>"$shell_config"
fi
echo "✓ zoxide (${shell_config##*/})"
done
}
# Find the btop themes path (more robust)
get_btop_path() {
local btop_path=""
# 1. Standard XDG Data Home (most common for user installs)
if [[ -d "${XDG_DATA_HOME:-$HOME/.local/share}/btop/themes" ]]; then
btop_path="${XDG_DATA_HOME:-$HOME/.local/share}/btop/themes"
# 2. Homebrew on macOS (Cellar path is complex/versioned)
elif [[ "$OSTYPE" == "darwin"* ]]; then
local cellar_path
# Use 'find' starting from common Homebrew locations, looking for 'btop/themes'
cellar_path=$(find /opt/homebrew/Cellar/ /usr/local/Cellar/ -type d -name "btop" -prune -exec find {} -type d -name themes \; 2>/dev/null | head -1)
[[ -n "$cellar_path" ]] && btop_path="$cellar_path"
# 3. Standard system installs (Debian/RPM/etc)
elif [[ -d "/usr/share/btop/themes" ]]; then
btop_path="/usr/share/btop/themes"
elif [[ -d "/usr/local/share/btop/themes" ]]; then
btop_path="/usr/local/share/btop/themes"
fi
echo "$btop_path"
}
# --- Main Logic ---
main() {
local mode="${TARGET_MODE:-$(get_mode)}"
# Cache the mode
echo "$mode" >"$CACHE_FILE" 2>/dev/null || true
echo "Switching to $mode mode..."
# Update configs in parallel for speed
# 1. Starship
{
update_config \
"$CONFIG_DIR/starship.toml" \
'palette = ".*"' \
'palette = "monokai_pro"' \
'palette = "solarized_light"'
} &
# 2. Btop
{
local btop_path
btop_path=$(get_btop_path)
if [[ -n "$btop_path" ]]; then
# Ensure the theme is referenced only by its name in the replacement, not the full path,
# as btop.conf should only contain the name unless the theme is custom/outside its path.
# However, since you are referencing files outside the config dir, we keep the path structure.
update_config \
"$CONFIG_DIR/btop/btop.conf" \
'color_theme = ".*"' \
"color_theme = \"monokai.theme\"" \
"color_theme = \"solarized_light.theme\""
else
echo "✗ btop (Could not determine Btop themes path)"
fi
} &
# 3. AeroSpace (macOS only)
{
if [[ "$OSTYPE" == "darwin"* ]]; then
# Pattern: matches the full exec-and-forget line
local pattern_regex_aerospace="exec-and-forget borders active_color=0x[0-9a-fA-F]{8} inactive_color=0x[0-9a-fA-F]{8} width=[0-9.]+"
# Replacement: The exact, correct command with colors for dark/light
local dark_replacement="exec-and-forget borders active_color=0xfff5f5f5 inactive_color=0xff2a2f3b width=5.0"
local light_replacement="exec-and-forget borders active_color=0xff268BD2 inactive_color=0xff93A1A1 width=5.0"
update_config \
"$CONFIG_DIR/aerospace/aerospace.toml" \
"$pattern_regex_aerospace" \
"$dark_replacement" \
"$light_replacement"
# Immediately apply the borders command via 'aerospace' binary (requires it to be in PATH)
if command -v aerospace >/dev/null 2>&1; then
if [[ "$mode" == "light" ]]; then
aerospace "$light_replacement" 2>/dev/null || true
else
aerospace "$dark_replacement" 2>/dev/null || true
fi
echo "✓ AeroSpace (Borders applied)"
else
echo "✓ AeroSpace (Borders updated in config)"
fi
fi
} &
# 4. Zoxide/FZF
{
update_zoxide
} &
# Wait for all background jobs to finish
wait
echo "Done!"
echo ""
echo "Note: Restart your shell or run 'source ~/.zshrc' (or ~/.bashrc) to apply zoxide colors."
}
# --- Argument Parsing ---
case "${1:-}" in
"dark" | "light")
export TARGET_MODE="$1"
;;
"toggle")
current=$(get_mode)
export TARGET_MODE
TARGET_MODE=$([[ "$current" == "dark" ]] && echo "light" || echo "dark")
;;
*)
# No args or invalid - auto-detect
;;
esac
# Execute main function
main