174 lines
6.4 KiB
QML
174 lines
6.4 KiB
QML
pragma Singleton
|
|
|
|
// The desktop background.
|
|
//
|
|
// hyprpaper owns the actual painting; this owns choosing. Two things are worth
|
|
// knowing about hyprpaper 0.8:
|
|
//
|
|
// * Its IPC is much smaller than the documentation for older versions
|
|
// suggests. `wallpaper <output>,<path>` and `listactive` work; `preload`,
|
|
// `listloaded`, `unload`, and `reload` all answer "invalid hyprpaper
|
|
// request". So there is no preload step -- setting is a single call.
|
|
// * hyprpaper.conf lives in the Panama repo via the ~/.config/hypr symlink,
|
|
// so it cannot be rewritten at runtime without dirtying a tracked file.
|
|
// The chosen wallpaper therefore lives in the shared settings store like
|
|
// every other preference, and is re-applied when the shell starts.
|
|
//
|
|
// The argument is "<output>,<path>", so a path containing a comma would be
|
|
// parsed as a different request. The schema's pattern rejects those, and the
|
|
// value is passed as a single argv element rather than through a shell.
|
|
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import QtQuick
|
|
import qs.config
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
// Absolute paths of candidate images, newest first.
|
|
property var available: []
|
|
property string active: ""
|
|
property string lastError: ""
|
|
property bool scanning: false
|
|
|
|
readonly property string configured: DesktopPreferences.get("wallpaperPath")
|
|
readonly property string shippedPath: `${Quickshell.env("HOME")}/Pictures/Wallpapers/faroe_islands.jpg`
|
|
|
|
// Directories searched for wallpapers, in order. Screenshots are
|
|
// deliberately excluded: a folder of 300 screenshots is not a wallpaper
|
|
// picker, and including it made the grid useless on this machine.
|
|
readonly property var searchRoots: [
|
|
`${Quickshell.env("HOME")}/Pictures/Wallpapers`,
|
|
`${Quickshell.env("HOME")}/Pictures/Backgrounds`,
|
|
`${Quickshell.env("HOME")}/.local/share/backgrounds`,
|
|
"/usr/share/backgrounds"
|
|
]
|
|
|
|
Process {
|
|
id: scan
|
|
|
|
// -print0 would be safer against odd filenames, but the schema already
|
|
// rejects paths containing commas or newlines, and this list is only
|
|
// ever offered as candidates -- the value that gets stored is validated
|
|
// again on the way in.
|
|
command: ["bash", "-lc",
|
|
"find " + root.searchRoots.map(dir => `'${dir}'`).join(" ")
|
|
+ " -maxdepth 2 -type f \\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' \\)"
|
|
+ " -printf '%T@ %p\\n' 2>/dev/null | sort -rn | cut -d' ' -f2- | head -60"]
|
|
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
const paths = this.text.split("\n").map(line => line.trim()).filter(line => line.length > 0);
|
|
root.available = paths;
|
|
root.scanning = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: activeQuery
|
|
command: ["hyprctl", "hyprpaper", "listactive"]
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
// "DP-2: /path/to/image.jpg", one line per output.
|
|
const first = this.text.split("\n").find(line => line.indexOf(":") > 0);
|
|
root.active = first ? first.slice(first.indexOf(":") + 1).trim() : "";
|
|
}
|
|
}
|
|
}
|
|
|
|
// hyprpaper requires an explicit output name: the "<empty>,<path>" form that
|
|
// older versions accepted as "all outputs" is silently ignored by 0.8, so a
|
|
// wallpaper set that way appears to succeed and never changes. Outputs are
|
|
// therefore walked one at a time.
|
|
Process {
|
|
id: apply
|
|
|
|
property string requested: ""
|
|
property string storedValue: ""
|
|
property var remaining: []
|
|
|
|
onExited: (exitCode, exitStatus) => {
|
|
if (exitCode !== 0) {
|
|
root.lastError = "hyprpaper could not load that image.";
|
|
apply.remaining = [];
|
|
return;
|
|
}
|
|
if (apply.remaining.length > 0) {
|
|
const next = apply.remaining[0];
|
|
apply.remaining = apply.remaining.slice(1);
|
|
apply.exec(["hyprctl", "hyprpaper", "wallpaper", `${next},${apply.requested}`]);
|
|
return;
|
|
}
|
|
root.lastError = "";
|
|
DesktopPreferences.set("wallpaperPath", apply.storedValue);
|
|
root.refreshActive();
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
root.rescan();
|
|
root.refreshActive();
|
|
restore.restart();
|
|
}
|
|
|
|
// hyprpaper is started by the compositor's autostart, so it may not be
|
|
// listening yet when the shell comes up. Re-applying the stored choice
|
|
// after a short delay makes the wallpaper survive a reboot without needing
|
|
// hyprpaper.conf to know about it.
|
|
Timer {
|
|
id: restore
|
|
interval: 1500
|
|
onTriggered: {
|
|
const stored = root.configured;
|
|
if (stored !== "" && stored !== root.active)
|
|
root.set(stored);
|
|
}
|
|
}
|
|
|
|
function rescan(): void {
|
|
if (scan.running)
|
|
return;
|
|
root.scanning = true;
|
|
scan.running = true;
|
|
}
|
|
|
|
function refreshActive(): void {
|
|
if (!activeQuery.running)
|
|
activeQuery.running = true;
|
|
}
|
|
|
|
// Applies to every connected output. Returns false when the path is not one
|
|
// the schema will accept, so a caller can report the refusal.
|
|
function set(path: string): bool {
|
|
const effectivePath = path === "" ? root.shippedPath : path;
|
|
if (PreferenceSchema.coerce("wallpaperPath", effectivePath) === undefined) {
|
|
root.lastError = "That file path cannot be used as a wallpaper.";
|
|
return false;
|
|
}
|
|
if (apply.running)
|
|
return false;
|
|
|
|
apply.requested = effectivePath;
|
|
apply.storedValue = path;
|
|
|
|
const outputs = Quickshell.screens.map(screen => screen.name).filter(name => !!name);
|
|
if (outputs.length === 0) {
|
|
root.lastError = "No display to set a wallpaper on.";
|
|
return false;
|
|
}
|
|
|
|
apply.remaining = outputs.slice(1);
|
|
apply.exec(["hyprctl", "hyprpaper", "wallpaper", `${outputs[0]},${effectivePath}`]);
|
|
return true;
|
|
}
|
|
|
|
// The display name for a path: the file's own name, without extension,
|
|
// with separators turned into spaces.
|
|
function titleFor(path: string): string {
|
|
const file = String(path).split("/").pop();
|
|
return file.replace(/\.[^.]+$/, "").replace(/[_-]+/g, " ");
|
|
}
|
|
}
|