From 0b2332e45d7f4c93e3c7e8fd41a59ec218eca4b3 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 11 May 2026 17:35:21 +0200 Subject: [PATCH] Permanent process for speed --- bin/omarchy-menu-images | 109 +++++++++++++++++++--- default/hypr/autostart.lua | 1 + default/quickshell/wallpaper-switcher.qml | 104 +++++++++++++++++---- 3 files changed, 184 insertions(+), 30 deletions(-) diff --git a/bin/omarchy-menu-images b/bin/omarchy-menu-images index 11d08667..9f50c729 100755 --- a/bin/omarchy-menu-images +++ b/bin/omarchy-menu-images @@ -36,7 +36,11 @@ if (( ${#image_dirs[@]} == 0 )); then fi selection_file=$(mktemp) -trap 'rm -f "$selection_file"' EXIT +done_file=$(mktemp) +rm -f "$done_file" +trap 'rm -f "$selection_file" "$done_file"' EXIT +socket_path="${XDG_RUNTIME_DIR:-/run/user/$UID}/omarchy-image-selector.sock" +selector_qml="$OMARCHY_PATH/default/quickshell/wallpaper-switcher.qml" image_dirs_env="" for dir in "${image_dirs[@]}"; do @@ -60,26 +64,103 @@ if [[ -n $current_image ]]; then fi cache_dir=${XDG_CACHE_HOME:-$HOME/.cache}/omarchy/image-selector +index_file="$cache_dir/index.tsv" +rows="" mkdir -p "$cache_dir" +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="" + for dir in "${image_dirs[@]}"; do if [[ -d $dir ]]; then - while IFS= read -r -d '' image; do - hash=$(md5sum "$image" | cut -d ' ' -f 1) - thumbnail="$cache_dir/$hash.jpg" - - if [[ ! -f $thumbnail ]]; then - magick "$image" -auto-orient -resize '1536x864^' -gravity center -extent '1536x864' -strip -quality 82 "$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) + rows_signature+="$dir:$(stat -Lc '%Y' "$dir")"$'\n' fi done -OMARCHY_IMAGE_SELECTOR_SELECTION_FILE=$selection_file \ - OMARCHY_IMAGE_SELECTOR_DIRS="$image_dirs_env" \ - OMARCHY_IMAGE_SELECTOR_SELECTED="$selected_list_image" \ - OMARCHY_IMAGE_SELECTOR_COLORS_FILE="$colors_file" \ - quickshell -p "$OMARCHY_PATH/default/quickshell/wallpaper-switcher.qml" >/dev/null +thumbnail_for() { + local image="$1" + local signature hash thumbnail + + signature=$(stat -Lc '%s:%Y' "$image") || return + hash=$(awk -F '\t' -v path="$image" -v sig="$signature" '$1 == path && $2 == sig { print $3; exit }' "$index_file" 2>/dev/null) + + if [[ -z $hash ]]; then + hash=$(md5sum "$image" | cut -d ' ' -f 1) + printf '%s\t%s\t%s\n' "$image" "$signature" "$hash" >>"$index_file" + fi + + thumbnail="$cache_dir/$hash.jpg" + + if [[ ! -f $thumbnail ]]; then + magick "$image" -auto-orient -resize '1536x864^' -gravity center -extent '1536x864' -strip -quality 82 "$thumbnail" + fi + + 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 + + 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) + fi + done + + printf '%s' "$rows" >"$rows_cache_file" + printf '%s' "$rows_signature" >"$rows_signature_file" +fi + +rows_payload=${rows//$'\t'/$'\f'} +rows_payload=${rows_payload//$'\n'/$'\v'} + +selector_pid=$(quickshell list -p "$selector_qml" --json 2>/dev/null | jq -r '.[0].pid // empty') +if [[ -n $selector_pid ]]; then + selector_age=$(ps -o etimes= -p "$selector_pid" 2>/dev/null | tr -d ' ') + qml_mtime=$(stat -c %Y "$selector_qml") + now=$(date +%s) + + if [[ -n $selector_age ]] && (( now - selector_age < qml_mtime )); then + quickshell kill -p "$selector_qml" >/dev/null 2>&1 || true + rm -f "$socket_path" + fi +fi + +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 + + sleep 0.01 + done +fi + +if [[ ! -S $socket_path ]]; then + echo "Image selector failed to start" >&2 + exit 1 +fi + +printf '%s\t%s\t%s\t%s\t%s\n' "$rows_payload" "$selected_list_image" "$selection_file" "$done_file" "$colors_file" | + socat -u - "UNIX-CONNECT:$socket_path" + +while [[ ! -e $done_file ]]; do + sleep 0.01 +done if [[ -s $selection_file ]]; then cat "$selection_file" diff --git a/default/hypr/autostart.lua b/default/hypr/autostart.lua index 7eed5fbb..846b788a 100644 --- a/default/hypr/autostart.lua +++ b/default/hypr/autostart.lua @@ -4,6 +4,7 @@ hl.on("hyprland.start", function() hl.exec_cmd("! omarchy-toggle-enabled waybar-off && uwsm-app -- waybar") hl.exec_cmd("uwsm-app -- fcitx5 --disable notificationitem") hl.exec_cmd("uwsm-app -- swaybg -i ~/.config/omarchy/current/background -m fill") + hl.exec_cmd("uwsm-app -- quickshell -p ~/.local/share/omarchy/default/quickshell/wallpaper-switcher.qml") hl.exec_cmd("/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1") hl.exec_cmd("omarchy-first-run") hl.exec_cmd("omarchy-powerprofiles-init") diff --git a/default/quickshell/wallpaper-switcher.qml b/default/quickshell/wallpaper-switcher.qml index e686fbe9..01bc508e 100644 --- a/default/quickshell/wallpaper-switcher.qml +++ b/default/quickshell/wallpaper-switcher.qml @@ -9,11 +9,15 @@ ShellRoot { id: root property string imageDirs: Quickshell.env("OMARCHY_IMAGE_SELECTOR_DIRS") || Quickshell.env("OMARCHY_IMAGE_SELECTOR_DIR") || Quickshell.env("OMARCHY_STOCK_BACKGROUNDS_DIR") || (Quickshell.env("HOME") + "/.config/omarchy/current/theme/backgrounds") + property string imageRows: "" property string selectionFile: Quickshell.env("OMARCHY_IMAGE_SELECTOR_SELECTION_FILE") || Quickshell.env("OMARCHY_WALLPAPER_SELECTION_FILE") property string selectedImage: Quickshell.env("OMARCHY_IMAGE_SELECTOR_SELECTED") property string colorsFile: Quickshell.env("OMARCHY_IMAGE_SELECTOR_COLORS_FILE") || (Quickshell.env("HOME") + "/.config/omarchy/current/theme/wallpaper-switcher-colors.json") property int selectedIndex: 0 property bool imagesLoaded: false + property bool opened: false + property string doneFile: "" + 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" property color foreground: "#cacccc" @@ -31,6 +35,10 @@ ShellRoot { return "'" + String(value).replace(/'/g, "'\\''") + "'" } + function decodeField(value) { + return String(value || "").replace(/\v/g, "\n").replace(/\f/g, "\t") + } + function currentPath() { if (imageModel.count === 0) return "" return imageModel.get(selectedIndex).filePath @@ -49,10 +57,51 @@ ShellRoot { function applySelected() { var path = currentPath() if (!path) return - applyProc.command = ["bash", "-lc", "printf '%s\\n' " + shellQuote(path) + " > " + shellQuote(selectionFile)] + applyProc.command = ["bash", "-lc", "printf '%s\\n' " + shellQuote(path) + " > " + shellQuote(selectionFile) + "; : > " + shellQuote(doneFile)] applyProc.running = true } + function cancel() { + cancelProc.command = ["bash", "-lc", ": > " + shellQuote(doneFile)] + cancelProc.running = true + } + + function loadRows(rows) { + var paths = rows.split("\n") + for (var i = 0; i < paths.length; i++) { + var row = paths[i].trim() + if (!row) continue + + var columns = row.split("\t") + root.addImage(columns[0], columns[1]) + } + + root.select(root.selectedImageIndex()) + root.imagesLoaded = true + root.opened = true + list.forceActiveFocus() + } + + function openSelector(nextImageDirs, nextImageRows, nextSelectedImage, nextSelectionFile, nextDoneFile, nextColorsFile) { + imageDirs = nextImageDirs + imageRows = nextImageRows + selectedImage = nextSelectedImage + selectionFile = nextSelectionFile + doneFile = nextDoneFile + colorsFile = nextColorsFile || (Quickshell.env("HOME") + "/.config/omarchy/current/theme/wallpaper-switcher-colors.json") + imageModel.clear() + selectedIndex = 0 + list.currentIndex = 0 + imagesLoaded = false + opened = false + if (imageRows) { + loadRows(imageRows) + } else { + loadImagesProc.output = "" + loadImagesProc.running = true + } + } + ListModel { id: imageModel onCountChanged: { @@ -92,20 +141,38 @@ ShellRoot { } } onExited: { - var paths = output.split("\n") - for (var i = 0; i < paths.length; i++) { - var row = paths[i].trim() - if (!row) continue - - var columns = row.split("\t") - root.addImage(columns[0], columns[1]) - } - root.select(root.selectedImageIndex()) - root.imagesLoaded = true + root.loadRows(output) } } - Component.onCompleted: loadImagesProc.running = true + Component.onCompleted: { + if (selectionFile) + openSelector(imageDirs, "", selectedImage, selectionFile, Quickshell.env("OMARCHY_IMAGE_SELECTOR_DONE_FILE"), colorsFile) + } + + IpcHandler { + target: "image-selector" + + function open(imageDirs: string, imageRows: string, selectedImage: string, selectionFile: string, doneFile: string, colorsFile: string): void { + root.openSelector(imageDirs, imageRows, selectedImage, selectionFile, doneFile, colorsFile) + } + } + + SocketServer { + active: true + path: root.socketPath + + handler: Socket { + id: clientSocket + parser: SplitParser { + onRead: function(message) { + var fields = message.split("\t") + root.openSelector("", root.decodeField(fields[0]), fields[1] || "", fields[2] || "", fields[3] || "", fields[4] || "") + clientSocket.connected = false + } + } + } + } FileView { path: root.colorsFile @@ -125,12 +192,17 @@ ShellRoot { Process { id: applyProc - onExited: Qt.quit() + onExited: root.opened = false + } + + Process { + id: cancelProc + onExited: root.opened = false } PanelWindow { id: panel - visible: root.imagesLoaded + visible: root.opened && root.imagesLoaded anchors { top: true; bottom: true; left: true; right: true } color: "transparent" WlrLayershell.namespace: "omarchy-image-selector" @@ -145,7 +217,7 @@ ShellRoot { MouseArea { anchors.fill: parent - onClicked: Qt.quit() + onClicked: root.cancel() } Item { @@ -189,7 +261,7 @@ ShellRoot { Keys.priority: Keys.BeforeItem Keys.onPressed: function(event) { if (event.key === Qt.Key_Escape) { - Qt.quit() + root.cancel() event.accepted = true } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { root.applySelected()