Krolyxon
Commit db7ac21

Branches

Refs →

aurora.configquickshellauroraWall.qml

166 lines · 6.9 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 (all colours, terminal included; Theme then
8// recolours itself). 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 function apply(path) {
53 if (!path) return
54 current = path
55 Quickshell.execDetached(["sh", "-c", applyJob, "sh", path, conf, Settings.matuScheme, String(Settings.matuContrast)])
56 }
57
58 // matugen only. --prefer: v4 otherwise asks on stdin when the image has several source colours.
59 // With "missing", only if there is no output yet (fresh install).
60 readonly property string matuJob:
61 '[ "$4" = missing ] && [ -f "$HOME/.cache/matugen/aurora.json" ] && exit 0; ' +
62 'matugen image "$1" -m dark -t "$2" --contrast="$3" --prefer saturation -q'
63 function recolor(onlyIfMissing) {
64 if (!current) return
65 Quickshell.execDetached(["sh", "-c", matuJob, "sh", current, Settings.matuScheme,
66 String(Settings.matuContrast), onlyIfMissing ? "missing" : ""])
67 }
68 Timer { id: recolorLater; interval: 400; onTriggered: root.recolor(false) }
69 Connections {
70 target: Settings
71 function onMatuSchemeChanged() { recolorLater.restart() }
72 function onMatuContrastChanged() { recolorLater.restart() }
73 }
74 // random from the picker's folder (Settings.wallFolder), never the one already shown
75 function random() {
76 if (!files.length) {
77 pendingRandom = true
78 if (!loading) listFolder(folder)
79 return
80 }
81 var pick = files.filter(f => f !== current)
82 if (!pick.length) pick = files
83 apply(pick[Math.floor(Math.random() * pick.length)])
84 }
85
86 Process {
87 id: dirsProc
88 command: ["find", root.rootDir, "-mindepth", "1", "-maxdepth", "1", "-type", "d", "-not", "-name", ".*"]
89 stdout: StdioCollector {
90 onStreamFinished: {
91 var out = [], l = text.split("\n")
92 for (var i = 0; i < l.length; i++) {
93 var s = l[i].trim()
94 if (s !== "") out.push(s.substring(s.lastIndexOf("/") + 1))
95 }
96 out.sort()
97 root.folders = out
98 }
99 }
100 }
101
102 Process {
103 id: listProc
104 stdout: StdioCollector {
105 onStreamFinished: {
106 var out = [], l = text.split("\n")
107 for (var i = 0; i < l.length; i++) if (l[i] !== "") out.push(l[i])
108 out.sort()
109 root.files = out
110 root.loading = false
111 if (root.pendingRandom) { root.pendingRandom = false; if (out.length) root.random() }
112 root.makeThumbs()
113 }
114 }
115 }
116
117 // ---- thumbnails ----
118 // one background job per folder: 4 parallel `magick` runs, prints each hash when its thumb exists
119 readonly property string thumbJob:
120 'f="$1"; h=$(printf %s "$f" | md5sum | cut -c1-32); t="$0/$h.jpg"; ' +
121 'if [ ! -f "$t" ]; then ' +
122 ' magick "$f[0]" -thumbnail 320x180^ -gravity center -extent 320x180 -quality 82 "$t.part.jpg" 2>/dev/null ' +
123 ' && mv "$t.part.jpg" "$t"; ' +
124 'fi; [ -f "$t" ] && echo "$h"'
125 function makeThumbs() {
126 if (!files.length) return
127 thumbProc.command = ["sh", "-c",
128 'j="$1"; d="$2"; shift 2; mkdir -p "$d"; printf "%s\\0" "$@" | xargs -0 -n1 -P4 sh -c "$j" "$d"',
129 "sh", thumbJob, thumbDir].concat(files)
130 thumbProc.running = true
131 }
132 property var pendingReady: ({})
133 Timer {
134 id: flush
135 interval: 250
136 onTriggered: {
137 var r = root.ready
138 for (var k in root.pendingReady) r[k] = true
139 root.pendingReady = {}
140 root.ready = r
141 root.readyChanged()
142 }
143 }
144 Process {
145 id: thumbProc
146 stdout: SplitParser {
147 onRead: line => {
148 root.pendingReady[line.trim()] = true
149 if (!flush.running) flush.start()
150 }
151 }
152 }
153
154 // current wallpaper, straight from hyprpaper.conf. None yet (fresh install): pick a random one.
155 FileView {
156 path: root.conf
157 watchChanges: true
158 onFileChanged: reload()
159 onLoadFailed: root.random()
160 onLoaded: {
161 var m = text().match(/^\s*path\s*=\s*(.+)$/m)
162 if (m) root.current = m[1].trim()
163 if (!root.checkedColours) { root.checkedColours = true; root.recolor(true) }
164 }
165 }
166}