// Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. pragma Singleton import Quickshell import Quickshell.Io import QtQuick // The wallpaper side. Setting one is `wallp`'s job: it knows about the two // outputs, the saved state and the accent run, and none of that is worth // reimplementing here. Singleton { id: root readonly property string home: Quickshell.env("HOME") readonly property string dir: `${home}/Pictures/wallpapers` // Which screen a pick applies to. wallp calls them H and V. property string target: "H" // Staged picks, empty until something is chosen. Selecting does not set // anything: both screens can be composed and applied together, which is // also a single wallp call rather than two. property string pendingH: "" property string pendingV: "" readonly property bool dirty: pendingH !== "" || pendingV !== "" function pick(path) { if (target === "V") pendingV = path; else pendingH = path; } function clearPending() { pendingH = ""; pendingV = ""; } // What a screen should show: the staged pick if there is one, else what // is actually set. function shown(which) { if (which === "V") return pendingV !== "" ? pendingV : currentV; return pendingH !== "" ? pendingH : currentH; } property list files: [] property bool scanning: true property bool applying: false signal applied(bool ok, string message) // Sorted by most recent first: the wallpaper wanted now is usually one // added recently. -maxdepth keeps a stray git checkout from being walked. function refresh() { scanning = true; scanProc.running = false; scanProc.running = true; } Process { id: scanProc running: true command: ["sh", "-c", `find ${JSON.stringify(root.dir)} -maxdepth 2 -type f ` + `\\( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.webp' \\) ` + `-not -path '*/.*' -printf '%T@ %p\\n' 2>/dev/null | sort -rn | cut -d' ' -f2-`] stdout: StdioCollector { onStreamFinished: { root.files = text.trim().split("\n").filter(s => s.length); root.scanning = false; } } } function basename(path) { return path.slice(path.lastIndexOf("/") + 1); } // What is on each screen now. wallp writes these on every set, so // watching them keeps the mock honest even when set from a terminal. property string currentH: "" property string currentV: "" FileView { path: `${root.home}/.config/wallp/wall_h` watchChanges: true onFileChanged: reload() onLoaded: root.currentH = text().trim() } FileView { path: `${root.home}/.config/wallp/wall_v` watchChanges: true onFileChanged: reload() onLoaded: root.currentV = text().trim() } // wallp does the work, including running udt-accent, so the accent follows // the new wallpaper exactly as it does from the command line. Both screens // go in one call: wallp accepts H= and V= together. function apply() { if (applying || !dirty) return; applying = true; const args = []; if (pendingH !== "") args.push(`H=${JSON.stringify(pendingH)}`); if (pendingV !== "") args.push(`V=${JSON.stringify(pendingV)}`); applyProc.command = ["sh", "-c", `wallp --set ${args.join(" ")} >/dev/null 2>&1`]; applyProc.running = true; } Process { id: applyProc stderr: StdioCollector { id: applyErr } onExited: code => { root.applying = false; if (code === 0) root.clearPending(); root.applied(code === 0, code === 0 ? "" : (applyErr.text.trim() || `wallp exited ${code}`)); } } }