aboutsummaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/copy_menu.sh82
-rw-r--r--scripts/lib_apps.sh116
-rw-r--r--scripts/lib_backup.sh72
-rw-r--r--scripts/lib_copy.sh362
-rw-r--r--scripts/lib_detect.sh49
-rw-r--r--scripts/lib_prompts.sh249
-rw-r--r--scripts/lib_update.sh84
7 files changed, 1014 insertions, 0 deletions
diff --git a/scripts/copy_menu.sh b/scripts/copy_menu.sh
new file mode 100755
index 00000000..87f9301f
--- /dev/null
+++ b/scripts/copy_menu.sh
@@ -0,0 +1,82 @@
+#!/usr/bin/env bash
+
+# show_copy_menu
+# Arguments:
+# $1 - express_supported flag (1 if available, 0 otherwise)
+# Sets global COPY_MENU_CHOICE to one of: install, upgrade, express, quit
+show_copy_menu() {
+ local express_supported="${1:-0}"
+ local menu_title=" KooL's Hyprland Dotfiles "
+ local prompt="Select what you would like to do:"
+
+ local install_tag="Install"
+ local upgrade_tag="Upgrade"
+ local express_tag="Express"
+ local update_tag="Update"
+ local quit_tag="Quit"
+
+ local install_desc="Fresh copy"
+ local upgrade_desc="Backups + prompts"
+ local express_desc="Skips restores & wallpapers"
+ local update_desc="Stash + git pull"
+ local quit_desc="Exit without changes"
+
+ local choice=""
+ run_basic_menu() {
+ while true; do
+ printf "\n%s\n" "$menu_title"
+ printf "%s\n" "$prompt"
+ printf " 1) Install - %s\n" "$install_desc"
+ printf " 2) Upgrade - %s\n" "$upgrade_desc"
+ if [ "$express_supported" -eq 1 ]; then
+ printf " 3) Express - %s\n" "$express_desc"
+ else
+ printf " 3) Express - %s (disabled)\n" "$express_desc"
+ fi
+ printf " 4) Update - %s\n" "$update_desc"
+ printf " 5) Quit - %s\n" "$quit_desc"
+ printf "Enter choice [1-5]: "
+ read -r text_choice
+ case "$text_choice" in
+ 1) choice="$install_tag"; break ;;
+ 2) choice="$upgrade_tag"; break ;;
+ 3)
+ if [ "$express_supported" -eq 1 ]; then
+ choice="$express_tag"
+ break
+ else
+ echo "Express is disabled on this system."
+ fi
+ ;;
+ 4) choice="$update_tag"; break ;;
+ 5) choice="$quit_tag"; break ;;
+ *) echo "Invalid selection. Please choose 1-5." ;;
+ esac
+ done
+ }
+
+ if [ "$COPY_TUI_BACKEND" = "basic" ]; then
+ run_basic_menu
+ COPY_MENU_CHOICE="$choice"
+ return 0
+ fi
+
+ # Fallback to whiptail if present
+ if command -v whiptail >/dev/null 2>&1; then
+ if ! choice=$(whiptail --title "$menu_title" --menu "$prompt" 17 60 8 \
+ "$install_tag" "$install_desc" \
+ "$upgrade_tag" "$upgrade_desc" \
+ "$express_tag" "$express_desc" \
+ "$update_tag" "$update_desc" \
+ "$quit_tag" "$quit_desc" 3>&1 1>&2 2>&3); then
+ COPY_MENU_CHOICE="quit"
+ return 1
+ fi
+ else
+ # Plain-text fallback
+ run_basic_menu
+ fi
+
+ # shellcheck disable=SC2034 # used by parent script after sourcing this file
+ COPY_MENU_CHOICE="$choice"
+}
diff --git a/scripts/lib_apps.sh b/scripts/lib_apps.sh
new file mode 100644
index 00000000..562e5c5b
--- /dev/null
+++ b/scripts/lib_apps.sh
@@ -0,0 +1,116 @@
+#!/usr/bin/env bash
+# App enablement and editor selection helpers.
+
+enable_asusctl() {
+ local log="$1"
+ if command -v asusctl >/dev/null 2>&1; then
+ local OVERLAY_SA="config/hypr/configs/Startup_Apps.conf"
+ mkdir -p "$(dirname "$OVERLAY_SA")"
+ touch "$OVERLAY_SA"
+ grep -qx 'exec-once = rog-control-center' "$OVERLAY_SA" || echo 'exec-once = rog-control-center' >>"$OVERLAY_SA"
+ fi
+}
+
+enable_blueman() {
+ local log="$1"
+ if command -v blueman-applet >/dev/null 2>&1; then
+ local OVERLAY_SA="config/hypr/configs/Startup_Apps.conf"
+ mkdir -p "$(dirname "$OVERLAY_SA")"
+ touch "$OVERLAY_SA"
+ grep -qx 'exec-once = blueman-applet' "$OVERLAY_SA" || echo 'exec-once = blueman-applet' >>"$OVERLAY_SA"
+ fi
+}
+
+enable_ags() {
+ local log="$1"
+ if command -v ags >/dev/null 2>&1; then
+ echo "${INFO:-[INFO]} AGS detected - enabling in startup and refresh scripts" 2>&1 | tee -a "$log"
+ local OVERLAY_SA="config/hypr/configs/Startup_Apps.conf"
+ mkdir -p "$(dirname "$OVERLAY_SA")"
+ touch "$OVERLAY_SA"
+ grep -qx 'exec-once = ags' "$OVERLAY_SA" || echo 'exec-once = ags' >>"$OVERLAY_SA"
+ sed -i '/#ags -q && ags &/s/^#//' config/hypr/scripts/RefreshNoWaybar.sh
+ sed -i '/#ags -q && ags &/s/^#//' config/hypr/scripts/Refresh.sh
+ fi
+}
+
+enable_quickshell() {
+ local log="$1"
+ if command -v qs >/dev/null 2>&1; then
+ echo "${INFO:-[INFO]} Quickshell detected - enabling in startup and refresh scripts" 2>&1 | tee -a "$log"
+ local OVERLAY_SA="config/hypr/configs/Startup_Apps.conf"
+ mkdir -p "$(dirname "$OVERLAY_SA")"
+ touch "$OVERLAY_SA"
+ grep -qx 'exec-once = qs' "$OVERLAY_SA" || echo 'exec-once = qs' >>"$OVERLAY_SA"
+ sed -i '/#pkill qs && qs &/s/^#//' config/hypr/scripts/RefreshNoWaybar.sh
+ sed -i '/#pkill qs && qs &/s/^#//' config/hypr/scripts/Refresh.sh
+ fi
+}
+
+ensure_keybinds_init() {
+ local log="$1"
+ local OVERLAY_SA="config/hypr/configs/Startup_Apps.conf"
+ mkdir -p "$(dirname "$OVERLAY_SA")"
+ if ! grep -qx 'exec-once = \$scriptsDir/KeybindsLayoutInit.sh' "$OVERLAY_SA"; then
+ echo 'exec-once = $scriptsDir/KeybindsLayoutInit.sh' >>"$OVERLAY_SA"
+ echo "${INFO:-[INFO]} Added KeybindsLayoutInit.sh to user Startup_Apps overlay" 2>&1 | tee -a "$log"
+ fi
+}
+
+install_terminal_configs() {
+ local log="$1"
+
+ # Ghostty
+ local GHOSTTY_SRC="config/ghostty/ghostty.config"
+ local GHOSTTY_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/ghostty"
+ local GHOSTTY_DEST="$GHOSTTY_DIR/config"
+ if [ -f "$GHOSTTY_SRC" ]; then
+ mkdir -p "$GHOSTTY_DIR"
+ install -m 0644 "$GHOSTTY_SRC" "$GHOSTTY_DEST" 2>&1 | tee -a "$log"
+ if [ -f "$GHOSTTY_DIR/wallust.conf" ]; then
+ sed -i -E 's/^(\\s*palette\\s*=\\s*)([0-9]{1,2}):/\\1\\2=/' "$GHOSTTY_DIR/wallust.conf" 2>&1 | tee -a "$log" || true
+ fi
+ else
+ echo "${ERROR:-[ERROR]} - $GHOSTTY_SRC not found; skipping Ghostty config install." 2>&1 | tee -a "$log"
+ fi
+
+ # WezTerm
+ local WEZTERM_SRC="config/wezterm/wezterm.lua"
+ local WEZTERM_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/wezterm"
+ local WEZTERM_DEST="$WEZTERM_DIR/wezterm.lua"
+ if [ -f "$WEZTERM_SRC" ]; then
+ mkdir -p "$WEZTERM_DIR"
+ install -m 0644 "$WEZTERM_SRC" "$WEZTERM_DEST" 2>&1 | tee -a "$log"
+ else
+ echo "${ERROR:-[ERROR]} - $WEZTERM_SRC not found; skipping WezTerm config install." 2>&1 | tee -a "$log"
+ fi
+}
+
+choose_default_editor() {
+ local log="$1"
+ local editor_set=0
+ update_editor() {
+ local editor=$1
+ sed -i "s/#env = EDITOR,.*/env = EDITOR,$editor #default editor/" config/hypr/UserConfigs/01-UserDefaults.conf
+ echo "${OK:-[OK]} Default editor set to ${MAGENTA:-}$editor${RESET:-}." 2>&1 | tee -a "$log"
+ }
+ if command -v nvim &>/dev/null; then
+ printf "${INFO:-[INFO]} ${MAGENTA:-}neovim${RESET:-} is detected as installed\n"
+ if ! read -r -p "${CAT:-[ACTION]} Do you want to make ${MAGENTA:-}neovim${RESET:-} the default editor? (y/N): " EDITOR_CHOICE </dev/tty; then
+ :
+ elif [[ "$EDITOR_CHOICE" == "y" || "$EDITOR_CHOICE" == "Y" ]]; then
+ update_editor "nvim"
+ editor_set=1
+ fi
+ fi
+ printf "\n"
+ if [[ "$editor_set" -eq 0 ]] && command -v vim &>/dev/null; then
+ printf "${INFO:-[INFO]} ${MAGENTA:-}vim${RESET:-} is detected as installed\n"
+ if read -r -p "${CAT:-[ACTION]} Do you want to make ${MAGENTA:-}vim${RESET:-} the default editor? (y/N): " EDITOR_CHOICE </dev/tty; then
+ if [[ "$EDITOR_CHOICE" == "y" || "$EDITOR_CHOICE" == "Y" ]]; then
+ update_editor "vim"
+ editor_set=1
+ fi
+ fi
+ fi
+}
diff --git a/scripts/lib_backup.sh b/scripts/lib_backup.sh
new file mode 100644
index 00000000..6867fb6d
--- /dev/null
+++ b/scripts/lib_backup.sh
@@ -0,0 +1,72 @@
+#!/usr/bin/env bash
+# Backup helper utilities shared by copy.sh (and future scripts).
+
+# Create a unique backup directory name with month, day, hours, and minutes.
+get_backup_dirname() {
+ echo "back-up_$(date +"%m%d_%H%M")"
+}
+
+# Move a directory to a timestamped backup alongside the original.
+# Usage: backup_dir "/path/to/dir" [logfile]
+backup_dir() {
+ local dir="$1"
+ local log="${2:-/dev/null}"
+ local backup_suffix
+
+ [ -z "$dir" ] && return 1
+ backup_suffix=$(get_backup_dirname)
+ mv "$dir" "${dir}-backup-${backup_suffix}" 2>&1 | tee -a "$log"
+}
+
+# Cleanup old backups under ~/.config, keeping the newest for each base dir.
+# mode: "auto" (no prompts) or "prompt" (asks before delete); log optional.
+cleanup_backups() {
+ local mode="${1:-prompt}"
+ local log="${2:-/dev/null}"
+ local CONFIG_DIR="$HOME/.config"
+ local BACKUP_PREFIX="-backup"
+
+ for DIR in "$CONFIG_DIR"/*; do
+ [ -d "$DIR" ] || continue
+ local BACKUP_DIRS=()
+
+ for BACKUP in "$DIR"$BACKUP_PREFIX*; do
+ [ -d "$BACKUP" ] && BACKUP_DIRS+=("$BACKUP")
+ done
+
+ [ ${#BACKUP_DIRS[@]} -gt 1 ] || continue
+
+ # Determine latest backup by mtime
+ local latest_backup="${BACKUP_DIRS[0]}"
+ for BACKUP in "${BACKUP_DIRS[@]}"; do
+ [ "$BACKUP" -nt "$latest_backup" ] && latest_backup="$BACKUP"
+ done
+
+ if [ "$mode" = "auto" ]; then
+ for BACKUP in "${BACKUP_DIRS[@]}"; do
+ if [ "$BACKUP" != "$latest_backup" ]; then
+ rm -rf "$BACKUP"
+ fi
+ done
+ echo "${INFO:-[INFO]} Express mode: trimmed backups for ${YELLOW:-}${DIR##*/}${RESET:-}, keeping ${MAGENTA:-}${latest_backup##*/}${RESET:-}." 2>&1 | tee -a "$log"
+ continue
+ fi
+
+ printf "\n%s Found multiple backups for: %s\n" "${INFO:-[INFO]}" "${DIR##*/}"
+ echo "${YELLOW:-}Backups:${RESET:-}"
+ for BACKUP in "${BACKUP_DIRS[@]}"; do
+ echo " - ${BACKUP##*/}"
+ done
+ echo -n "${CAT:-[ACTION]} Delete older backups and keep only the latest? (y/N): "
+ read back_choice
+ if [[ "$back_choice" == [Yy]* ]]; then
+ for BACKUP in "${BACKUP_DIRS[@]}"; do
+ if [ "$BACKUP" != "$latest_backup" ]; then
+ rm -rf "$BACKUP"
+ echo "Deleted: ${BACKUP##*/}"
+ fi
+ done
+ echo "Kept: ${latest_backup##*/}"
+ fi
+ done
+}
diff --git a/scripts/lib_copy.sh b/scripts/lib_copy.sh
new file mode 100644
index 00000000..fa1231c5
--- /dev/null
+++ b/scripts/lib_copy.sh
@@ -0,0 +1,362 @@
+#!/usr/bin/env bash
+# Copy helpers split into phases to keep copy.sh lean.
+
+copy_phase1() {
+ local log="$1"
+ local dirs="fastfetch kitty rofi swaync"
+ for DIR2 in $dirs; do
+ local DIRPATH="$HOME/.config/$DIR2"
+ if [ -d "$DIRPATH" ]; then
+ while true; do
+ printf "\n${INFO:-[INFO]} Found ${YELLOW:-}$DIR2${RESET:-} config found in ~/.config/\n"
+ echo -n "${CAT:-[ACTION]} Do you want to replace ${YELLOW:-}$DIR2${RESET:-} config? (y/n): "
+ read DIR1_CHOICE
+ case "$DIR1_CHOICE" in
+ [Yy]*) BACKUP_DIR=$(get_backup_dirname)
+ mv "$DIRPATH" "$DIRPATH-backup-$BACKUP_DIR" 2>&1 | tee -a "$log"
+ echo -e "${NOTE:-[NOTE]} - Backed up $DIR2 to $DIRPATH-backup-$BACKUP_DIR." 2>&1 | tee -a "$log"
+ cp -r "config/$DIR2" "$HOME/.config/$DIR2" 2>&1 | tee -a "$log"
+ echo -e "${OK:-[OK]} - Replaced $DIR2 with new configuration." 2>&1 | tee -a "$log"
+ if [ "$DIR2" = "rofi" ]; then
+ if [ -d "$DIRPATH-backup-$BACKUP_DIR/themes" ]; then
+ for file in "$DIRPATH-backup-$BACKUP_DIR/themes"/*; do
+ [ -e "$file" ] || continue
+ cp -n "$file" "$HOME/.config/rofi/themes/" >>"$log" 2>&1 || true
+ done || true
+ fi
+ if [ -f "$DIRPATH-backup-$BACKUP_DIR/0-shared-fonts.rasi" ]; then
+ cp "$DIRPATH-backup-$BACKUP_DIR/0-shared-fonts.rasi" "$HOME/.config/rofi/0-shared-fonts.rasi" >>"$log" 2>&1
+ fi
+ fi
+ break ;;
+ [Nn]*) echo -e "${NOTE:-[NOTE]} - Skipping ${YELLOW:-}$DIR2${RESET:-}" 2>&1 | tee -a "$log"; break ;;
+ *) echo -e "${WARN:-[WARN]} - Invalid choice. Please enter Y or N." ;;
+ esac
+ done
+ else
+ cp -r "config/$DIR2" "$HOME/.config/$DIR2" 2>&1 | tee -a "$log"
+ echo -e "${OK:-[OK]} - Copy completed for ${YELLOW:-}$DIR2${RESET:-}" 2>&1 | tee -a "$log"
+ fi
+ done
+}
+
+copy_waybar() {
+ local log="$1"
+ local DIRW="waybar"
+ local DIRPATHw="$HOME/.config/$DIRW"
+ if [ -d "$DIRPATHw" ]; then
+ while true; do
+ echo -n "${CAT:-[ACTION]} Do you want to replace ${YELLOW:-}$DIRW${RESET:-} config? (y/n): "
+ read DIR1_CHOICE
+ case "$DIR1_CHOICE" in
+ [Yy]*) BACKUP_DIR=$(get_backup_dirname)
+ cp -r "$DIRPATHw" "$DIRPATHw-backup-$BACKUP_DIR" 2>&1 | tee -a "$log"
+ echo -e "${NOTE:-[NOTE]} - Backed up $DIRW to $DIRPATHw-backup-$BACKUP_DIR." 2>&1 | tee -a "$log"
+ rm -rf "$DIRPATHw" && cp -r "config/$DIRW" "$DIRPATHw" 2>&1 | tee -a "$log"
+ for file in "config" "style.css"; do
+ symlink="$DIRPATHw-backup-$BACKUP_DIR/$file"
+ target_file="$DIRPATHw/$file"
+ if [ -L "$symlink" ]; then
+ symlink_target=$(readlink "$symlink")
+ if [ -f "$symlink_target" ]; then
+ rm -f "$target_file" && cp -f "$symlink_target" "$target_file"
+ fi
+ fi
+ done
+ for dir in "$DIRPATHw-backup-$BACKUP_DIR/configs"/*; do
+ [ -e "$dir" ] || continue
+ if [ -d "$dir" ]; then
+ target_dir="$HOME/.config/waybar/configs/$(basename "$dir")"
+ [ -d "$target_dir" ] || cp -r "$dir" "$HOME/.config/waybar/configs/"
+ fi
+ done
+ for file in "$DIRPATHw-backup-$BACKUP_DIR/configs"/*; do
+ [ -e "$file" ] || continue
+ target_file="$HOME/.config/waybar/configs/$(basename "$file")"
+ [ -e "$target_file" ] || cp "$file" "$HOME/.config/waybar/configs/"
+ done || true
+ for file in "$DIRPATHw-backup-$BACKUP_DIR/style"/*; do
+ [ -e "$file" ] || continue
+ if [ -d "$file" ]; then
+ target_dir="$HOME/.config/waybar/style/$(basename "$file")"
+ [ -d "$target_dir" ] || cp -r "$file" "$HOME/.config/waybar/style/"
+ else
+ target_file="$HOME/.config/waybar/style/$(basename "$file")"
+ [ -e "$target_file" ] || cp "$file" "$HOME/.config/waybar/style/"
+ fi
+ done || true
+ BACKUP_FILEw="$DIRPATHw-backup-$BACKUP_DIR/UserModules"
+ [ -f "$BACKUP_FILEw" ] && cp -f "$BACKUP_FILEw" "$DIRPATHw/UserModules"
+ break ;;
+ [Nn]*) echo -e "${NOTE:-[NOTE]} - Skipping ${YELLOW:-}$DIRW${RESET:-} config replacement." 2>&1 | tee -a "$log"; break ;;
+ *) echo -e "${WARN:-[WARN]} - Invalid choice. Please enter Y or N." ;;
+ esac
+ done
+ else
+ cp -r "config/$DIRW" "$DIRPATHw" 2>&1 | tee -a "$log"
+ echo -e "${OK:-[OK]} - Copy completed for ${YELLOW:-}$DIRW${RESET:-}" 2>&1 | tee -a "$log"
+ fi
+}
+
+copy_phase2() {
+ local log="$1"
+ local DIR="btop cava hypr Kvantum qt5ct qt6ct swappy wallust wlogout"
+ for DIR_NAME in $DIR; do
+ local DIRPATH="$HOME/.config/$DIR_NAME"
+ if [ -d "$DIRPATH" ]; then
+ echo -e "\n${NOTE:-[NOTE]} - Config for ${YELLOW:-}$DIR_NAME${RESET:-} found, attempting to back up."
+ BACKUP_DIR=$(get_backup_dirname)
+ mv "$DIRPATH" "$DIRPATH-backup-$BACKUP_DIR" 2>&1 | tee -a "$log"
+ fi
+ if [ -d "config/$DIR_NAME" ]; then
+ cp -r "config/$DIR_NAME/" "$HOME/.config/$DIR_NAME" 2>&1 | tee -a "$log"
+ echo "${OK:-[OK]} - Copy of config for ${YELLOW:-}$DIR_NAME${RESET:-} completed!" 2>&1 | tee -a "$log"
+ else
+ echo "${ERROR:-[ERROR]} - Directory config/$DIR_NAME does not exist to copy." 2>&1 | tee -a "$log"
+ fi
+ done
+ install_terminal_configs "$log"
+}
+
+# Restore Animations and Monitor Profiles plus key hypr files from backup
+restore_hypr_assets() {
+ local log="$1"
+ local express_mode="$2"
+
+ local HYPR_DIR="$HOME/.config/hypr"
+ local BACKUP_DIR
+ BACKUP_DIR=$(get_backup_dirname)
+ local BACKUP_HYPR_PATH="$HYPR_DIR-backup-$BACKUP_DIR"
+
+ if [ -d "$BACKUP_HYPR_PATH" ]; then
+ if [ "$express_mode" -eq 1 ]; then
+ echo "${NOTE:-[NOTE]} Express mode: skipping automatic restoration of animations and monitor profiles." 2>&1 | tee -a "$log"
+ return
+ fi
+
+ echo -e "\n${NOTE:-[NOTE]} Restoring ${SKY_BLUE:-}Animations & Monitor Profiles${RESET:-} into ${YELLOW:-}$HYPR_DIR${RESET:-}..."
+
+ local DIR_B=("Monitor_Profiles" "animations" "wallpaper_effects")
+ for DIR_RESTORE in "${DIR_B[@]}"; do
+ local BACKUP_SUBDIR="$BACKUP_HYPR_PATH/$DIR_RESTORE"
+ if [ -d "$BACKUP_SUBDIR" ]; then
+ cp -r "$BACKUP_SUBDIR" "$HYPR_DIR/" 2>&1 | tee -a "$log"
+ echo "${OK:-[OK]} - Restored directory: ${MAGENTA:-}$DIR_RESTORE${RESET:-}" 2>&1 | tee -a "$log"
+ fi
+ done
+
+ local FILE_B=("monitors.conf" "workspaces.conf")
+ for FILE_RESTORE in "${FILE_B[@]}"; do
+ local BACKUP_FILE="$BACKUP_HYPR_PATH/$FILE_RESTORE"
+ if [ -f "$BACKUP_FILE" ]; then
+ cp "$BACKUP_FILE" "$HYPR_DIR/$FILE_RESTORE" 2>&1 | tee -a "$log"
+ echo "${OK:-[OK]} - Restored file: ${MAGENTA:-}$FILE_RESTORE${RESET:-}" 2>&1 | tee -a "$log"
+ fi
+ done
+ fi
+}
+
+# Helper to extract overlay additions/disables from previous user file vs base
+compose_overlay_from_backup() {
+ local type="$1" # startup|windowrules
+ local base_file="$2"
+ local old_user_file="$3"
+ local new_user_file="$4"
+ local disable_file="$5"
+
+ mkdir -p "$(dirname "$new_user_file")"
+ : >"$new_user_file"
+ : >"$disable_file"
+
+ if [ "$type" = "startup" ]; then
+ grep -E '^\s*exec-once\s*=' "$old_user_file" | sed -E 's/^\s+//;s/\s+$//' | sort -u >"$old_user_file.tmp.exec"
+ grep -E '^\s*exec-once\s*=' "$base_file" | sed -E 's/^\s+//;s/\s+$//' | sort -u >"$base_file.tmp.exec"
+ comm -23 "$old_user_file.tmp.exec" "$base_file.tmp.exec" >"$new_user_file"
+ grep -E '^\s*#\s*exec-once\s*=' "$old_user_file" |
+ sed -E 's/^\s*#\s*exec-once\s*=\s*//' |
+ sed -E 's/^\s+//;s/\s+$//' |
+ grep -Ev '^\$scriptsDir/KeybindsLayoutInit\.sh$' |
+ sort -u >"$disable_file"
+ rm -f "$old_user_file.tmp.exec" "$base_file.tmp.exec"
+ elif [ "$type" = "windowrules" ]; then
+ grep -E '^(windowrule|layerrule)\s*=' "$old_user_file" | sed -E 's/^\s+//;s/\s+$//' | sort -u >"$old_user_file.tmp.rules"
+ grep -E '^(windowrule|layerrule)\s*=' "$base_file" | sed -E 's/^\s+//;s/\s+$//' | sort -u >"$base_file.tmp.rules"
+ comm -23 "$old_user_file.tmp.rules" "$base_file.tmp.rules" >"$new_user_file"
+ grep -E '^\s*#\s*(windowrule|layerrule)\s*=' "$old_user_file" | sed -E 's/^\s*#\s*//' | sed -E 's/^\s+//;s/\s+$//' | sort -u >"$disable_file"
+ rm -f "$old_user_file.tmp.rules" "$base_file.tmp.rules"
+ fi
+}
+
+restore_user_configs() {
+ local log="$1"
+ local express_mode="$2"
+
+ local DIRPATH="$HOME/.config/hypr"
+ local BACKUP_DIR
+ BACKUP_DIR=$(get_backup_dirname)
+ local BACKUP_DIR_PATH="$DIRPATH-backup-$BACKUP_DIR/UserConfigs"
+
+ if [ -z "$BACKUP_DIR" ]; then
+ echo "${ERROR:-[ERROR]} - Backup directory name is empty. Exiting." 2>&1 | tee -a "$log"
+ exit 1
+ fi
+
+ if [ -d "$BACKUP_DIR_PATH" ] && [ "$express_mode" -eq 1 ]; then
+ echo "${NOTE:-[NOTE]} Express mode: skipping UserConfigs restoration prompts." 2>&1 | tee -a "$log"
+ return
+ fi
+
+ if [ -d "$BACKUP_DIR_PATH" ] && [ "$express_mode" -eq 0 ]; then
+ local VERSION_FILE
+ VERSION_FILE=$(find "$DIRPATH" -maxdepth 1 -name "v*.*.*" | head -n 1)
+ local CURRENT_VERSION="999.9.9"
+ if [ -n "$VERSION_FILE" ]; then
+ CURRENT_VERSION=$(basename "$VERSION_FILE" | sed 's/^v//')
+ fi
+
+ local TARGET_VERSION="2.3.19"
+
+ echo -e "${NOTE:-[NOTE]} Restoring previous ${MAGENTA:-}User-Configs${RESET:-}... " 2>&1 | tee -a "$log"
+ printf "${WARNING:-}\
+ █▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█\n\
+ NOTES for RESTORING PREVIOUS CONFIGS\n\
+ █▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█\n\n\
+ The 'UserConfigs' directory is for all your personal settings.\n\
+ Files in this directory will override the default configurations,\n\
+ so your customizations are not lost when you update.\n\
+" >&2
+
+ if version_gte "$CURRENT_VERSION" "$TARGET_VERSION"; then
+ read -r -p "${CAT:-[ACTION]} Do you want to restore your previous UserConfigs directory? (Y/n): " restore_userconfigs_dir
+ if [[ "$restore_userconfigs_dir" != [Nn]* ]]; then
+ echo "${NOTE:-[NOTE]} Restoring UserConfigs directory..." 2>&1 | tee -a "$log"
+ rsync -a "$BACKUP_DIR_PATH/" "$DIRPATH/UserConfigs/" 2>&1 | tee -a "$log"
+ echo "${OK:-[OK]} - UserConfigs directory restored." 2>&1 | tee -a "$log"
+ else
+ echo "${NOTE:-[NOTE]} - Skipped restoring UserConfigs." 2>&1 | tee -a "$log"
+ fi
+ else
+ echo -e "${NOTE:-[NOTE]} Detected version ${YELLOW:-}v$CURRENT_VERSION${RESET:-} (older than v$TARGET_VERSION). Using legacy restoration mode." 2>&1 | tee -a "$log"
+
+ local FILES_TO_RESTORE=(
+ "01-UserDefaults.conf"
+ "ENVariables.conf"
+ "LaptopDisplay.conf"
+ "Laptops.conf"
+ "Startup_Apps.conf"
+ "UserDecorations.conf"
+ "UserAnimations.conf"
+ "UserKeybinds.conf"
+ "UserSettings.conf"
+ "WindowRules.conf"
+ )
+
+ for FILE_NAME in "${FILES_TO_RESTORE[@]}"; do
+ local BACKUP_FILE="$BACKUP_DIR_PATH/$FILE_NAME"
+ if [ -f "$BACKUP_FILE" ]; then
+ if [ "$FILE_NAME" = "Startup_Apps.conf" ]; then
+ compose_overlay_from_backup "startup" "$DIRPATH/configs/Startup_Apps.conf" "$BACKUP_FILE" "$DIRPATH/UserConfigs/Startup_Apps.conf" "$DIRPATH/UserConfigs/Startup_Apps.disable"
+ echo "${OK:-[OK]} - Migrated overlay for ${YELLOW:-}$FILE_NAME${RESET:-}" 2>&1 | tee -a "$log"
+ continue
+ fi
+ if [ "$FILE_NAME" = "WindowRules.conf" ]; then
+ compose_overlay_from_backup "windowrules" "$DIRPATH/configs/WindowRules.conf" "$BACKUP_FILE" "$DIRPATH/UserConfigs/WindowRules.conf" "$DIRPATH/UserConfigs/WindowRules.disable"
+ echo "${OK:-[OK]} - Migrated overlay for ${YELLOW:-}$FILE_NAME${RESET:-}" 2>&1 | tee -a "$log"
+ continue
+ fi
+
+ printf "\n${INFO:-[INFO]} Found ${YELLOW:-}$FILE_NAME${RESET:-} in hypr backup...\n"
+ read -r -p "${CAT:-[ACTION]} Do you want to restore ${YELLOW:-}$FILE_NAME${RESET:-} from backup? (Y/n): " file_restore
+
+ if [[ "$file_restore" != [Nn]* ]]; then
+ if cp "$BACKUP_FILE" "$DIRPATH/UserConfigs/$FILE_NAME"; then
+ echo "${OK:-[OK]} - $FILE_NAME restored!" 2>&1 | tee -a "$log"
+ else
+ echo "${ERROR:-[ERROR]} - Failed to restore $FILE_NAME!" 2>&1 | tee -a "$log"
+ fi
+ else
+ echo "${NOTE:-[NOTE]} - Skipped restoring $FILE_NAME." 2>&1 | tee -a "$log"
+ fi
+ fi
+ done
+ fi
+ fi
+}
+
+restore_user_scripts() {
+ local log="$1"
+ local express_mode="$2"
+
+ local DIRSHPATH="$HOME/.config/hypr"
+ local BACKUP_DIR
+ BACKUP_DIR=$(get_backup_dirname)
+ local BACKUP_DIR_PATH_S="$DIRSHPATH-backup-$BACKUP_DIR/UserScripts"
+ local SCRIPTS_TO_RESTORE=("RofiBeats.sh" "Weather.py" "Weather.sh")
+
+ if [ -d "$BACKUP_DIR_PATH_S" ] && [ "$express_mode" -eq 1 ]; then
+ echo "${NOTE:-[NOTE]} Express mode: skipping UserScripts restoration prompts." 2>&1 | tee -a "$log"
+ return
+ fi
+
+ if [ -d "$BACKUP_DIR_PATH_S" ] && [ "$express_mode" -eq 0 ]; then
+ echo -e "${NOTE:-[NOTE]} Restoring previous ${MAGENTA:-}User-Scripts${RESET:-}..." 2>&1 | tee -a "$log"
+
+ for SCRIPT_NAME in "${SCRIPTS_TO_RESTORE[@]}"; do
+ local BACKUP_SCRIPT="$BACKUP_DIR_PATH_S/$SCRIPT_NAME"
+ if [ -f "$BACKUP_SCRIPT" ]; then
+ printf "\n${INFO:-[INFO]} Found ${YELLOW:-}$SCRIPT_NAME${RESET:-} in hypr backup...\n"
+ read -r -p "${CAT:-[ACTION]} Do you want to restore ${YELLOW:-}$SCRIPT_NAME${RESET:-} from backup? (y/N): " script_restore
+
+ if [[ "$script_restore" == [Yy]* ]]; then
+ if cp "$BACKUP_SCRIPT" "$DIRSHPATH/UserScripts/$SCRIPT_NAME"; then
+ echo "${OK:-[OK]} - $SCRIPT_NAME restored!" 2>&1 | tee -a "$log"
+ else
+ echo "${ERROR:-[ERROR]} - Failed to restore $SCRIPT_NAME!" 2>&1 | tee -a "$log"
+ fi
+ else
+ echo "${NOTE:-[NOTE]} - Skipped restoring $SCRIPT_NAME." 2>&1 | tee -a "$log"
+ fi
+ fi
+ done
+ fi
+}
+
+restore_hypr_files() {
+ local log="$1"
+ local express_mode="$2"
+
+ local DIRPATH="$HOME/.config/hypr"
+ local BACKUP_DIR
+ BACKUP_DIR=$(get_backup_dirname)
+ local BACKUP_DIR_PATH_F="$DIRPATH-backup-$BACKUP_DIR"
+ local FILES_2_RESTORE=("hyprlock.conf" "hypridle.conf")
+
+ if [ -d "$BACKUP_DIR_PATH_F" ] && [ "$express_mode" -eq 1 ]; then
+ echo "${NOTE:-[NOTE]} Express mode: skipping individual hypr file restoration prompts." 2>&1 | tee -a "$log"
+ return
+ fi
+
+ if [ -d "$BACKUP_DIR_PATH_F" ] && [ "$express_mode" -eq 0 ]; then
+ echo -e "${NOTE:-[NOTE]} Restoring some files in ${MAGENTA:-}$HOME/.config/hypr directory${RESET:-}..." 2>&1 | tee -a "$log"
+
+ for FILE_RESTORE in "${FILES_2_RESTORE[@]}"; do
+ local BACKUP_FILE="$BACKUP_DIR_PATH_F/$FILE_RESTORE"
+ if [ -f "$BACKUP_FILE" ]; then
+ echo -e "\n${INFO:-[INFO]} Found ${YELLOW:-}$FILE_RESTORE${RESET:-} in hypr backup..."
+ read -r -p "${CAT:-[ACTION]} Do you want to restore ${YELLOW:-}$FILE_RESTORE${RESET:-} from backup? (y/N): " file2restore
+
+ if [[ "$file2restore" == [Yy]* ]]; then
+ if cp "$BACKUP_FILE" "$DIRPATH/$FILE_RESTORE"; then
+ echo "${OK:-[OK]} - $FILE_RESTORE restored!" 2>&1 | tee -a "$log"
+ else
+ echo "${ERROR:-[ERROR]} - Failed to restore $FILE_RESTORE!" 2>&1 | tee -a "$log"
+ fi
+ else
+ echo "${NOTE:-[NOTE]} - Skipped restoring $FILE_RESTORE." 2>&1 | tee -a "$log"
+ fi
+ else
+ echo "${ERROR:-[ERROR]} - Backup file $BACKUP_FILE does not exist." 2>&1 | tee -a "$log"
+ fi
+ done
+ fi
+}
diff --git a/scripts/lib_detect.sh b/scripts/lib_detect.sh
new file mode 100644
index 00000000..5cb26c1b
--- /dev/null
+++ b/scripts/lib_detect.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+# Detection and environment adjustment helpers shared by copy.sh.
+
+# Nvidia tweaks: uncomments envs and adjusts hardware cursor setting.
+detect_nvidia_adjust() {
+ local log="$1"
+ if lspci -k | grep -A 2 -E "(VGA|3D)" | grep -iq nvidia; then
+ echo "${INFO:-[INFO]} Nvidia GPU detected. Setting up proper env's and configs" 2>&1 | tee -a "$log" || true
+ sed -i '/env = LIBVA_DRIVER_NAME,nvidia/s/^#//' config/hypr/configs/ENVariables.conf
+ sed -i '/env = __GLX_VENDOR_LIBRARY_NAME,nvidia/s/^#//' config/hypr/configs/ENVariables.conf
+ sed -i '/env = NVD_BACKEND,direct/s/^#//' config/hypr/configs/ENVariables.conf
+ sed -i '/env = GSK_RENDERER,ngl/s/^#//' config/hypr/configs/ENVariables.conf
+ sed -i 's/^\([[:space:]]*no_hardware_cursors[[:space:]]*=[[:space:]]*\)2/\1 1/' config/hypr/configs/SystemSettings.conf
+ fi
+}
+
+# VM tweaks: enable software renderer envs and virtual monitor defaults.
+detect_vm_adjust() {
+ local log="$1"
+ if hostnamectl | grep -q 'Chassis: vm'; then
+ echo "${INFO:-[INFO]} System is running in a virtual machine. Setting up proper env's and configs" 2>&1 | tee -a "$log" || true
+ sed -i 's/^\([[:space:]]*no_hardware_cursors[[:space:]]*=[[:space:]]*\)2/\1 1/' config/hypr/configs/SystemSettings.conf
+ sed -i '/env = WLR_RENDERER_ALLOW_SOFTWARE,1/s/^#//' config/hypr/configs/ENVariables.conf
+ sed -i '/monitor = Virtual-1, 1920x1080@60,auto,1/s/^#//' config/hypr/monitors.conf
+ fi
+}
+
+# NixOS tweaks: ensure polkit overlay is enabled and default disabled.
+detect_nixos_adjust() {
+ local log="$1"
+ if hostnamectl | grep -q 'Operating System: NixOS'; then
+ echo "${INFO:-[INFO]} NixOS Distro Detected. Setting up proper env's and configs." 2>&1 | tee -a "$log" || true
+ local OVERLAY_SA="config/hypr/configs/Startup_Apps.conf"
+ local DISABLE_SA="config/hypr/configs/Startup_Apps.disable"
+ mkdir -p "$(dirname "$OVERLAY_SA")"
+ touch "$OVERLAY_SA" "$DISABLE_SA"
+ grep -qx 'exec-once = $scriptsDir/Polkit-NixOS.sh' "$OVERLAY_SA" || echo 'exec-once = $scriptsDir/Polkit-NixOS.sh' >>"$OVERLAY_SA"
+ grep -qx '\$scriptsDir/Polkit.sh' "$DISABLE_SA" || echo '$scriptsDir/Polkit.sh' >>"$DISABLE_SA"
+ fi
+}
+
+# Decide waybar config/style based on chassis type. Echoes chosen config path.
+detect_waybar_config() {
+ if hostnamectl | grep -q 'Chassis: desktop'; then
+ echo "desktop"
+ else
+ echo "laptop"
+ fi
+}
diff --git a/scripts/lib_prompts.sh b/scripts/lib_prompts.sh
new file mode 100644
index 00000000..bf6fafd7
--- /dev/null
+++ b/scripts/lib_prompts.sh
@@ -0,0 +1,249 @@
+#!/usr/bin/env bash
+# User interaction helpers extracted from copy.sh. Each helper echoes state or sets
+# globals deliberately to minimize side effects.
+
+# Detect keyboard layout via localectl or setxkbmap.
+prompt_detect_layout() {
+ if command -v localectl >/dev/null 2>&1; then
+ local layout
+ layout=$(localectl status --no-pager | awk '/X11 Layout/ {print $3}')
+ [ -n "$layout" ] && { echo "$layout"; return; }
+ fi
+ if command -v setxkbmap >/dev/null 2>&1; then
+ local layout
+ layout=$(setxkbmap -query | awk '/layout/ {print $2}')
+ [ -n "$layout" ] && { echo "$layout"; return; }
+ fi
+ echo "(unset)"
+}
+
+# Confirm or set keyboard layout; writes to SystemSettings.conf.
+prompt_keyboard_layout() {
+ local layout="$1"
+ local log="$2"
+
+ if [ "$layout" = "(unset)" ]; then
+ while true; do
+ printf "\n%.0s" {1..1}
+ print_color $WARNING "\n █▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
+ STOP AND READ
+ █▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█
+
+ !!! IMPORTANT WARNING !!!
+
+The Default Keyboard Layout could not be detected
+You need to set it Manually
+
+ !!! WARNING !!!
+
+Setting a wrong Keyboard Layout will cause Hyprland to crash
+If you are not sure, just type ${YELLOW}us${RESET}
+${SKYBLUE}You can change later in ~/.config/hypr/UserConfigs/UserSettings.conf${RESET}
+
+${MAGENTA} NOTE:${RESET}
+• You can also set more than 2 keyboard layouts
+• For example: ${YELLOW}us, kr, gb, ru${RESET}
+"
+ printf "\n%.0s" {1..1}
+
+ echo -n "${CAT} - Please enter the correct keyboard layout: "
+ read new_layout
+
+ if [ -n "$new_layout" ]; then
+ layout="$new_layout"
+ break
+ else
+ echo "${CAT} Please enter a keyboard layout."
+ fi
+ done
+ fi
+
+ printf "${NOTE} Detecting keyboard layout to prepare proper Hyprland Settings\n"
+ while true; do
+ printf "${INFO} Current keyboard layout is ${MAGENTA}$layout${RESET}\n"
+ echo -n "${CAT} Is this correct? [y/n] "
+ read keyboard_layout
+ case $keyboard_layout in
+ [yY])
+ awk -v layout="$layout" '/kb_layout/ {$0 = " kb_layout = " layout} 1' config/hypr/configs/SystemSettings.conf >temp.conf
+ mv temp.conf config/hypr/configs/SystemSettings.conf
+ echo "${NOTE} kb_layout ${MAGENTA}$layout${RESET} configured in settings." 2>&1 | tee -a "$log"
+ break
+ ;;
+ [nN])
+ printf "\n%.0s" {1..2}
+ print_color $WARNING "
+ █▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
+ STOP AND READ
+ █▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█
+
+ !!! IMPORTANT WARNING !!!
+
+The Default Keyboard Layout could not be detected
+You need to set it Manually
+
+ !!! WARNING !!!
+
+Setting a wrong Keyboard Layout will cause Hyprland to crash
+If you are not sure, just type ${YELLOW}us${RESET}
+${SKYBLUE}You can change later in ~/.config/hypr/UserConfigs/UserSettings.conf${RESET}
+
+${MAGENTA} NOTE:${RESET}
+• You can also set more than 2 keyboard layouts
+• For example: ${YELLOW}us, kr, gb, ru${RESET}
+"
+ printf "\n%.0s" {1..1}
+ echo -n "${CAT} - Please enter the correct keyboard layout: "
+ read new_layout
+ awk -v new_layout="$new_layout" '/kb_layout/ {$0 = " kb_layout = " new_layout} 1' config/hypr/configs/SystemSettings.conf >temp.conf
+ mv temp.conf config/hypr/configs/SystemSettings.conf
+ echo "${OK} kb_layout $new_layout configured in settings." 2>&1 | tee -a "$log"
+ break
+ ;;
+ *)
+ echo "${ERROR} Please enter either 'y' or 'n'."
+ ;;
+ esac
+ done
+}
+
+# Prompt for resolution choice; echoes "< 1440p" or "≥ 1440p".
+prompt_resolution_choice() {
+ local choice
+ while true; do
+ echo "${INFO:-[INFO]} Select monitor resolution for scaling:"
+ echo " 1) < 1440p (lower DPI; smaller displays)"
+ echo " 2) ≥ 1440p (default; 1440p/2k/4k)"
+
+ if ! read -r -p "${CAT} Enter the number of your choice (1 or 2): " choice </dev/tty; then
+ echo "${ERROR} Unable to read input (tty unavailable)."
+ continue
+ fi
+ echo "${INFO:-[INFO]} You entered: '$choice'"
+ case "$choice" in
+ 1) echo "< 1440p"; return ;;
+ 2) echo "≥ 1440p"; return ;;
+ *) echo "${ERROR} Invalid choice. Please enter 1 for < 1440p or 2 for ≥ 1440p." ;;
+ esac
+ done
+}
+
+# Prompt for 12H clock; sets waybar/hyprlock/SDDM changes when accepted.
+prompt_clock_12h() {
+ local log="$1"
+ while true; do
+ echo -e "${NOTE} ${SKY_BLUE} By default, KooL's Dots are configured in 24H clock format."
+ echo -n "$CAT Do you want to change to 12H (AM/PM) clock format? (y/n): "
+ read answer
+ answer=$(echo "$answer" | tr '[:upper:]' '[:lower:]')
+ if [[ "$answer" == "y" ]]; then
+ # waybar clocks
+ sed -i 's#^\(\s*\)//\("format": " {:%I:%M %p}",\) #\1\2 #g' config/waybar/Modules 2>&1 | tee -a "$log"
+ sed -i 's#^\(\s*\)\("format": " {:%H:%M:%S}",\) #\1//\2#g' config/waybar/Modules 2>&1 | tee -a "$log"
+ sed -i 's#^\(\s*\)\("format": " {:%H:%M}",\) #\1//\2#g' config/waybar/Modules 2>&1 | tee -a "$log"
+ sed -i 's#^\(\s*\)//\("format": "{:%I:%M %p - %d/%b}",\) #\1\2#g' config/waybar/Modules 2>&1 | tee -a "$log"
+ sed -i 's#^\(\s*\)\("format": "{:%H:%M - %d/%b}",\) #\1//\2#g' config/waybar/Modules 2>&1 | tee -a "$log"
+ sed -i 's#^\(\s*\)//\("format": "{:%B | %a %d, %Y | %I:%M %p}",\) #\1\2#g' config/waybar/Modules 2>&1 | tee -a "$log"
+ sed -i 's#^\(\s*\)\("format": "{:%B | %a %d, %Y | %H:%M}",\) #\1//\2#g' config/waybar/Modules 2>&1 | tee -a "$log"
+ sed -i 's#^\(\s*\)//\("format": "{:%A, %I:%M %P}",\) #\1\2#g' config/waybar/Modules 2>&1 | tee -a "$log"
+ sed -i 's#^\(\s*\)\("format": "{:%a %d | %H:%M}",\) #\1//\2#g' config/waybar/Modules 2>&1 | tee -a "$log"
+
+ # hyprlock
+ local HYPRLOCK_FILE="config/hypr/hyprlock.conf"
+ if [ ! -f "$HYPRLOCK_FILE" ] && [ -f "config/hypr/hyprlock-1080p.conf" ]; then
+ HYPRLOCK_FILE="config/hypr/hyprlock-1080p.conf"
+ fi
+ if [ -f "$HYPRLOCK_FILE" ]; then
+ sed -i 's/^\s*text = cmd\[update:1000\] echo \"\$(date +\"%H\")\"/# &/' "$HYPRLOCK_FILE" 2>&1 | tee -a "$log"
+ sed -i 's/^\(\s*\)# *text = cmd\[update:1000\] echo \"\$(date +\"%I\")\" #AM\/PM/\1 text = cmd\[update:1000\] echo \"\$(date +\"%I\")\" #AM\/PM/' "$HYPRLOCK_FILE" 2>&1 | tee -a "$log"
+ sed -i 's/^\s*text = cmd\[update:1000\] echo \"\$(date +\"%S\")\"/# &/' "$HYPRLOCK_FILE" 2>&1 | tee -a "$log"
+ sed -i 's/^\(\s*\)# *text = cmd\[update:1000\] echo \"\$(date +\"%S %p\")\" #AM\/PM/\1 text = cmd\[update:1000\] echo \"\$(date +\"%S %p\")\" #AM\/PM/' "$HYPRLOCK_FILE" 2>&1 | tee -a "$log"
+ else
+ echo "${WARN} hyprlock template not found; skipping 12H lock format edits" 2>&1 | tee -a "$log"
+ fi
+
+ if [ "${EXPRESS_MODE:-0}" -eq 0 ]; then
+ apply_sddm_12h_format "/usr/share/sddm/themes/simple-sddm" "$log"
+ apply_sddm_12h_format "/usr/share/sddm/themes/simple_sddm_2" "$log"
+ apply_sddm_12h_format_sequoia "/usr/share/sddm/themes/sequoia_2" "$log"
+ else
+ echo "${NOTE:-[NOTE]} Express mode: skipping SDDM 12H edits to avoid sudo prompts." 2>&1 | tee -a "$log"
+ fi
+ echo "${OK} 12H format set on waybar clocks succesfully." 2>&1 | tee -a "$log"
+ return
+ elif [[ "$answer" == "n" ]]; then
+ echo "${NOTE} You chose not to change to 12H format." 2>&1 | tee -a "$log"
+ return
+ else
+ echo "${ERROR} Invalid choice. Please enter y for yes or n for no."
+ fi
+ done
+}
+
+apply_sddm_12h_format() {
+ local sddm_directory="$1"
+ local log="$2"
+ if [ -d "$sddm_directory" ]; then
+ echo "Editing ${SKY_BLUE}$sddm_directory${RESET} to 12H format" 2>&1 | tee -a "$log"
+ if ! sudo -n sed -i 's|^## HourFormat="hh:mm AP"|HourFormat="hh:mm AP"|' "$sddm_directory/theme.conf" 2>&1 | tee -a "$log"; then
+ echo "${WARN:-[WARN]} Skipping SDDM 12H edit (sudo password required)." 2>&1 | tee -a "$log"
+ return
+ fi
+ sudo -n sed -i 's|^HourFormat="HH:mm"|## HourFormat="HH:mm"|' "$sddm_directory/theme.conf" 2>&1 | tee -a "$log" || true
+ fi
+}
+
+apply_sddm_12h_format_sequoia() {
+ local sddm_directory="$1"
+ local log="$2"
+ if [ -d "$sddm_directory" ]; then
+ echo "${YELLOW}sddm sequoia_2${RESET} theme exists. Editing to 12H format" 2>&1 | tee -a "$log"
+ if ! sudo -n sed -i 's|^clockFormat="HH:mm"|## clockFormat="HH:mm"|' "$sddm_directory/theme.conf" 2>&1 | tee -a "$log"; then
+ echo "${WARN:-[WARN]} Skipping sequoia SDDM 12H edit (sudo password required)." 2>&1 | tee -a "$log"
+ return
+ fi
+ if ! grep -q 'clockFormat="hh:mm AP"' "$sddm_directory/theme.conf"; then
+ sudo -n sed -i '/^clockFormat=/a clockFormat="hh:mm AP"' "$sddm_directory/theme.conf" 2>&1 | tee -a "$log" || true
+ fi
+ echo "${OK} 12H format set to SDDM successfully." 2>&1 | tee -a "$log"
+ fi
+}
+
+
+# Express upgrade confirmation; may set EXPRESS_MODE=1.
+prompt_express_upgrade() {
+ local express_supported="$1"
+ local log="$2"
+ if [ "$EXPRESS_MODE" -eq 1 ] && [ "$express_supported" -eq 0 ]; then
+ echo "${NOTE} Express mode requires installed dotfiles v${MIN_EXPRESS_VERSION} or newer. Continuing with standard upgrade prompts." 2>&1 | tee -a "$log"
+ EXPRESS_MODE=0
+ return
+ fi
+ if [ "$UPGRADE_MODE" -eq 1 ] && [ "$EXPRESS_MODE" -eq 0 ]; then
+ if [ "$express_supported" -eq 0 ]; then
+ echo "${NOTE} Express mode requires installed dotfiles v${MIN_EXPRESS_VERSION} or newer. Continuing with standard upgrade prompts." 2>&1 | tee -a "$log"
+ else
+ while true; do
+ echo "${NOTE} Express mode skips config restore prompts, SDDM/background questions, and trims old backups."
+ if ! read -r -p "${CAT} Do you want to continue with EXPRESS upgrade mode? (y/N): " express_choice </dev/tty; then
+ echo "${ERROR} Unable to read input for express choice; defaulting to standard prompts." 2>&1 | tee -a "$log"
+ break
+ fi
+ case "$express_choice" in
+ [Yy])
+ EXPRESS_MODE=1
+ echo "${INFO} Express mode enabled for this upgrade." 2>&1 | tee -a "$log"
+ break
+ ;;
+ [Nn] | "")
+ echo "${NOTE} Continuing with standard upgrade prompts." 2>&1 | tee -a "$log"
+ break
+ ;;
+ *)
+ echo "${WARN} Please answer y or n."
+ ;;
+ esac
+ done
+ fi
+ fi
+}
diff --git a/scripts/lib_update.sh b/scripts/lib_update.sh
new file mode 100644
index 00000000..0a70dff0
--- /dev/null
+++ b/scripts/lib_update.sh
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+
+# run_repo_update
+# Arguments:
+# $1 - expected repository root (typically SCRIPT_DIR from copy.sh)
+# Behavior:
+# * Verifies the script is executed from Hyprland-Dots root.
+# * Stashes local changes (including untracked), pulls latest changes.
+# * Shows progress, reports errors, and summarizes results.
+# * Waits for user input before returning control to caller.
+run_repo_update() {
+ local repo_dir="${1:-$(pwd)}"
+ local expected_name="Hyprland-Dots"
+ local log_dir="$repo_dir/Copy-Logs"
+ local log_file="$log_dir/update-$(date +%d-%H%M%S)_git.log"
+
+ mkdir -p "$log_dir"
+
+ echo "${INFO} Starting repository update..." | tee -a "$log_file"
+
+ if [ ! -d "$repo_dir" ] || [ "$(basename "$repo_dir")" != "$expected_name" ]; then
+ echo "${ERROR} This helper must be run from the $expected_name directory. Current: $(pwd)" | tee -a "$log_file"
+ read -n1 -s -r -p "Press any key to return to the menu..."
+ echo
+ return 1
+ fi
+
+ if [ "$PWD" != "$repo_dir" ]; then
+ echo "${INFO} Changing directory to $repo_dir" | tee -a "$log_file"
+ cd "$repo_dir" || {
+ echo "${ERROR} Failed to change directory to $repo_dir" | tee -a "$log_file"
+ read -n1 -s -r -p "Press any key to return to the menu..."
+ echo
+ return 1
+ }
+ fi
+
+ local head_before stash_msg pull_status=0
+ head_before=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
+
+ echo "${INFO} Checking working tree..." | tee -a "$log_file"
+ if git diff --quiet && git diff --cached --quiet; then
+ stash_msg="No local changes; no stash created."
+ echo "${NOTE} $stash_msg" | tee -a "$log_file"
+ else
+ echo "${INFO} Stashing local changes (tracked + untracked)..." | tee -a "$log_file"
+ if stash_output=$(git stash push -u 2>&1); then
+ stash_msg="Created stash: $(echo "$stash_output" | head -n1)"
+ echo "${OK} $stash_msg" | tee -a "$log_file"
+ else
+ echo "${ERROR} git stash failed. Details:" | tee -a "$log_file"
+ echo "$stash_output" | tee -a "$log_file"
+ read -n1 -s -r -p "Press any key to return to the menu..."
+ echo
+ return 1
+ fi
+ fi
+
+ echo "${INFO} Pulling latest changes..." | tee -a "$log_file"
+ if git pull --ff-only 2>&1 | tee -a "$log_file"; then
+ pull_status=0
+ echo "${OK} Repository updated successfully." | tee -a "$log_file"
+ else
+ pull_status=$?
+ echo "${ERROR} git pull failed (exit $pull_status)." | tee -a "$log_file"
+ fi
+
+ local head_after
+ head_after=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
+
+ echo "----------------------------------------" | tee -a "$log_file"
+ echo "Summary:" | tee -a "$log_file"
+ echo " Repo : $repo_dir" | tee -a "$log_file"
+ echo " HEAD before : $head_before" | tee -a "$log_file"
+ echo " HEAD after : $head_after" | tee -a "$log_file"
+ echo " Stash : $stash_msg" | tee -a "$log_file"
+ echo " Pull status : $( [ $pull_status -eq 0 ] && echo success || echo failure )" | tee -a "$log_file"
+ echo "----------------------------------------" | tee -a "$log_file"
+
+ read -n1 -s -r -p "Press any key to return to the main menu..."
+ echo
+
+ return $pull_status
+}
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage