commit 327959e0fac3290da8db6e24dab918aaf6c1ff86 Author: Mattia Mascarello Date: Wed Apr 16 16:47:59 2025 +0200 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..8d598a7 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# GNOME Terminal Auto Theme Sync + +![GNOME Terminal Logo](https://upload.wikimedia.org/wikipedia/commons/d/da/GNOME_Terminal_icon_2019.svg) + +Automatically sync your GNOME Terminal theme with your system's light/dark mode preferences. + +## Features + +- 🌓 Automatic theme switching between light and dark modes +- ⚡ Instant theme application when system preference changes +- 🔄 Background process that monitors system theme changes +- 🛠️ Easy installation and uninstallation + +## Installation + +1. **Download the project files**: + +```bash +git clone https://github.com/MatMasIt/gnome-terminal-autotheme.git +cd gnome-terminal-autotheme +``` + +2. **Run the installer**: + +```bash +./install.sh +``` + +3. **Follow the prompts** to complete installation. + +The installer will: +- Copy necessary files +- Set up autostart entry +- Start the theme sync service + +## Uninstallation + +To remove the auto theme sync, simply run the installer again. + +You'll be prompted to choose: +- Remove just the autostart entry or the whole installation +- Whether to keep your terminal in light or dark mode after uninstall + +## Requirements + +- GNOME Desktop Environment (tested on GNOME 40+) +- GNOME Terminal +- Bash 4.0+ + +## Configuration + +Advanced users can edit the theme definitions in: [sync-terminal-theme.sh](sync-terminal-theme.sh) + +## Contributing + +Contributions are welcome! Please open an issue or pull request. + +## License + +MIT License - See [LICENSE](LICENSE) file for details \ No newline at end of file diff --git a/gnome-terminal-autotheme.desktop b/gnome-terminal-autotheme.desktop new file mode 100644 index 0000000..743822d --- /dev/null +++ b/gnome-terminal-autotheme.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Application +Exec=PATH/sync-terminal-theme.sh +Hidden=false +NoDisplay=false +X-GNOME-Autostart-enabled=true +Name=GNOME Terminal Theme Sync diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..dc1f27c --- /dev/null +++ b/install.sh @@ -0,0 +1,305 @@ +#!/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 \ No newline at end of file diff --git a/sync-terminal-theme.sh b/sync-terminal-theme.sh new file mode 100755 index 0000000..de25bee --- /dev/null +++ b/sync-terminal-theme.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# Get the default profile UUID +PROFILE_ID=$(gsettings get org.gnome.Terminal.ProfilesList default | tr -d \') +PROFILE_PATH="/org/gnome/terminal/legacy/profiles:/:$PROFILE_ID" + +# Define themes +set_dark_theme() { + gsettings set org.gnome.Terminal.Legacy.Settings theme-variant 'dark' + dconf write $PROFILE_PATH/use-theme-colors false + dconf write $PROFILE_PATH/foreground-color "'#d4d4d4'" + dconf write $PROFILE_PATH/background-color "'#1e1e1e'" + dconf write $PROFILE_PATH/palette "['#1e1e1e', '#f44747', '#619955', '#ffaf00', '#85dfff', '#d27bff', '#4EC9B0', '#d4d4d4', '#808080', '#f44747', '#619955', '#ffaf00', '#85dfff', '#d27bff', '#4EC9B0', '#ffffff']" + dconf write $PROFILE_PATH/cursor-color "'#d4d4d4'" +} + +set_light_theme() { + gsettings set org.gnome.Terminal.Legacy.Settings theme-variant 'light' + dconf write $PROFILE_PATH/use-theme-colors false + dconf write $PROFILE_PATH/foreground-color "'#000000'" + dconf write $PROFILE_PATH/background-color "'#ffffff'" + dconf write $PROFILE_PATH/palette "['#000000', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#d3d7cf', '#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#eeeeec']" + dconf write $PROFILE_PATH/cursor-color "'#000000'" +} + +update_terminal_theme() { + SCHEME=$(gsettings get org.gnome.desktop.interface color-scheme) + + if [[ "$SCHEME" == *"dark"* ]]; then + set_dark_theme + else + set_light_theme + fi +} + +# Check for manual arguments (light or dark) +if [[ "${1:-}" == "light" ]]; then + set_light_theme + echo "[sync-script] Applied light theme manually." + exit 0 +elif [[ "${1:-}" == "dark" ]]; then + set_dark_theme + echo "[sync-script] Applied dark theme manually." + exit 0 +fi + +# Run once immediately to apply based on the current GNOME theme +update_terminal_theme + +# Monitor and auto-update GNOME theme changes +echo "[sync-script] Monitoring GNOME theme changes..." +gsettings monitor org.gnome.desktop.interface color-scheme | while read -r; do + update_terminal_theme +done