diff --git a/bin/omarchy-menu-images b/bin/omarchy-menu-images index bf0c535d..540a6313 100755 --- a/bin/omarchy-menu-images +++ b/bin/omarchy-menu-images @@ -12,13 +12,27 @@ show_labels=false cache_only=false image_dirs=() +usage() { + echo "Usage: omarchy-menu-images [--selected ] [--colors-file ] [--print-name] [--show-labels] [--cache-only] ..." +} + while [[ $# -gt 0 ]]; do case "$1" in --selected) + if (( $# < 2 )); then + usage >&2 + exit 1 + fi + selected_image="$2" shift 2 ;; --colors-file) + if (( $# < 2 )); then + usage >&2 + exit 1 + fi + colors_file="$2" shift 2 ;; @@ -35,7 +49,7 @@ while [[ $# -gt 0 ]]; do shift ;; --help|-h) - echo "Usage: omarchy-menu-images [--selected ] [--colors-file ] [--print-name] [--show-labels] [--cache-only] ..." + usage exit 0 ;; *) @@ -46,7 +60,7 @@ while [[ $# -gt 0 ]]; do done if (( ${#image_dirs[@]} == 0 )); then - echo "Usage: omarchy-menu-images [--selected ] [--colors-file ] [--print-name] [--show-labels] [--cache-only] ..." >&2 + usage >&2 exit 1 fi @@ -90,10 +104,17 @@ cache_key=$(printf '%s' "$image_dirs_env" | md5sum | cut -d ' ' -f 1) rows_cache_file="$cache_dir/$cache_key.rows" rows_signature_file="$cache_dir/$cache_key.signature" rows_signature="" +image_files=() for dir in "${image_dirs[@]}"; do if [[ -d $dir ]]; then rows_signature+="$dir:$(stat -Lc '%Y' "$dir")"$'\n' + + while IFS= read -r -d '' image; do + image_files+=("$image") + image_signature=$(stat -Lc '%s:%Y' "$image") || continue + rows_signature+="$image:$image_signature"$'\n' + done < <(find -L "$dir" -maxdepth 1 -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.gif' -o -iname '*.bmp' -o -iname '*.webp' \) -print0 2>/dev/null | sort -z) fi done @@ -112,27 +133,23 @@ thumbnail_for() { thumbnail="$cache_dir/$hash.jpg" if [[ ! -f $thumbnail ]]; then - magick "$image" -auto-orient -resize '1536x864^' -gravity center -extent '1536x864' -strip -quality 82 "$thumbnail" + magick "${image}[0]" -auto-orient -resize '1536x864^' -gravity center -extent '1536x864' -strip -quality 82 "$thumbnail" fi - printf '%s' "$thumbnail" + [[ -f $thumbnail ]] && printf '%s' "$thumbnail" } if [[ -f $rows_cache_file && -f $rows_signature_file ]] && cmp -s "$rows_signature_file" <(printf '%s' "$rows_signature"); then rows=$(<"$rows_cache_file") else - for dir in "${image_dirs[@]}"; do - if [[ -d $dir ]]; then - while IFS= read -r -d '' image; do - thumbnail=$(thumbnail_for "$image") - [[ -n $thumbnail ]] || continue + for image in "${image_files[@]}"; do + thumbnail=$(thumbnail_for "$image") + [[ -n $thumbnail ]] || continue - if [[ -z $rows ]]; then - rows="$image"$'\t'"$thumbnail" - else - rows+=$'\n'"$image"$'\t'"$thumbnail" - fi - done < <(find -L "$dir" -maxdepth 1 -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' \) -print0 2>/dev/null) + if [[ -z $rows ]]; then + rows="$image"$'\t'"$thumbnail" + else + rows+=$'\n'"$image"$'\t'"$thumbnail" fi done @@ -155,31 +172,46 @@ if [[ $cache_only == true ]]; then exit 0 fi -if [[ -S $socket_path && $selector_qml -nt $socket_path ]]; then - quickshell kill -p "$selector_qml" >/dev/null 2>&1 || true - rm -f "$socket_path" -fi +ensure_selector() { + if [[ -S $socket_path && $selector_qml -nt $socket_path ]]; then + quickshell kill -p "$selector_qml" >/dev/null 2>&1 || true + rm -f "$socket_path" + fi -if [[ ! -S $socket_path ]]; then - quickshell kill -p "$selector_qml" >/dev/null 2>&1 || true - quickshell -d -p "$selector_qml" >/dev/null + if [[ ! -S $socket_path ]]; then + quickshell kill -p "$selector_qml" >/dev/null 2>&1 || true + quickshell -d -p "$selector_qml" >/dev/null - for ((i = 0; i < 100; i++)); do - if [[ -S $socket_path ]]; then - break - fi + for ((i = 0; i < 100; i++)); do + if [[ -S $socket_path ]]; then + break + fi - sleep 0.01 - done -fi + sleep 0.01 + done + fi -if [[ ! -S $socket_path ]]; then + [[ -S $socket_path ]] +} + +send_request() { + printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$rows_payload" "$selected_list_image" "$selection_file" "$done_file" "$colors_payload" "$show_labels" | + socat -u - "UNIX-CONNECT:$socket_path" +} + +if ! ensure_selector; then echo "Image selector failed to start" >&2 exit 1 fi -printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$rows_payload" "$selected_list_image" "$selection_file" "$done_file" "$colors_payload" "$show_labels" | - socat -u - "UNIX-CONNECT:$socket_path" +if ! send_request; then + rm -f "$socket_path" + + if ! ensure_selector || ! send_request; then + echo "Image selector failed to accept request" >&2 + exit 1 + fi +fi while [[ ! -e $done_file ]]; do sleep 0.01 diff --git a/bin/omarchy-theme-switcher b/bin/omarchy-theme-switcher index a21ed4aa..a053a70d 100755 --- a/bin/omarchy-theme-switcher +++ b/bin/omarchy-theme-switcher @@ -13,13 +13,19 @@ mkdir -p "$preview_dir" find_preview() { local theme_path="$1" + local preview preview_name - if [[ -f $theme_path/preview.png ]]; then - printf '%s\n' "$theme_path/preview.png" - elif [[ -f $theme_path/preview.jpg ]]; then - printf '%s\n' "$theme_path/preview.jpg" - elif [[ -d $theme_path/backgrounds ]]; then - find -L "$theme_path/backgrounds" -maxdepth 1 -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' \) -print -quit 2>/dev/null + for preview_name in preview.png preview.jpg preview.jpeg preview.webp preview.gif preview.bmp; do + preview=$(find -L "$theme_path" -maxdepth 1 -type f -iname "$preview_name" -print -quit 2>/dev/null) + + if [[ -n $preview ]]; then + printf '%s\n' "$preview" + return + fi + done + + if [[ -d $theme_path/backgrounds ]]; then + find -L "$theme_path/backgrounds" -maxdepth 1 -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.gif' -o -iname '*.bmp' -o -iname '*.webp' \) -print 2>/dev/null | sort | head -n 1 fi } @@ -27,6 +33,7 @@ add_theme_preview() { local theme_name="$1" local preview="$2" local extension="${preview##*.}" + extension="${extension,,}" [[ -n $preview ]] || return [[ -e $preview_dir/$theme_name.$extension ]] && return @@ -38,6 +45,15 @@ theme_signature="" for theme_dir in "$USER_THEMES_PATH" "$OMARCHY_THEMES_PATH"; do if [[ -d $theme_dir ]]; then theme_signature+="$theme_dir:$(stat -Lc '%Y' "$theme_dir")"$'\n' + + while IFS= read -r -d '' theme_path; do + preview=$(find_preview "$theme_path") + theme_signature+="$theme_path:$(stat -Lc '%Y' "$theme_path")"$'\n' + + if [[ -n $preview ]]; then + theme_signature+="$preview:$(stat -Lc '%s:%Y' "$preview")"$'\n' + fi + done < <(find -L "$theme_dir" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) -print0 2>/dev/null | sort -z) fi done @@ -67,7 +83,7 @@ fi current_theme=$(cat "$HOME/.config/omarchy/current/theme.name" 2>/dev/null) selected_preview="" -for extension in png jpg jpeg webp; do +for extension in png jpg jpeg webp gif bmp; do if [[ -e $preview_dir/$current_theme.$extension ]]; then selected_preview="$preview_dir/$current_theme.$extension" break diff --git a/default/quickshell/background-switcher.qml b/default/quickshell/background-switcher.qml index 1810f823..0552b1de 100644 --- a/default/quickshell/background-switcher.qml +++ b/default/quickshell/background-switcher.qml @@ -17,7 +17,11 @@ ShellRoot { property bool imagesLoaded: false property bool opened: false property bool showLabels: false + property bool requestActive: false + property int requestSerial: 0 + property int applySerial: 0 property string doneFile: "" + property var doneFilesToRelease: [] property string socketPath: (Quickshell.env("XDG_RUNTIME_DIR") || ("/run/user/" + Quickshell.env("UID"))) + "/omarchy-image-selector.sock" property color accent: "#798186" property color background: "#101315" @@ -62,23 +66,52 @@ ShellRoot { selectedIndex = index } + function releaseNextDoneFile() { + if (releaseProc.running || doneFilesToRelease.length === 0) return + + var path = doneFilesToRelease.shift() + releaseProc.command = ["bash", "-lc", ": > " + shellQuote(path)] + releaseProc.running = true + } + + function finishDoneFile(path) { + if (!path) return + doneFilesToRelease.push(path) + releaseNextDoneFile() + } + function applySelected() { var path = currentPath() - if (!path) return - if (!selectionFile) return - applyProc.command = ["bash", "-lc", "printf '%s\\n' " + shellQuote(path) + " > " + shellQuote(selectionFile) + "; : > " + shellQuote(doneFile)] + if (!path || !selectionFile) { + cancel() + return + } + + var activeSelectionFile = selectionFile + var activeDoneFile = doneFile + applySerial = requestSerial + requestActive = false + selectionFile = "" + doneFile = "" + + applyProc.command = ["bash", "-lc", "printf '%s\\n' " + shellQuote(path) + " > " + shellQuote(activeSelectionFile) + "; : > " + shellQuote(activeDoneFile)] applyProc.running = true } function cancel() { - cancelProc.command = ["bash", "-lc", ": > " + shellQuote(doneFile)] - cancelProc.running = true + if (requestActive) + finishDoneFile(doneFile) + + requestActive = false + selectionFile = "" + doneFile = "" + root.opened = false } function loadRows(rows) { var paths = rows.split("\n") for (var i = 0; i < paths.length; i++) { - var row = paths[i].trim() + var row = paths[i] if (!row) continue var columns = row.split("\t") @@ -92,11 +125,17 @@ ShellRoot { } function openSelector(nextImageDirs, nextImageRows, nextSelectedImage, nextSelectionFile, nextDoneFile, nextColorsFile, nextColorsRaw, nextShowLabels) { + if (requestActive && doneFile && doneFile !== nextDoneFile) + finishDoneFile(doneFile) + + requestSerial += 1 + imageDirs = nextImageDirs imageRows = nextImageRows selectedImage = nextSelectedImage selectionFile = nextSelectionFile doneFile = nextDoneFile + requestActive = !!doneFile showLabels = nextShowLabels === true || nextShowLabels === "true" colorsFile = nextColorsFile || (Quickshell.env("HOME") + "/.config/omarchy/current/theme/background-switcher-colors.json") if (nextColorsRaw) @@ -139,7 +178,7 @@ ShellRoot { Process { id: loadImagesProc property string output: "" - command: ["bash", "-lc", "cache_dir=${XDG_CACHE_HOME:-$HOME/.cache}/omarchy/image-selector; while IFS= read -r dir; do [[ -n $dir && -d $dir ]] && find -L \"$dir\" -maxdepth 1 -type f \\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' \\) -print0; done <<< " + shellQuote(root.imageDirs) + " | sort -z | while IFS= read -r -d '' image; do hash=$(md5sum \"$image\" | cut -d ' ' -f 1); thumb=\"$cache_dir/$hash.jpg\"; [[ -f $thumb ]] || thumb=$image; printf '%s\\t%s\\n' \"$image\" \"$thumb\"; done"] + command: ["bash", "-lc", "cache_dir=${XDG_CACHE_HOME:-$HOME/.cache}/omarchy/image-selector; while IFS= read -r dir; do [[ -n $dir && -d $dir ]] && find -L \"$dir\" -maxdepth 1 -type f \\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.gif' -o -iname '*.bmp' -o -iname '*.webp' \\) -print0; done <<< " + shellQuote(root.imageDirs) + " | sort -z | while IFS= read -r -d '' image; do hash=$(md5sum \"$image\" | cut -d ' ' -f 1); thumb=\"$cache_dir/$hash.jpg\"; [[ -f $thumb ]] || thumb=$image; printf '%s\\t%s\\n' \"$image\" \"$thumb\"; done"] stdout: SplitParser { onRead: function(data) { loadImagesProc.output += data + "\n" @@ -197,12 +236,15 @@ ShellRoot { Process { id: applyProc - onExited: root.opened = false + onExited: { + if (root.applySerial === root.requestSerial) + root.opened = false + } } Process { - id: cancelProc - onExited: root.opened = false + id: releaseProc + onExited: root.releaseNextDoneFile() } PanelWindow {