aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/appearance/WallEngine.qml
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/modules/appearance/WallEngine.qml')
-rw-r--r--desktop/modules/appearance/WallEngine.qml115
1 files changed, 115 insertions, 0 deletions
diff --git a/desktop/modules/appearance/WallEngine.qml b/desktop/modules/appearance/WallEngine.qml
new file mode 100644
index 0000000..9d90245
--- /dev/null
+++ b/desktop/modules/appearance/WallEngine.qml
@@ -0,0 +1,115 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// 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 QtQuick
+import "../.."
+import "Shuffle.js" as Shuffle
+
+// The wallpaper engine: both wallpapers on a timer, from a list per screen
+// picked with the heart in the picker. Ticks go through Wallpapers.run like
+// Apply, so the burn plays, but as the light run: udt-accent leaves GRUB, the
+// homepage and SDDM alone. Each screen has a home, the wallpaper last chosen
+// for it by hand, and switching off goes back to it with a full run, so those
+// three match the desktop again. See the spec,
+// docs/superpowers/specs/2026-10-29-wallpaper-engine-design.md.
+Singleton {
+ id: root
+
+ readonly property var cfg: Prefs.wallEngine
+ readonly property bool on: cfg.on
+
+ function list(which) { return which === "V" ? cfg.v : cfg.h; }
+ function listed(path, which) { return list(which).indexOf(path) >= 0; }
+ // ponytail: present means in the gallery's last scan, so a file deleted
+ // since stays drawable until a rescan, and wallp then skips it.
+ function present(path) { return Wallpapers.files.indexOf(path) >= 0; }
+
+ function toggle(path, which) {
+ const l = list(which).slice();
+ const i = l.indexOf(path);
+ if (i >= 0) l.splice(i, 1);
+ else l.push(path);
+ Prefs.setWallEngine(which === "V" ? { v: l } : { h: l });
+ }
+
+ function setOn(v) {
+ if (v === on) return;
+ if (v) {
+ Prefs.setWallEngine({ on: true, homeH: Wallpapers.currentH, homeV: Wallpapers.currentV });
+ } else {
+ Prefs.setWallEngine({ on: false });
+ goHome();
+ }
+ }
+
+ function setMinutes(m) {
+ if (Number.isInteger(m) && m >= 1 && m !== cfg.minutes) Prefs.setWallEngine({ minutes: m });
+ }
+
+ // ponytail: in memory, so a restart starts a new round.
+ property var bagH: []
+ property var bagV: []
+
+ // A screen whose list is empty, or whose pick is already up, is left out.
+ function tick() {
+ if (Wallpapers.applying) return;
+ const h = Shuffle.draw(cfg.h.filter(present), bagH, Wallpapers.currentH);
+ const v = Shuffle.draw(cfg.v.filter(present), bagV, Wallpapers.currentV);
+ const ph = h.pick !== Wallpapers.currentH ? h.pick : "";
+ const pv = v.pick !== Wallpapers.currentV ? v.pick : "";
+ if (Wallpapers.run(ph, pv, "tick")) {
+ bagH = h.bag;
+ bagV = v.bag;
+ }
+ }
+
+ function next() {
+ tick();
+ timer.restart();
+ }
+
+ // A switch-off during a run cannot start its own; it goes when that run
+ // finishes.
+ property bool homeWaiting: false
+ function goHome() {
+ const h = cfg.homeH !== Wallpapers.currentH ? cfg.homeH : "";
+ const v = cfg.homeV !== Wallpapers.currentV ? cfg.homeV : "";
+ if (h === "" && v === "") return;
+ homeWaiting = !Wallpapers.run(h, v, "home");
+ }
+
+ Timer {
+ id: timer
+ interval: root.cfg.minutes * 60000
+ repeat: true
+ running: root.on
+ onTriggered: root.tick()
+ }
+
+ Connections {
+ target: Wallpapers
+ // A pick by hand is the new home, and stays up a full interval.
+ function onManualApplied(h, v) {
+ if (!root.on) return;
+ Prefs.setWallEngine({ homeH: h !== "" ? h : root.cfg.homeH,
+ homeV: v !== "" ? v : root.cfg.homeV });
+ timer.restart();
+ }
+ function onFinished() {
+ if (!root.homeWaiting || root.on) return;
+ root.homeWaiting = false;
+ root.goHome();
+ }
+ }
+}