From 333ec367dfba1241972f7b580a344c818124f6e4 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 11 May 2026 15:26:56 +0200 Subject: [PATCH] Let's build our own wall --- bin/omarchy-first-run | 1 - bin/omarchy-menu | 3 +- bin/omarchy-restart-skwd-wall | 5 - bin/omarchy-theme-bg-next | 6 +- bin/omarchy-theme-bg-set | 5 +- bin/omarchy-theme-bg-switcher | 20 ++ bin/omarchy-theme-set | 1 - bin/omarchy-theme-set-skwd-wall | 14 - config/skwd-wall/config.json | 79 ----- default/hypr/autostart.lua | 1 + default/hypr/bindings/utilities.lua | 2 +- default/quickshell/wallpaper-switcher.qml | 275 ++++++++++++++++++ default/themed/skwd-wall-colors.json.tpl | 27 -- .../themed/wallpaper-switcher-colors.json.tpl | 5 + install/first-run/skwd-wall.sh | 2 - install/omarchy-base.packages | 2 +- migrations/1778494413.sh | 16 - 17 files changed, 309 insertions(+), 155 deletions(-) delete mode 100755 bin/omarchy-restart-skwd-wall create mode 100755 bin/omarchy-theme-bg-switcher delete mode 100755 bin/omarchy-theme-set-skwd-wall delete mode 100644 config/skwd-wall/config.json create mode 100644 default/quickshell/wallpaper-switcher.qml delete mode 100644 default/themed/skwd-wall-colors.json.tpl create mode 100644 default/themed/wallpaper-switcher-colors.json.tpl delete mode 100644 install/first-run/skwd-wall.sh delete mode 100644 migrations/1778494413.sh diff --git a/bin/omarchy-first-run b/bin/omarchy-first-run index 277b0b2d..97b41f80 100755 --- a/bin/omarchy-first-run +++ b/bin/omarchy-first-run @@ -18,7 +18,6 @@ if [[ -f $FIRST_RUN_MODE ]]; then bash "$OMARCHY_PATH/install/first-run/gnome-theme.sh" bash "$OMARCHY_PATH/install/first-run/gdk-scale.sh" bash "$OMARCHY_PATH/install/first-run/swayosd.sh" - bash "$OMARCHY_PATH/install/first-run/skwd-wall.sh" bash "$OMARCHY_PATH/install/first-run/gtk-primary-paste.sh" bash "$OMARCHY_PATH/install/first-run/elephant.sh" omarchy-hook-install post-update "$OMARCHY_PATH/install/first-run/install-voxtype.hook" diff --git a/bin/omarchy-menu b/bin/omarchy-menu index 22d95acb..274aef0a 100755 --- a/bin/omarchy-menu +++ b/bin/omarchy-menu @@ -326,7 +326,8 @@ show_theme_menu() { } show_background_menu() { - skwd wall toggle + background=$(omarchy-theme-bg-switcher) + [[ -n $background ]] && omarchy-theme-bg-set "$background" } show_font_menu() { diff --git a/bin/omarchy-restart-skwd-wall b/bin/omarchy-restart-skwd-wall deleted file mode 100755 index c83521d6..00000000 --- a/bin/omarchy-restart-skwd-wall +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# omarchy:summary=Restart Skwd-wall wallpaper daemon - -systemctl --user restart skwd-daemon.service 2>/dev/null || true diff --git a/bin/omarchy-theme-bg-next b/bin/omarchy-theme-bg-next index 2fa5c658..0356d6f6 100755 --- a/bin/omarchy-theme-bg-next +++ b/bin/omarchy-theme-bg-next @@ -40,9 +40,5 @@ else NEW_BACKGROUND="${BACKGROUNDS[$NEXT_INDEX]}" fi - # Set new background symlink - ln -nsf "$NEW_BACKGROUND" "$CURRENT_BACKGROUND_LINK" - - # Apply the new background through Skwd-wall - skwd wall apply "{\"type\":\"static\",\"path\":\"$CURRENT_BACKGROUND_LINK\"}" >/dev/null + omarchy-theme-bg-set "$NEW_BACKGROUND" fi diff --git a/bin/omarchy-theme-bg-set b/bin/omarchy-theme-bg-set index 1eb00009..9c695d0e 100755 --- a/bin/omarchy-theme-bg-set +++ b/bin/omarchy-theme-bg-set @@ -20,5 +20,6 @@ fi # Create symlink to the new background ln -nsf "$BACKGROUND" "$CURRENT_BACKGROUND_LINK" -# Apply the new background through Skwd-wall -skwd wall apply "{\"type\":\"static\",\"path\":\"$CURRENT_BACKGROUND_LINK\"}" >/dev/null +# Kill existing swaybg and start new one +pkill -x swaybg +setsid uwsm-app -- swaybg -i "$CURRENT_BACKGROUND_LINK" -m fill >/dev/null 2>&1 & diff --git a/bin/omarchy-theme-bg-switcher b/bin/omarchy-theme-bg-switcher new file mode 100755 index 00000000..a39c30fc --- /dev/null +++ b/bin/omarchy-theme-bg-switcher @@ -0,0 +1,20 @@ +#!/bin/bash + +# omarchy:summary=Open the Omarchy wallpaper switcher +# omarchy:group=theme +# omarchy:name=bg-switcher +# omarchy:aliases=omarchy wallpaper + +OMARCHY_PATH=${OMARCHY_PATH:-$HOME/.local/share/omarchy} +selection_file=$(mktemp) +trap 'rm -f "$selection_file"' EXIT +theme_name=$(cat "$HOME/.config/omarchy/current/theme.name" 2>/dev/null) + +OMARCHY_WALLPAPER_SELECTION_FILE=$selection_file \ + OMARCHY_STOCK_BACKGROUNDS_DIR="$HOME/.config/omarchy/current/theme/backgrounds" \ + OMARCHY_USER_BACKGROUNDS_DIR="$HOME/.config/omarchy/backgrounds/$theme_name" \ + quickshell -p "$OMARCHY_PATH/default/quickshell/wallpaper-switcher.qml" >/dev/null + +if [[ -s $selection_file ]]; then + cat "$selection_file" +fi diff --git a/bin/omarchy-theme-set b/bin/omarchy-theme-set index a10d6c27..fe57c2af 100755 --- a/bin/omarchy-theme-set +++ b/bin/omarchy-theme-set @@ -68,7 +68,6 @@ omarchy-theme-set-browser omarchy-theme-set-vscode omarchy-theme-set-obsidian omarchy-theme-set-keyboard -omarchy-theme-set-skwd-wall # Call hook on theme set omarchy-hook theme-set "$THEME_NAME" >/dev/null diff --git a/bin/omarchy-theme-set-skwd-wall b/bin/omarchy-theme-set-skwd-wall deleted file mode 100755 index 787908eb..00000000 --- a/bin/omarchy-theme-set-skwd-wall +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -# omarchy:summary=Refresh Skwd-wall after an Omarchy theme change -# omarchy:hidden=true - -THEME_COLORS="$HOME/.config/omarchy/current/theme/skwd-wall-colors.json" -SKWD_COLORS="$HOME/.cache/skwd-wall/colors.json" - -if [[ -f $THEME_COLORS ]]; then - mkdir -p "$(dirname "$SKWD_COLORS")" - cp "$THEME_COLORS" "$SKWD_COLORS" -fi - -skwd wall cache_rebuild >/dev/null diff --git a/config/skwd-wall/config.json b/config/skwd-wall/config.json deleted file mode 100644 index aaa2d7e6..00000000 --- a/config/skwd-wall/config.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "compositor": "hyprland", - "monitor": "", - "general": { - "locale": "", - "closeOnSelection": true, - "reopenAtLastSelection": false, - "filterBarAlwaysVisible": false, - "notifyOnWallpaperChange": false, - "randomIncludeWE": false, - "randomIncludeFavourites": false - }, - "paths": { - "wallpaper": "~/.config/omarchy/current/theme/backgrounds", - "videoWallpaper": "~/videowalls", - "cache": "", - "templates": "", - "scripts": "", - "steam": "", - "steamWorkshop": "", - "steamWeAssets": "" - }, - "features": { - "matugen": false, - "ollama": false, - "steam": false, - "wallhaven": false - }, - "colorSource": "magick", - "ollama": { - "url": "http://localhost:11434", - "model": "gemma3:4b", - "consolidateEnabled": true - }, - "steam": { - "apiKey": "", - "username": "" - }, - "wallhaven": { - "apiKey": "" - }, - "matugen": { - "schemeType": "scheme-fidelity", - "mode": "dark" - }, - "integrations": [], - "components": { - "wallpaperSelector": { - "displayMode": "slices", - "sliceSpacing": -30, - "hexScrollStep": 1, - "customPresets": {}, - "expandedWidth": 768, - "sliceHeight": 432, - "sliceWidth": 108, - "visibleCount": 14, - "skewOffset": 28 - } - }, - "wallpaperMute": true, - "pickOnlyMode": false, - "externalWallpaperCommand": "", - "postProcessing": [], - "performance": { - "imageOptimizePreset": "balanced", - "imageOptimizeResolution": "4k", - "videoConvertPreset": "balanced", - "videoConvertResolution": "2k", - "autoOptimizeImages": false, - "autoConvertVideos": false, - "imageTrashDays": 7, - "videoTrashDays": 7, - "autoDeleteImageTrash": false, - "autoDeleteVideoTrash": false - }, - "transition": { - "durationMs": 200 - } -} diff --git a/default/hypr/autostart.lua b/default/hypr/autostart.lua index 3ac29274..7eed5fbb 100644 --- a/default/hypr/autostart.lua +++ b/default/hypr/autostart.lua @@ -3,6 +3,7 @@ hl.on("hyprland.start", function() hl.exec_cmd("uwsm-app -- mako") 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("/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/hypr/bindings/utilities.lua b/default/hypr/bindings/utilities.lua index 3deccd4b..57a3e70e 100644 --- a/default/hypr/bindings/utilities.lua +++ b/default/hypr/bindings/utilities.lua @@ -15,7 +15,7 @@ hl.bind("SUPER + SHIFT + CTRL + UP", hl.dsp.exec_cmd("omarchy-style-waybar-posit hl.bind("SUPER + SHIFT + CTRL + DOWN", hl.dsp.exec_cmd("omarchy-style-waybar-position bottom"), { description = "Move Waybar to bottom" }) hl.bind("SUPER + SHIFT + CTRL + LEFT", hl.dsp.exec_cmd("omarchy-style-waybar-position left"), { description = "Move Waybar to left" }) hl.bind("SUPER + SHIFT + CTRL + RIGHT", hl.dsp.exec_cmd("omarchy-style-waybar-position right"), { description = "Move Waybar to right" }) -hl.bind("SUPER + CTRL + SPACE", hl.dsp.exec_cmd("skwd wall toggle"), { description = "Wallpaper switcher" }) +hl.bind("SUPER + CTRL + SPACE", hl.dsp.exec_cmd("bash -lc 'background=$(omarchy-theme-bg-switcher) && [[ -n $background ]] && omarchy-theme-bg-set \"$background\"'"), { description = "Wallpaper switcher" }) hl.bind("SUPER + SHIFT + CTRL + SPACE", hl.dsp.exec_cmd("omarchy-menu theme"), { description = "Theme menu" }) hl.bind("SUPER + BACKSPACE", hl.dsp.exec_cmd("omarchy-hyprland-window-transparency-toggle"), { description = "Toggle window transparency" }) hl.bind("SUPER + SHIFT + BACKSPACE", hl.dsp.exec_cmd("omarchy-hyprland-window-gaps-toggle"), { description = "Toggle window gaps" }) diff --git a/default/quickshell/wallpaper-switcher.qml b/default/quickshell/wallpaper-switcher.qml new file mode 100644 index 00000000..b6fdf16c --- /dev/null +++ b/default/quickshell/wallpaper-switcher.qml @@ -0,0 +1,275 @@ +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import QtQuick +import QtQuick.Effects +import QtQuick.Shapes + +ShellRoot { + id: root + + property string stockBackgroundsDir: Quickshell.env("OMARCHY_STOCK_BACKGROUNDS_DIR") || (Quickshell.env("HOME") + "/.config/omarchy/current/theme/backgrounds") + property string userBackgroundsDir: Quickshell.env("OMARCHY_USER_BACKGROUNDS_DIR") + property string currentBackground: Quickshell.env("HOME") + "/.config/omarchy/current/background" + property string selectionFile: Quickshell.env("OMARCHY_WALLPAPER_SELECTION_FILE") + property int selectedIndex: 0 + property color accent: "#798186" + property color background: "#101315" + property color foreground: "#cacccc" + property int expandedWidth: 768 + property int sliceWidth: 108 + property int sliceHeight: 432 + property int sliceSpacing: -30 + property int skewOffset: 28 + + function fileUrl(path) { + return "file://" + path.split("/").map(encodeURIComponent).join("/") + } + + function shellQuote(value) { + return "'" + String(value).replace(/'/g, "'\\''") + "'" + } + + function currentPath() { + if (wallpaperModel.count === 0) return "" + return wallpaperModel.get(selectedIndex, "filePath") + } + + function select(index) { + if (index < 0 || index >= wallpaperModel.count) return + selectedIndex = index + list.currentIndex = index + list.positionViewAtIndex(index, ListView.Center) + } + + function applySelected() { + var path = currentPath() + if (!path) return + applyProc.command = ["bash", "-lc", "printf '%s\\n' " + shellQuote(path) + " > " + shellQuote(selectionFile)] + applyProc.running = true + } + + ListModel { + id: wallpaperModel + } + + function addBackground(path) { + if (!path) return + var fileName = path.split("/").pop() + + for (var i = wallpaperModel.count - 1; i >= 0; i--) { + if (wallpaperModel.get(i).fileName === fileName) + wallpaperModel.remove(i) + } + + wallpaperModel.append({ filePath: path, fileName: fileName }) + } + + Process { + id: loadBackgroundsProc + command: ["bash", "-lc", "for dir in " + shellQuote(root.stockBackgroundsDir) + " " + shellQuote(root.userBackgroundsDir) + "; do [[ -d $dir ]] && find -L \"$dir\" -maxdepth 1 -type f \\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' \\) -print | sort; done"] + stdout: SplitParser { + onRead: function(data) { + var paths = data.split("\n") + for (var i = 0; i < paths.length; i++) + root.addBackground(paths[i].trim()) + } + } + } + + Component.onCompleted: loadBackgroundsProc.running = true + + FileView { + path: Quickshell.env("HOME") + "/.config/omarchy/current/theme/wallpaper-switcher-colors.json" + watchChanges: true + onLoaded: root.loadColors(text()) + onFileChanged: { reload(); root.loadColors(text()) } + } + + function loadColors(raw) { + try { + var colors = JSON.parse(raw || "{}") + root.accent = colors.primary || root.accent + root.background = colors.background || root.background + root.foreground = colors.backgroundText || root.foreground + } catch (e) {} + } + + Process { + id: applyProc + onExited: Qt.quit() + } + + PanelWindow { + id: panel + anchors { top: true; bottom: true; left: true; right: true } + color: "transparent" + WlrLayershell.namespace: "omarchy-wallpaper-switcher" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + exclusionMode: ExclusionMode.Ignore + + Rectangle { + anchors.fill: parent + color: Qt.rgba(0, 0, 0, 0.55) + } + + MouseArea { + anchors.fill: parent + onClicked: Qt.quit() + } + + Item { + id: card + width: Math.min(parent.width - 80, root.expandedWidth + 13 * (root.sliceWidth + root.sliceSpacing) + 40) + height: root.sliceHeight + 60 + anchors.centerIn: parent + + MouseArea { anchors.fill: parent; onClicked: {} } + + ListView { + id: list + anchors.top: parent.top + anchors.topMargin: 30 + anchors.bottom: parent.bottom + anchors.bottomMargin: 30 + anchors.horizontalCenter: parent.horizontalCenter + width: root.expandedWidth + 13 * (root.sliceWidth + root.sliceSpacing) + orientation: ListView.Horizontal + model: wallpaperModel + spacing: root.sliceSpacing + clip: false + focus: true + currentIndex: root.selectedIndex + preferredHighlightBegin: (width - root.expandedWidth) / 2 + preferredHighlightEnd: (width + root.expandedWidth) / 2 + highlightRangeMode: ListView.StrictlyEnforceRange + highlightMoveDuration: 120 + highlight: Item {} + header: Item { width: (list.width - root.expandedWidth) / 2; height: 1 } + footer: Item { width: (list.width - root.expandedWidth) / 2; height: 1 } + + Keys.onEscapePressed: Qt.quit() + Keys.onReturnPressed: root.applySelected() + Keys.onLeftPressed: root.select(Math.max(0, root.selectedIndex - 1)) + Keys.onRightPressed: root.select(Math.min(wallpaperModel.count - 1, root.selectedIndex + 1)) + + Component.onCompleted: forceActiveFocus() + + delegate: Item { + id: item + required property int index + required property string filePath + required property string fileName + + width: index === root.selectedIndex ? root.expandedWidth : root.sliceWidth + height: list.height + z: index === root.selectedIndex ? 100 : 50 - Math.min(Math.abs(index - root.selectedIndex), 40) + + readonly property real skAbs: Math.abs(root.skewOffset) + readonly property real topLeft: root.skewOffset >= 0 ? skAbs : 0 + readonly property real topRight: root.skewOffset >= 0 ? width : width - skAbs + readonly property real bottomRight: root.skewOffset >= 0 ? width - skAbs : width + readonly property real bottomLeft: root.skewOffset >= 0 ? 0 : skAbs + + Behavior on width { NumberAnimation { duration: 120; easing.type: Easing.OutCubic } } + + Item { + id: maskShape + anchors.fill: parent + visible: false + layer.enabled: true + + Shape { + anchors.fill: parent + antialiasing: true + preferredRendererType: Shape.CurveRenderer + ShapePath { + fillColor: "white" + strokeColor: "transparent" + startX: item.topLeft; startY: 0 + PathLine { x: item.topRight; y: 0 } + PathLine { x: item.bottomRight; y: item.height } + PathLine { x: item.bottomLeft; y: item.height } + PathLine { x: item.topLeft; y: 0 } + } + } + } + + Shape { + x: index === root.selectedIndex ? 4 : 2 + y: index === root.selectedIndex ? 10 : 5 + width: item.width + height: item.height + opacity: index === root.selectedIndex ? 0.5 : 0.32 + antialiasing: true + preferredRendererType: Shape.CurveRenderer + ShapePath { + fillColor: "#000000" + strokeColor: "transparent" + startX: item.topLeft; startY: 0 + PathLine { x: item.topRight; y: 0 } + PathLine { x: item.bottomRight; y: item.height } + PathLine { x: item.bottomLeft; y: item.height } + PathLine { x: item.topLeft; y: 0 } + } + } + + Item { + anchors.fill: parent + layer.enabled: true + layer.smooth: true + layer.effect: MultiEffect { + maskEnabled: true + maskSource: maskShape + maskThresholdMin: 0.3 + maskSpreadAtMin: 0.3 + } + + Image { + id: image + anchors.fill: parent + source: root.fileUrl(item.filePath) + fillMode: Image.PreserveAspectCrop + asynchronous: true + cache: true + smooth: true + opacity: status === Image.Ready ? 1 : 0 + Behavior on opacity { NumberAnimation { duration: 120 } } + } + + Rectangle { + anchors.fill: parent + color: Qt.rgba(0, 0, 0, index === root.selectedIndex ? 0 : 0.42) + Behavior on color { ColorAnimation { duration: 120 } } + } + } + + Shape { + anchors.fill: parent + antialiasing: true + preferredRendererType: Shape.CurveRenderer + ShapePath { + fillColor: "transparent" + strokeColor: index === root.selectedIndex ? root.accent : Qt.rgba(0, 0, 0, 0.65) + strokeWidth: index === root.selectedIndex ? 3 : 1 + startX: item.topLeft; startY: 0 + PathLine { x: item.topRight; y: 0 } + PathLine { x: item.bottomRight; y: item.height } + PathLine { x: item.bottomLeft; y: item.height } + PathLine { x: item.topLeft; y: 0 } + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: root.select(index) + onClicked: index === root.selectedIndex ? root.applySelected() : root.select(index) + } + } + } + } + } +} diff --git a/default/themed/skwd-wall-colors.json.tpl b/default/themed/skwd-wall-colors.json.tpl deleted file mode 100644 index 60d0c05e..00000000 --- a/default/themed/skwd-wall-colors.json.tpl +++ /dev/null @@ -1,27 +0,0 @@ -{ - "primary": "{{ accent }}", - "primaryText": "{{ selection_foreground }}", - "primaryContainer": "{{ selection_background }}", - "primaryContainerText": "{{ selection_foreground }}", - "onPrimary": "{{ selection_foreground }}", - "secondary": "{{ color2 }}", - "secondaryText": "{{ background }}", - "secondaryContainer": "{{ color8 }}", - "secondaryContainerText": "{{ foreground }}", - "tertiary": "{{ active_border_color }}", - "tertiaryText": "{{ background }}", - "tertiaryContainer": "{{ color12 }}", - "tertiaryContainerText": "{{ foreground }}", - "background": "{{ background }}", - "backgroundText": "{{ foreground }}", - "surface": "{{ background }}", - "surfaceText": "{{ foreground }}", - "surfaceVariant": "{{ color8 }}", - "surfaceVariantText": "{{ foreground }}", - "surfaceContainer": "{{ color10 }}", - "outline": "{{ active_border_color }}", - "shadow": "#000000", - "inverseSurface": "{{ foreground }}", - "inverseSurfaceText": "{{ background }}", - "inversePrimary": "{{ accent }}" -} diff --git a/default/themed/wallpaper-switcher-colors.json.tpl b/default/themed/wallpaper-switcher-colors.json.tpl new file mode 100644 index 00000000..96a92436 --- /dev/null +++ b/default/themed/wallpaper-switcher-colors.json.tpl @@ -0,0 +1,5 @@ +{ + "primary": "{{ accent }}", + "background": "{{ background }}", + "backgroundText": "{{ foreground }}" +} diff --git a/install/first-run/skwd-wall.sh b/install/first-run/skwd-wall.sh deleted file mode 100644 index a3e95d1e..00000000 --- a/install/first-run/skwd-wall.sh +++ /dev/null @@ -1,2 +0,0 @@ -systemctl --user daemon-reload -systemctl --user enable --now skwd-daemon.service diff --git a/install/omarchy-base.packages b/install/omarchy-base.packages index 6ee75819..acdddc47 100644 --- a/install/omarchy-base.packages +++ b/install/omarchy-base.packages @@ -117,7 +117,7 @@ spotify socat starship sushi -skwd-wall +swaybg swayosd system-config-printer tesseract diff --git a/migrations/1778494413.sh b/migrations/1778494413.sh deleted file mode 100644 index 7e045b50..00000000 --- a/migrations/1778494413.sh +++ /dev/null @@ -1,16 +0,0 @@ -echo "Replace swaybg with Skwd-wall for wallpaper selection" - -omarchy-pkg-add skwd-wall -omarchy-pkg-drop swaybg - -if [[ ! -f ~/.config/skwd-wall/config.json ]]; then - mkdir -p ~/.config/skwd-wall - cp "$OMARCHY_PATH/config/skwd-wall/config.json" ~/.config/skwd-wall/config.json -fi - -omarchy-theme-refresh - -systemctl --user daemon-reload -systemctl --user enable --now skwd-daemon.service 2>/dev/null || true - -skwd wall apply "{\"type\":\"static\",\"path\":\"$HOME/.config/omarchy/current/background\"}" >/dev/null 2>&1 || true