#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' # Constants readonly DEFAULT_DIR="$HOME/.local/share/gnome-terminal-autotheme" readonly DESKTOP_FILE_NAME="gnome-terminal-autotheme.desktop" readonly AUTOSTART_DIR="$HOME/.config/autostart" readonly AUTOSTART_TARGET="$AUTOSTART_DIR/$DESKTOP_FILE_NAME" readonly SCRIPT_NAME="sync-terminal-theme.sh" readonly VERSION="1.1.0" # Colors for output readonly RED='\033[0;31m' readonly GREEN='\033[0;32m' readonly YELLOW='\033[0;33m' readonly BLUE='\033[0;34m' readonly NC='\033[0m' # No Color # Utility functions error() { echo -e "${RED}[ERROR]${NC} $1" >&2 } warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } info() { echo -e "${BLUE}[INFO]${NC} $1" } success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } check_installed() { command -v "$1" >/dev/null 2>&1 } stop_existing_processes() { local pids pids=$(pgrep -f "$SCRIPT_NAME" || true) if [[ -n "$pids" ]]; then info "Stopping existing theme sync processes..." for pid in $pids; do if kill -0 "$pid" 2>/dev/null; then kill "$pid" && info "Stopped process $pid" || warning "Failed to stop process $pid" fi done # Wait a moment for processes to terminate sleep 0.5 fi } validate_path() { local path="$1" [[ -z "$path" ]] && { error "Path cannot be empty"; return 1; } [[ "$path" == "/" ]] && { error "Cannot use root directory"; return 1; } [[ "$path" =~ ^/etc ]] && { error "System directories not allowed"; return 1; } return 0 } is_gnome() { [[ "$XDG_CURRENT_DESKTOP" =~ GNOME|gnome ]] || check_installed gnome-session } start_sync() { info "Running sync script now in the background..." nohup bash "$INSTALL_DIR/$SCRIPT_NAME" >/dev/null 2>&1 & disown success "Theme sync script running in background (PID: $!)" } check_integrity() { local install_dir="${1:-$DEFAULT_DIR}" info "Checking installation integrity in: $install_dir" local desktop_file="$AUTOSTART_TARGET" local target_exec if [[ ! -f "$desktop_file" ]]; then error "Autostart desktop entry not found: $desktop_file" return 1 fi # Extract Exec path from .desktop file target_exec=$(grep -oP '^Exec=\K.+' "$desktop_file" | head -n1) if [[ -z "$target_exec" ]]; then error "No Exec entry found in desktop file" return 1 fi if [[ ! -f "$target_exec" ]]; then error "Script file referenced in .desktop does not exist: $target_exec" return 1 fi if [[ ! -x "$target_exec" ]]; then error "Script file is not executable: $target_exec" return 1 fi if ! head -n 1 "$target_exec" | grep -qE '^#!.*/bash'; then error "Script does not start with a valid bash shebang: $target_exec" return 1 fi success "All basic integrity checks passed" } uninstall() { local install_dir="$1" local uninstall_choice="${2:-autostart}" local theme_choice="${3:-d}" info "Starting uninstallation..." # Kill running process stop_existing_processes # Remove autostart entry if [[ -f "$AUTOSTART_TARGET" ]]; then rm -f -- "$AUTOSTART_TARGET" && success "Autostart entry removed" || error "Failed to remove autostart entry" fi # Apply final theme case "${theme_choice,,}" in l|light) bash "$install_dir/$SCRIPT_NAME" light && success "Applied light theme" || error "Failed to apply light theme" ;; d|dark) bash "$install_dir/$SCRIPT_NAME" dark && success "Applied dark theme" || error "Failed to apply dark theme" ;; *) warning "No theme change applied" ;; esac # Remove installation directory if requested if [[ "$uninstall_choice" =~ ^whole$ ]]; then if [[ -d "$install_dir" ]]; then rm -rf -- "$install_dir" && success "Installation directory removed" || error "Failed to remove directory" else warning "Installation directory not found" fi fi success "Uninstallation complete" } install() { local reinstall="${1:-false}" local provided_path="${2:-}" info "Starting installation..." stop_existing_processes # Check for gnome-terminal if ! check_installed gnome-terminal; then warning "gnome-terminal not found" read -rp "Continue anyway? [y/N]: " proceed_terminal [[ "${proceed_terminal,,}" =~ ^y ]] || { error "Aborted"; exit 1; } fi # Check for GNOME if ! is_gnome; then warning "GNOME not detected" read -rp "Continue anyway? [y/N]: " proceed_gnome [[ "${proceed_gnome,,}" =~ ^y ]] || { error "Aborted"; exit 1; } fi # Use provided path or prompt user if [[ -n "$provided_path" ]]; then INSTALL_DIR=$(realpath -m "$provided_path") else read -rp "Enter install path [default: $DEFAULT_DIR]: " install_dir INSTALL_DIR=$(realpath -m "${install_dir:-$DEFAULT_DIR}") fi if ! validate_path "$INSTALL_DIR"; then error "Invalid install path: $INSTALL_DIR" exit 1 fi # Create directory mkdir -p -- "$INSTALL_DIR" || { error "Failed to create directory"; exit 1; } # Verify source files exist if [[ ! -f "$SCRIPT_NAME" || ! -f "$DESKTOP_FILE_NAME" ]]; then error "Missing required files: $SCRIPT_NAME and $DESKTOP_FILE_NAME must be in current directory" exit 1 fi # Copy files cp -- "$SCRIPT_NAME" "$INSTALL_DIR/" || { error "Failed to copy script"; exit 1; } cp -- "$DESKTOP_FILE_NAME" "$INSTALL_DIR/" || { error "Failed to copy desktop file"; exit 1; } # Make script executable chmod +x "$INSTALL_DIR/$SCRIPT_NAME" || { error "Failed to make script executable"; exit 1; } # Create autostart entry mkdir -p -- "$AUTOSTART_DIR" || { error "Failed to create autostart directory"; exit 1; } # Escape path for sed local escaped_path=$(printf '%q' "$INSTALL_DIR/$SCRIPT_NAME") sed "s|Exec=.*|Exec=$escaped_path|" "$INSTALL_DIR/$DESKTOP_FILE_NAME" > "$AUTOSTART_TARGET" || { error "Failed to create autostart entry" exit 1 } success "Installation complete" success "Script installed to: $INSTALL_DIR" success "Autostart entry created: $AUTOSTART_TARGET" # Start sync process start_sync } main() { echo -e "${BLUE}=== GNOME Terminal Auto Theme Sync v$VERSION ===${NC}" # Check if installed if [[ -f "$AUTOSTART_TARGET" ]]; then local old_install_dir=$(grep -oP 'Exec=\K.+' "$AUTOSTART_TARGET" | sed "s|/$SCRIPT_NAME||") if [[ -z "$old_install_dir" || ! -d "$old_install_dir" ]]; then warning "Could not determine valid install path from autostart entry (you may want to look into it)" info "Fallback to default installation directory" old_install_dir="$DEFAULT_DIR" if [[ ! -d "$old_install_dir" ]]; then error "Default install directory does not exist: $old_install_dir" exit 1 fi fi info "Theme watcher already installed at: $old_install_dir" read -rp "Do you want to (u)ninstall, (r)einstall or check (i)ntegrity? [u/r/i/N]: " choice case "${choice,,}" in u) # Loop until a valid choice is entered for uninstall while true; do read -rp "Uninstall (w)hole installation or (a)utostart entry? [w/a]: " uninstall_choice case "${uninstall_choice,,}" in a) uninstall_choice="autostart"; break ;; w) uninstall_choice="whole"; break ;; esac echo "Invalid choice, please enter 'w' for whole or 'a' for autostart." done # Loop until a valid choice is entered for theme selection while true; do read -rp "Set terminal theme to (l)ight or (d)ark after uninstall? [l/d]: " theme_choice case "${theme_choice,,}" in l) theme_choice="l"; break ;; d) theme_choice="d"; break ;; esac echo "Invalid choice, please enter 'l' for light or 'd' for dark." done uninstall "$old_install_dir" "$uninstall_choice" "$theme_choice" exit 0 ;; r) read -rp "Enter new install path [default: $DEFAULT_DIR]: " install_dir INSTALL_DIR="${install_dir:-$DEFAULT_DIR}" INSTALL_DIR=$(realpath -m "$INSTALL_DIR") if [[ "$INSTALL_DIR" != "$old_install_dir" ]]; then read -rp "The new install path is different from the old one ($old_install_dir). Remove old directory? [y/N]: " remove_old if [[ "${remove_old,,}" =~ ^y ]]; then if [[ -d "$old_install_dir" ]]; then rm -rf -- "$old_install_dir" && success "Old installation directory removed" || error "Failed to remove old directory" else warning "Old installation directory not found" fi fi fi install true "$INSTALL_DIR" ;; i) check_integrity "$old_install_dir" exit $? ;; *) info "No changes made" exit 0 ;; esac else install fi } main