diff --git a/bin/omarchy-theme-set b/bin/omarchy-theme-set index 782b4ba7..e440ee43 100755 --- a/bin/omarchy-theme-set +++ b/bin/omarchy-theme-set @@ -10,8 +10,10 @@ if [[ -z $1 ]]; then fi CURRENT_THEME_PATH="$HOME/.local/state/omarchy/current/theme" +CURRENT_THEME_NAME_PATH="$HOME/.local/state/omarchy/current/theme.name" NEXT_THEME_PATH="$HOME/.local/state/omarchy/current/next-theme" CURRENT_BACKGROUND_LINK="$HOME/.local/state/omarchy/current/background" +THEME_BACKGROUND_STATE_PATH="$HOME/.local/state/omarchy/theme-backgrounds" BACKGROUND_TRANSITION_CACHE="$HOME/.cache/omarchy/background-transitions" THEME_SET_LOCK="${XDG_RUNTIME_DIR:-/tmp}/omarchy-theme-set.lock" USER_THEMES_PATH="$HOME/.config/omarchy/themes" @@ -74,6 +76,24 @@ snapshot_current_background() { snapshot_background_path "$current_background" "previous" } +theme_background_state_file() { + local theme="$1" + + [[ -n $theme && ${theme##*/} == $theme && $theme != "." && $theme != ".." ]] || return 1 + printf '%s/%s\n' "$THEME_BACKGROUND_STATE_PATH" "$theme" +} + +remember_current_theme_background() { + local current_background state_file + + state_file=$(theme_background_state_file "$PREVIOUS_THEME_NAME") || return + current_background=$(readlink -f "$CURRENT_BACKGROUND_LINK" 2>/dev/null || true) + [[ -f $current_background ]] || return + + mkdir -p "$THEME_BACKGROUND_STATE_PATH" + printf '%s\n' "$current_background" >"$state_file" +} + background_transition_uses_snapshots() { local next_background="$1" local current_background @@ -86,7 +106,7 @@ choose_theme_background() { local theme_path="${1:-$CURRENT_THEME_PATH}" local current_theme_backgrounds="$CURRENT_THEME_PATH/backgrounds" local backgrounds=() - local current_background index next_index i + local current_background remembered_background state_file index next_index i CHOSEN_THEME_BACKGROUND="" mapfile -d '' -t backgrounds < <( @@ -98,25 +118,40 @@ choose_theme_background() { (( ${#backgrounds[@]} > 0 )) || return 1 - current_background=$(readlink "$CURRENT_BACKGROUND_LINK" 2>/dev/null || true) - if [[ $theme_path != $CURRENT_THEME_PATH && ${current_background%/*} == $current_theme_backgrounds ]]; then - current_background="$theme_path/backgrounds/${current_background##*/}" - fi - - index=-1 - for i in "${!backgrounds[@]}"; do - if [[ ${backgrounds[$i]} == $current_background ]]; then - index=$i - break + if [[ $PREVIOUS_THEME_NAME == "$THEME_NAME" ]]; then + current_background=$(readlink "$CURRENT_BACKGROUND_LINK" 2>/dev/null || true) + if [[ $theme_path != $CURRENT_THEME_PATH && ${current_background%/*} == $current_theme_backgrounds ]]; then + current_background="$theme_path/backgrounds/${current_background##*/}" fi - done - if (( index == -1 )); then - CHOSEN_THEME_BACKGROUND="${backgrounds[0]}" - else - next_index=$(((index + 1) % ${#backgrounds[@]})) - CHOSEN_THEME_BACKGROUND="${backgrounds[$next_index]}" + index=-1 + for i in "${!backgrounds[@]}"; do + if [[ ${backgrounds[$i]} == $current_background ]]; then + index=$i + break + fi + done + + if (( index != -1 )); then + next_index=$(((index + 1) % ${#backgrounds[@]})) + CHOSEN_THEME_BACKGROUND="${backgrounds[$next_index]}" + else + CHOSEN_THEME_BACKGROUND="${backgrounds[0]}" + fi + return fi + + state_file=$(theme_background_state_file "$THEME_NAME" 2>/dev/null || true) + remembered_background=$(cat "$state_file" 2>/dev/null || true) + if [[ $theme_path != $CURRENT_THEME_PATH && ${remembered_background%/*} == $current_theme_backgrounds ]]; then + remembered_background="$theme_path/backgrounds/${remembered_background##*/}" + fi + if [[ -f $remembered_background ]]; then + CHOSEN_THEME_BACKGROUND="$remembered_background" + return + fi + + CHOSEN_THEME_BACKGROUND="${backgrounds[0]}" } choose_staged_theme_background() { @@ -296,6 +331,9 @@ fi exec 9>"$THEME_SET_LOCK" flock 9 +PREVIOUS_THEME_NAME=$(cat "$CURRENT_THEME_NAME_PATH" 2>/dev/null || true) +remember_current_theme_background + # Setup clean next theme directory (for atomic theme config swapping) rm -rf "$NEXT_THEME_PATH" mkdir -p "$NEXT_THEME_PATH" @@ -338,7 +376,7 @@ rm -rf "$CURRENT_THEME_PATH" mv "$NEXT_THEME_PATH" "$CURRENT_THEME_PATH" # Store theme name for reference -echo "$THEME_NAME" >"$HOME/.local/state/omarchy/current/theme.name" +echo "$THEME_NAME" >"$CURRENT_THEME_NAME_PATH" # Make the running shell pick up the new palette immediately while the rest of # the theme hooks run. diff --git a/test/shell.d/theme-background-memory-test.sh b/test/shell.d/theme-background-memory-test.sh new file mode 100644 index 00000000..b79dc1ac --- /dev/null +++ b/test/shell.d/theme-background-memory-test.sh @@ -0,0 +1,130 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "$0")/base-test.sh" + +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT + +test_home="$test_tmp/home" +runtime_dir="$test_tmp/runtime" +current_state="$test_home/.local/state/omarchy/current" +background_state="$test_home/.local/state/omarchy/theme-backgrounds" +mkdir -p "$test_home" "$runtime_dir" + +set_theme() { + HOME="$test_home" XDG_RUNTIME_DIR="$runtime_dir" OMARCHY_PATH="$ROOT" PATH="$ROOT/bin:$PATH" \ + OMARCHY_THEME_HEADLESS=1 "$ROOT/bin/omarchy-theme-set" "$1" >/dev/null +} + +current_background_name() { + basename "$(readlink "$current_state/background")" +} + +theme_a="tokyo-night" +theme_b="catppuccin" + +set_theme "$theme_a" +mapfile -t theme_a_backgrounds < <(find "$current_state/theme/backgrounds" -maxdepth 1 -type f -print | sort) +(( ${#theme_a_backgrounds[@]} > 1 )) || fail "test theme has multiple backgrounds" +theme_a_first=${theme_a_backgrounds[0]##*/} +theme_a_selected_path=${theme_a_backgrounds[1]} +theme_a_selected=${theme_a_selected_path##*/} +ln -nsf "$theme_a_selected_path" "$current_state/background" + +set_theme "$theme_b" +mapfile -t theme_b_backgrounds < <(find "$current_state/theme/backgrounds" -maxdepth 1 -type f -print | sort) +(( ${#theme_b_backgrounds[@]} > 1 )) || fail "second test theme has multiple backgrounds" +theme_b_first=${theme_b_backgrounds[0]##*/} + +common_background=$(comm -12 \ + <(find "$ROOT/themes/$theme_a/backgrounds" -maxdepth 1 -type f -printf '%f\n' | sort) \ + <(find "$current_state/theme/backgrounds" -maxdepth 1 -type f -printf '%f\n' | sort) | head -n 1) +[[ -n $common_background ]] || fail "test themes share a background filename" +theme_b_common_path=$(find "$current_state/theme/backgrounds" -maxdepth 1 -type f -name "$common_background" -print -quit) +ln -nsf "$theme_b_common_path" "$current_state/background" + +[[ $(<"$background_state/$theme_a") == "$theme_a_selected_path" ]] || fail "theme switch remembers the outgoing background path" +pass "theme switch remembers the outgoing background path" + +set_theme "$theme_a" +[[ $(current_background_name) == "$theme_a_selected" ]] || fail "shared filenames do not override the remembered background" +pass "shared filenames do not override the remembered background" + +set_theme "$theme_b" +[[ $(current_background_name) == "$common_background" ]] || fail "themes remember backgrounds independently" +pass "themes remember backgrounds independently" + +external_background="$test_tmp/external-background.webp" +cp "$(readlink -f "$current_state/background")" "$external_background" +ln -nsf "$external_background" "$current_state/background" +set_theme "$theme_a" +set_theme "$theme_b" +[[ $(readlink -f "$current_state/background") == "$external_background" ]] || fail "theme switch restores an external background path" +pass "theme switch restores an external background path" + +set_theme "$theme_b" +[[ $(current_background_name) == "$theme_b_first" ]] || fail "reapplying a theme with an external background falls back to the first image" +pass "reapplying a theme with an external background falls back to the first image" + +set_theme "$theme_a" +user_background_dir="$test_home/.config/omarchy/backgrounds/$theme_a" +user_background="$user_background_dir/$theme_a_first" +mkdir -p "$user_background_dir" +cp "${theme_a_backgrounds[0]}" "$user_background" +ln -nsf "$user_background" "$current_state/background" +set_theme "$theme_b" +set_theme "$theme_a" +[[ $(readlink -f "$current_state/background") == "$user_background" ]] || fail "theme switch distinguishes duplicate background filenames" +pass "theme switch distinguishes duplicate background filenames" + +set_theme "$theme_b" +rm -f "$user_background" +printf '%s\n' "$test_tmp/missing-background.webp" >"$background_state/$theme_a" +set_theme "$theme_a" +[[ $(current_background_name) == "$theme_a_first" ]] || fail "missing remembered background falls back to the first image" +pass "missing remembered background falls back to the first image" + +set_theme "$theme_a" +[[ $(current_background_name) == "$theme_a_selected" ]] || fail "reapplying the active theme still cycles backgrounds" +pass "reapplying the active theme still cycles backgrounds" + +printf '%s\n' "../escaped" >"$current_state/theme.name" +set_theme "$theme_b" +[[ ! -e $test_home/.local/state/omarchy/escaped ]] || fail "invalid theme names cannot escape the background state directory" +pass "invalid theme names cannot escape the background state directory" + +# Interactive switches choose before swapping the staged theme into place. +source <(awk ' + /^(theme_background_state_file|choose_theme_background|choose_staged_theme_background)\(\) \{/ { copying=1 } + copying { print } + copying && /^}$/ { copying=0 } +' "$ROOT/bin/omarchy-theme-set") + +HOME="$test_home" +CURRENT_THEME_PATH="$current_state/theme" +NEXT_THEME_PATH="$current_state/next-theme" +CURRENT_BACKGROUND_LINK="$current_state/background" +THEME_BACKGROUND_STATE_PATH="$background_state" +THEME_NAME="staged-test" +PREVIOUS_THEME_NAME="$theme_b" +mkdir -p "$NEXT_THEME_PATH/backgrounds" +printf 'first\n' >"$NEXT_THEME_PATH/backgrounds/first.png" +printf 'selected\n' >"$NEXT_THEME_PATH/backgrounds/selected.mp4" +printf '%s\n' "$CURRENT_THEME_PATH/backgrounds/selected.mp4" >"$background_state/$THEME_NAME" +choose_staged_theme_background +[[ $CHOSEN_THEME_BACKGROUND == "$CURRENT_THEME_PATH/backgrounds/selected.mp4" ]] || fail "staged selection restores a remembered file absent from the outgoing theme" +pass "staged selection restores a remembered file absent from the outgoing theme" + +printf 'outgoing only\n' >"$CURRENT_THEME_PATH/backgrounds/outgoing-only.png" +printf '%s\n' "$CURRENT_THEME_PATH/backgrounds/outgoing-only.png" >"$background_state/$THEME_NAME" +choose_staged_theme_background +[[ $CHOSEN_THEME_BACKGROUND == "$CURRENT_THEME_PATH/backgrounds/first.png" ]] || fail "staged selection rejects remembered files absent from the incoming theme" +pass "staged selection rejects remembered files absent from the incoming theme" + +PREVIOUS_THEME_NAME="$THEME_NAME" +ln -nsf "$CURRENT_THEME_PATH/backgrounds/first.png" "$CURRENT_BACKGROUND_LINK" +choose_staged_theme_background +[[ $CHOSEN_THEME_BACKGROUND == "$CURRENT_THEME_PATH/backgrounds/selected.mp4" ]] || fail "staged same-theme selection still cycles backgrounds" +pass "staged same-theme selection still cycles backgrounds" diff --git a/test/shell.d/video-background-test.sh b/test/shell.d/video-background-test.sh index 761c97a9..cdae2b1a 100755 --- a/test/shell.d/video-background-test.sh +++ b/test/shell.d/video-background-test.sh @@ -299,6 +299,7 @@ NEXT_THEME_PATH="$transition_home/.local/state/omarchy/current/next-theme" CURRENT_BACKGROUND_LINK="$transition_home/.local/state/omarchy/current/background" BACKGROUND_TRANSITION_CACHE="$transition_home/.cache/omarchy/background-transitions" THEME_NAME="video-test" +PREVIOUS_THEME_NAME="$THEME_NAME" HOME="$transition_home" mkdir -p "$CURRENT_THEME_PATH/backgrounds" "$NEXT_THEME_PATH/backgrounds" "$HOME/.config/omarchy/backgrounds/$THEME_NAME" printf 'old image\n' >"$CURRENT_THEME_PATH/backgrounds/old.png"