Krolyxon
Branch master

Branches

Refs →

aurora.configquickshellauroraWall.qml

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