121 lines
4.0 KiB
QML
121 lines
4.0 KiB
QML
pragma Singleton
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import "root:/config"
|
|
|
|
// The wallpaper currently on screen, per monitor.
|
|
//
|
|
// The lock screen used to point at a hardcoded path in Theme.qml, which meant
|
|
// it showed whatever picture was correct on the day that line was written —
|
|
// takemi_2 while hyprpaper had been switched to takemi_1. Anything that puts
|
|
// the wallpaper on screen should ask hyprpaper what it is actually displaying,
|
|
// which is what this does.
|
|
//
|
|
// hyprpaper is the source of truth at runtime; hyprpaper.conf is the fallback
|
|
// for the window between the shell starting and hyprpaper answering, and for
|
|
// the case where hyprpaper is not running at all.
|
|
Singleton {
|
|
id: root
|
|
|
|
// monitor name -> file path. Monitors hyprpaper set with a blank `monitor =`
|
|
// come back under the empty-string key.
|
|
property var byMonitor: ({})
|
|
|
|
// The wallpaper to use when no particular monitor is being asked about.
|
|
property string path: ""
|
|
|
|
readonly property url source: root.path !== ""
|
|
? Qt.resolvedUrl("file://" + root.path)
|
|
: Theme.fallbackWallpaper
|
|
|
|
// Path set from hyprpaper.conf, used until (and unless) hyprctl answers.
|
|
property string configPath: ""
|
|
|
|
signal changed()
|
|
|
|
function forMonitor(name: string): url {
|
|
const p = root.byMonitor[name];
|
|
return p ? Qt.resolvedUrl("file://" + p) : root.source;
|
|
}
|
|
|
|
function refresh() {
|
|
reader.running = true;
|
|
}
|
|
|
|
function _expand(p: string): string {
|
|
const s = p.trim();
|
|
if (s.startsWith("~/")) return Quickshell.env("HOME") + s.slice(1);
|
|
return s;
|
|
}
|
|
|
|
function _adopt(map: var, fallback: string) {
|
|
root.byMonitor = map;
|
|
|
|
// Prefer the focused monitor's wallpaper, then any monitor's, then the
|
|
// config. Object.values order is insertion order here, which is the
|
|
// order hyprpaper listed them in.
|
|
const vals = Object.keys(map).map(k => map[k]).filter(v => v !== "");
|
|
const next = vals.length > 0 ? vals[0] : fallback;
|
|
|
|
if (next !== root.path) {
|
|
root.path = next;
|
|
root.changed();
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: reader
|
|
command: ["hyprctl", "hyprpaper", "listactive"]
|
|
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
const map = {};
|
|
for (const line of text.split("\n")) {
|
|
// "eDP-1: /home/narl/.wallpapers/takemi_1.jpg". Split on the
|
|
// first colon only — the path may contain one.
|
|
const at = line.indexOf(":");
|
|
if (at === -1) continue;
|
|
const mon = line.slice(0, at).trim();
|
|
const file = line.slice(at + 1).trim();
|
|
if (file === "" || file.startsWith("invalid")) continue;
|
|
map[mon] = file;
|
|
}
|
|
root._adopt(map, root.configPath);
|
|
}
|
|
}
|
|
|
|
// hyprpaper down, or hyprctl missing. Fall back to the config file.
|
|
onExited: code => {
|
|
if (code !== 0 && root.path === "") root._adopt({}, root.configPath);
|
|
}
|
|
}
|
|
|
|
// hyprpaper has no change signal to subscribe to, so poll — but slowly. A
|
|
// wallpaper changes when a person decides to change it, not continuously.
|
|
Timer {
|
|
interval: 10000
|
|
running: true
|
|
repeat: true
|
|
triggeredOnStart: true
|
|
onTriggered: root.refresh()
|
|
}
|
|
|
|
// Editing the config is the other way the wallpaper changes here, and that
|
|
// one we can watch properly.
|
|
FileView {
|
|
id: conf
|
|
path: Quickshell.env("HOME") + "/.config/hypr/hyprpaper.conf"
|
|
watchChanges: true
|
|
onFileChanged: reload()
|
|
onLoaded: {
|
|
const m = /^\s*path\s*=\s*(.+)$/m.exec(text());
|
|
if (m) root.configPath = root._expand(m[1]);
|
|
// A config edit is usually followed by a hyprpaper reload, so go
|
|
// ask what actually took effect rather than trusting the file.
|
|
root.refresh();
|
|
}
|
|
}
|
|
}
|