Krolyxon
Commit bf9a79c

Branches

Refs →

aurora.configquickshellauroraWall.qml

167 lines · 7.0 KB · QML

1pragma Singleton
2import QtQuick
3import Quickshell
4import Quickshell.Io
5
6// Wallpapers: folder listing, cached thumbnails, and applying them. aurora owns the wallpaper:
7// apply() writes hyprpaper.conf, switches hyprpaper live, runs matugen (UI colours, Theme then recolours itself)
8// and pywal16 (terminal colours only). recolor() re-runs just matugen when the scheme/contrast setting changes.
9// ~/.local/bin/setwall is only a CLI front-end for the IPC calls (wallrandom / wall / wallset).
10Singleton {
11 id: root
12
13 readonly property string home: Quickshell.env("HOME")
14 property string rootDir: home + "/pix/wallpapers"
15 property string folder: Settings.wallFolder // default dusky-dark
16 readonly property string conf: home + "/.config/hypr/hyprpaper.conf"
17 readonly property string thumbDir: home + "/.cache/aurora/thumbs"
18
19 property var folders: [] // subfolder names; "" = everything under rootDir
20 property var files: [] // absolute paths of the current folder
21 property var ready: ({}) // md5(path) -> true once its thumbnail exists
22 property string current: "" // wallpaper hyprpaper is showing
23 property bool loading: false
24 property bool pendingRandom: false // random() asked before the folder was listed
25 property bool checkedColours: false // matugen output checked once at startup
26
27 function dirOf(name) { return name === "" ? rootDir : rootDir + "/" + name }
28 function thumbOf(path) { return thumbDir + "/" + Qt.md5(path) + ".jpg" }
29
30 function refresh() {
31 dirsProc.running = true
32 listFolder(folder)
33 }
34 function listFolder(name) {
35 folder = name
36 Settings.wallFolder = name
37 thumbProc.running = false
38 listProc.running = false
39 loading = true
40 listProc.command = ["find", dirOf(name), "-type", "f", "-not", "-path", "*/.*",
41 "-regextype", "posix-extended", "-iregex", ".*\\.(png|jpe?g|webp)"]
42 listProc.running = true
43 }
44 // hyprpaper.conf is the source of truth for the current wallpaper (hyprpaper reads it on start).
45 // If hyprpaper isn't running, let Hyprland start it so it doesn't live in aurora's service cgroup.
46 readonly property string applyJob:
47 'p="$1"; c="$2"; ' +
48 'printf "splash = false\\nwallpaper {\\n monitor =\\n path = %s\\n}\\n" "$p" > "$c"; ' +
49 'if pgrep -x hyprpaper >/dev/null; then hyprctl hyprpaper wallpaper ",$p"; ' +
50 'else hyprctl dispatch \'hl.dsp.exec_cmd("hyprpaper")\'; fi; ' +
51 'matugen image "$p" -m dark -t "$3" --contrast="$4" --prefer saturation -q; ' +
52 'wal -i "$p" -n -q'
53 function apply(path) {
54 if (!path) return
55 current = path
56 Quickshell.execDetached(["sh", "-c", applyJob, "sh", path, conf, Settings.matuScheme, String(Settings.matuContrast)])
57 }
58
59 // matugen only (UI colours). --prefer: v4 otherwise asks on stdin when the image has several source colours.
60 // With "missing", only if there is no output yet (fresh install).
61 readonly property string matuJob:
62 '[ "$4" = missing ] && [ -f "$HOME/.cache/matugen/aurora.json" ] && exit 0; ' +
63 'matugen image "$1" -m dark -t "$2" --contrast="$3" --prefer saturation -q'
64 function recolor(onlyIfMissing) {
65 if (!current) return
66 Quickshell.execDetached(["sh", "-c", matuJob, "sh", current, Settings.matuScheme,
67 String(Settings.matuContrast), onlyIfMissing ? "missing" : ""])
68 }
69 Timer { id: recolorLater; interval: 400; onTriggered: root.recolor(false) }
70 Connections {
71 target: Settings
72 function onMatuSchemeChanged() { recolorLater.restart() }
73 function onMatuContrastChanged() { recolorLater.restart() }
74 }
75 // random from the picker's folder (Settings.wallFolder), never the one already shown
76 function random() {
77 if (!files.length) {
78 pendingRandom = true
79 if (!loading) listFolder(folder)
80 return
81 }
82 var pick = files.filter(f => f !== current)
83 if (!pick.length) pick = files
84 apply(pick[Math.floor(Math.random() * pick.length)])
85 }
86
87 Process {
88 id: dirsProc
89 command: ["find", root.rootDir, "-mindepth", "1", "-maxdepth", "1", "-type", "d", "-not", "-name", ".*"]
90 stdout: StdioCollector {
91 onStreamFinished: {
92 var out = [], l = text.split("\n")
93 for (var i = 0; i < l.length; i++) {
94 var s = l[i].trim()
95 if (s !== "") out.push(s.substring(s.lastIndexOf("/") + 1))
96 }
97 out.sort()
98 root.folders = out
99 }
100 }
101 }
102
103 Process {
104 id: listProc
105 stdout: StdioCollector {
106 onStreamFinished: {
107 var out = [], l = text.split("\n")
108 for (var i = 0; i < l.length; i++) if (l[i] !== "") out.push(l[i])
109 out.sort()
110 root.files = out
111 root.loading = false
112 if (root.pendingRandom) { root.pendingRandom = false; if (out.length) root.random() }
113 root.makeThumbs()
114 }
115 }
116 }
117
118 // ---- thumbnails ----
119 // one background job per folder: 4 parallel `magick` runs, prints each hash when its thumb exists
120 readonly property string thumbJob:
121 'f="$1"; h=$(printf %s "$f" | md5sum | cut -c1-32); t="$0/$h.jpg"; ' +
122 'if [ ! -f "$t" ]; then ' +
123 ' magick "$f[0]" -thumbnail 320x180^ -gravity center -extent 320x180 -quality 82 "$t.part.jpg" 2>/dev/null ' +
124 ' && mv "$t.part.jpg" "$t"; ' +
125 'fi; [ -f "$t" ] && echo "$h"'
126 function makeThumbs() {
127 if (!files.length) return
128 thumbProc.command = ["sh", "-c",
129 'j="$1"; d="$2"; shift 2; mkdir -p "$d"; printf "%s\\0" "$@" | xargs -0 -n1 -P4 sh -c "$j" "$d"',
130 "sh", thumbJob, thumbDir].concat(files)
131 thumbProc.running = true
132 }
133 property var pendingReady: ({})
134 Timer {
135 id: flush
136 interval: 250
137 onTriggered: {
138 var r = root.ready
139 for (var k in root.pendingReady) r[k] = true
140 root.pendingReady = {}
141 root.ready = r
142 root.readyChanged()
143 }
144 }
145 Process {
146 id: thumbProc
147 stdout: SplitParser {
148 onRead: line => {
149 root.pendingReady[line.trim()] = true
150 if (!flush.running) flush.start()
151 }
152 }
153 }
154
155 // current wallpaper, straight from hyprpaper.conf. None yet (fresh install): pick a random one.
156 FileView {
157 path: root.conf
158 watchChanges: true
159 onFileChanged: reload()
160 onLoadFailed: root.random()
161 onLoaded: {
162 var m = text().match(/^\s*path\s*=\s*(.+)$/m)
163 if (m) root.current = m[1].trim()
164 if (!root.checkedColours) { root.checkedColours = true; root.recolor(true) }
165 }
166 }
167}