Let's build our own wall

This commit is contained in:
David Heinemeier Hansson
2026-05-11 15:28:33 -04:00
committed by Ryan Hughes
parent 7a988d8909
commit 333ec367df
17 changed files with 309 additions and 155 deletions
-1
View File
@@ -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"
+2 -1
View File
@@ -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() {
-5
View File
@@ -1,5 +0,0 @@
#!/bin/bash
# omarchy:summary=Restart Skwd-wall wallpaper daemon
systemctl --user restart skwd-daemon.service 2>/dev/null || true
+1 -5
View File
@@ -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
+3 -2
View File
@@ -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 &
+20
View File
@@ -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
-1
View File
@@ -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
-14
View File
@@ -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
-79
View File
@@ -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
}
}
+1
View File
@@ -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")
+1 -1
View File
@@ -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" })
+275
View File
@@ -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)
}
}
}
}
}
}
-27
View File
@@ -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 }}"
}
@@ -0,0 +1,5 @@
{
"primary": "{{ accent }}",
"background": "{{ background }}",
"backgroundText": "{{ foreground }}"
}
-2
View File
@@ -1,2 +0,0 @@
systemctl --user daemon-reload
systemctl --user enable --now skwd-daemon.service
+1 -1
View File
@@ -117,7 +117,7 @@ spotify
socat
starship
sushi
skwd-wall
swaybg
swayosd
system-config-printer
tesseract
-16
View File
@@ -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