283 lines
9 KiB
Bash
Executable file
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
|