1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-05 04:58:30 -04:00

wallpaper: migrate auto-cycling scheduling to backend

This commit is contained in:
bbedward
2026-06-30 13:11:29 -04:00
parent 161118122e
commit d704a0ba3d
10 changed files with 799 additions and 364 deletions
+15 -1
View File
@@ -129,9 +129,13 @@ Variants {
property bool useNextForEffect: false
property string pendingWallpaper: ""
property string _deferredSource: ""
// Held true from the moment a wallpaper change is initiated until it
// settles, so the paused render loop wakes and drives the async load
// and transition even when nothing else marks the window dirty.
property bool changePending: false
readonly property bool overviewBlurActive: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview && currentWallpaper.source !== ""
readonly property var backingWindow: Window.window
readonly property bool renderActive: !source || effectActive || overviewBlurActive || pendingWallpaper !== "" || _deferredSource !== "" || frameAnim.running || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading
readonly property bool renderActive: !source || effectActive || overviewBlurActive || pendingWallpaper !== "" || _deferredSource !== "" || changePending || frameAnim.running || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading
property int _settleFrames: 3
function invalidate() {
@@ -265,6 +269,7 @@ Variants {
root.useNextForEffect = false;
root.effectActive = false;
root.transitionProgress = 0.0;
root.changePending = false;
currentWallpaper.layer.enabled = false;
nextWallpaper.layer.enabled = false;
nextWallpaper.source = "";
@@ -502,6 +507,9 @@ Variants {
return;
}
root.changePending = true;
invalidate();
const formattedSource = source.startsWith("file://") ? source : encodeFileUrl(source);
if (!isInitialized || !currentWallpaper.source) {
@@ -688,6 +696,9 @@ Variants {
if (status === Image.Ready) {
imageMetrics.capture(implicitWidth, implicitHeight);
}
if (status === Image.Ready || status === Image.Error) {
root.changePending = false;
}
}
onSourceChanged: {
@@ -890,6 +901,8 @@ Variants {
property real imageHeight2: modelData.height
property real screenWidth: modelData.width
property real screenHeight: modelData.height
property real scrollX: 50
property real scrollY: 50
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/wp_fade.frag.qsb")
}
}
@@ -1048,6 +1061,7 @@ Variants {
currentWallpaper.layer.enabled = false;
nextWallpaper.layer.enabled = false;
root.effectActive = false;
root.changePending = false;
if (!root.pendingWallpaper)
return;
+4 -1
View File
@@ -55,6 +55,7 @@ Singleton {
signal evdevStateUpdate(var data)
signal gammaStateUpdate(var data)
signal themeAutoStateUpdate(var data)
signal wallpaperCycleUpdate(var data)
signal openUrlRequested(string url)
signal appPickerRequested(var data)
signal screensaverStateUpdate(var data)
@@ -68,7 +69,7 @@ Singleton {
property bool screensaverInhibited: false
property var screensaverInhibitors: []
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "bluetooth", "bluetooth.pairing", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "location", "sysupdate"]
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "wallpaper", "bluetooth", "bluetooth.pairing", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "location", "sysupdate"]
Component.onCompleted: {
if (socketPath && socketPath.length > 0) {
@@ -371,6 +372,8 @@ Singleton {
gammaStateUpdate(data);
} else if (service === "theme.auto") {
themeAutoStateUpdate(data);
} else if (service === "wallpaper") {
wallpaperCycleUpdate(data);
} else if (service === "browser.open_requested") {
if (data.target) {
if (data.requestType === "url" || !data.requestType) {
+144 -355
View File
@@ -22,62 +22,37 @@ Singleton {
return false;
}
readonly property bool shouldPauseCycling: fullscreenShowing || SessionService.locked
property string cachedCyclingTime: SessionData.wallpaperCyclingTime
property int cachedCyclingInterval: SessionData.wallpaperCyclingInterval
property string lastTimeCheck: ""
property var monitorTimers: ({})
property var monitorLastTimeChecks: ({})
readonly property bool serverSchedulingAvailable: DMSService.capabilities.includes("wallpaper")
property real lastCycleSeq: -1
property var monitorProcesses: ({})
Component {
id: monitorTimerComponent
Timer {
property string targetScreen: ""
running: false
repeat: true
onTriggered: {
if (typeof WallpaperCyclingService !== "undefined" && targetScreen !== "" && !WallpaperCyclingService.shouldPauseCycling) {
WallpaperCyclingService.cycleNextForMonitor(targetScreen);
}
Connections {
target: DMSService
function onWallpaperCycleUpdate(data) {
if (!data)
return;
const seq = data.cycleSeq || 0;
if (lastCycleSeq < 0) {
lastCycleSeq = seq;
return;
}
if (seq <= lastCycleSeq)
return;
lastCycleSeq = seq;
if (shouldPauseCycling)
return;
const target = data.target || "";
if (target === "") {
cycleToNextWallpaper();
} else {
cycleNextForMonitor(target);
}
}
}
Component {
id: monitorProcessComponent
Process {
property string targetScreenName: ""
property string currentWallpaper: ""
property bool goToPrevious: false
running: false
stdout: StdioCollector {
onStreamFinished: {
if (text && text.trim()) {
const files = text.trim().split('\n').filter(file => file.length > 0);
if (files.length <= 1)
return;
const wallpaperList = files.sort();
const currentPath = currentWallpaper;
let currentIndex = wallpaperList.findIndex(path => path === currentPath);
if (currentIndex === -1)
currentIndex = 0;
let targetIndex;
if (goToPrevious) {
targetIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1;
} else {
targetIndex = (currentIndex + 1) % wallpaperList.length;
}
const targetWallpaper = wallpaperList[targetIndex];
if (targetWallpaper && targetWallpaper !== currentPath) {
if (targetScreenName) {
SessionData.setMonitorWallpaper(targetScreenName, targetWallpaper);
} else {
SessionData.setWallpaper(targetWallpaper);
}
}
}
}
}
function onCapabilitiesReceived() {
lastCycleSeq = -1;
updateCyclingState();
}
}
@@ -93,17 +68,11 @@ Singleton {
}
function onWallpaperCyclingIntervalChanged() {
cachedCyclingInterval = SessionData.wallpaperCyclingInterval;
if (SessionData.wallpaperCyclingMode === "interval") {
updateCyclingState();
}
updateCyclingState();
}
function onWallpaperCyclingTimeChanged() {
cachedCyclingTime = SessionData.wallpaperCyclingTime;
if (SessionData.wallpaperCyclingMode === "time") {
updateCyclingState();
}
updateCyclingState();
}
function onPerMonitorWallpaperChanged() {
@@ -119,225 +88,117 @@ Singleton {
target: SessionService
function onSessionUnlocked() {
if (SessionData.wallpaperCyclingEnabled || SessionData.perMonitorWallpaper) {
updateCyclingState();
}
updateCyclingState();
}
}
function updateCyclingState() {
if (SessionData.perMonitorWallpaper) {
stopCycling();
updatePerMonitorCycling();
} else if (SessionData.wallpaperCyclingEnabled && SessionData.wallpaperPath) {
startCycling();
stopAllMonitorCycling();
} else {
stopCycling();
stopAllMonitorCycling();
}
cyclingActive = serverSchedulingAvailable && (SessionData.wallpaperCyclingEnabled || SessionData.perMonitorWallpaper);
pushConfigToServer();
}
function updatePerMonitorCycling() {
if (typeof Quickshell === "undefined")
function buildServerConfig() {
var monitors = {};
if (SessionData.perMonitorWallpaper && typeof Quickshell !== "undefined") {
var screens = Quickshell.screens;
for (var i = 0; i < screens.length; i++) {
var name = screens[i].name;
var s = SessionData.getMonitorCyclingSettings(name);
var wp = SessionData.getMonitorWallpaper(name);
monitors[name] = {
"enabled": !!(s.enabled && wp && !wp.startsWith("#")),
"mode": s.mode || "interval",
"intervalSec": s.interval || 300,
"time": s.time || "06:00"
};
}
}
return {
"perMonitor": SessionData.perMonitorWallpaper,
"global": {
"enabled": !!(SessionData.wallpaperCyclingEnabled && SessionData.wallpaperPath),
"mode": SessionData.wallpaperCyclingMode,
"intervalSec": SessionData.wallpaperCyclingInterval,
"time": SessionData.wallpaperCyclingTime
},
"monitors": monitors
};
}
function pushConfigToServer() {
if (!serverSchedulingAvailable)
return;
var screens = Quickshell.screens;
for (var i = 0; i < screens.length; i++) {
var screenName = screens[i].name;
var settings = SessionData.getMonitorCyclingSettings(screenName);
var wallpaper = SessionData.getMonitorWallpaper(screenName);
if (settings.enabled && wallpaper && !wallpaper.startsWith("#")) {
startMonitorCycling(screenName, settings);
} else {
stopMonitorCycling(screenName);
}
}
DMSService.sendRequest("wallpaper.setConfig", {
"config": buildServerConfig()
}, null);
}
function stopAllMonitorCycling() {
var screenNames = Object.keys(monitorTimers);
for (var i = 0; i < screenNames.length; i++) {
stopMonitorCycling(screenNames[i]);
}
function findCommand(wallpaperDir) {
return ["sh", "-c", `find -L "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
}
function startCycling() {
switch (SessionData.wallpaperCyclingMode) {
case "interval":
lastTimeCheck = "";
intervalTimer.interval = cachedCyclingInterval * 1000;
intervalTimer.start();
cyclingActive = true;
break;
case "time":
intervalTimer.stop();
cyclingActive = true;
checkTimeBasedCycling();
break;
}
}
function stopCycling() {
intervalTimer.stop();
cyclingActive = false;
}
function startMonitorCycling(screenName, settings) {
switch (settings.mode) {
case "interval":
{
var newChecks = Object.assign({}, monitorLastTimeChecks);
delete newChecks[screenName];
monitorLastTimeChecks = newChecks;
var timer = monitorTimers[screenName];
if (!timer && monitorTimerComponent && monitorTimerComponent.status === Component.Ready) {
var newTimers = Object.assign({}, monitorTimers);
var newTimer = monitorTimerComponent.createObject(root);
newTimer.targetScreen = screenName;
newTimers[screenName] = newTimer;
monitorTimers = newTimers;
timer = newTimer;
}
if (timer) {
timer.interval = settings.interval * 1000;
timer.start();
}
break;
}
case "time":
{
var existingTimer = monitorTimers[screenName];
if (existingTimer) {
existingTimer.stop();
existingTimer.destroy();
var newTimers = Object.assign({}, monitorTimers);
delete newTimers[screenName];
monitorTimers = newTimers;
}
var newChecks = Object.assign({}, monitorLastTimeChecks);
newChecks[screenName] = "";
monitorLastTimeChecks = newChecks;
break;
}
}
}
function stopMonitorCycling(screenName) {
var timer = monitorTimers[screenName];
if (timer) {
timer.stop();
timer.destroy();
var newTimers = Object.assign({}, monitorTimers);
delete newTimers[screenName];
monitorTimers = newTimers;
}
function monitorProcessFor(screenName) {
var process = monitorProcesses[screenName];
if (process) {
process.destroy();
var newProcesses = Object.assign({}, monitorProcesses);
delete newProcesses[screenName];
monitorProcesses = newProcesses;
if (process)
return process;
var newProcesses = Object.assign({}, monitorProcesses);
process = monitorProcessComponent.createObject(root);
newProcesses[screenName] = process;
monitorProcesses = newProcesses;
return process;
}
function cycle(screenName, wallpaperPath, goToPrevious) {
const currentWallpaper = wallpaperPath || SessionData.wallpaperPath;
if (!currentWallpaper)
return;
const wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'));
if (screenName && monitorProcessComponent.status === Component.Ready) {
var process = monitorProcessFor(screenName);
process.command = findCommand(wallpaperDir);
process.targetScreenName = screenName;
process.currentWallpaper = currentWallpaper;
process.goToPrevious = goToPrevious;
process.running = true;
return;
}
var newChecks = Object.assign({}, monitorLastTimeChecks);
delete newChecks[screenName];
monitorLastTimeChecks = newChecks;
var globalProcess = goToPrevious ? prevCyclingProcess : cyclingProcess;
globalProcess.command = findCommand(wallpaperDir);
globalProcess.targetScreenName = screenName || "";
globalProcess.currentWallpaper = currentWallpaper;
globalProcess.running = true;
}
function cycleToNextWallpaper(screenName, wallpaperPath) {
const currentWallpaper = wallpaperPath || SessionData.wallpaperPath;
if (!currentWallpaper)
return;
const wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'));
if (screenName && monitorProcessComponent && monitorProcessComponent.status === Component.Ready) {
// Use per-monitor process
var process = monitorProcesses[screenName];
if (!process) {
var newProcesses = Object.assign({}, monitorProcesses);
var newProcess = monitorProcessComponent.createObject(root);
newProcesses[screenName] = newProcess;
monitorProcesses = newProcesses;
process = newProcess;
}
if (process) {
process.command = ["sh", "-c", `find -L "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
process.targetScreenName = screenName;
process.currentWallpaper = currentWallpaper;
process.goToPrevious = false;
process.running = true;
}
} else {
// Use global process for fallback
cyclingProcess.command = ["sh", "-c", `find -L "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
cyclingProcess.targetScreenName = screenName || "";
cyclingProcess.currentWallpaper = currentWallpaper;
cyclingProcess.running = true;
}
cycle(screenName, wallpaperPath, false);
}
function cycleToPrevWallpaper(screenName, wallpaperPath) {
const currentWallpaper = wallpaperPath || SessionData.wallpaperPath;
if (!currentWallpaper)
cycle(screenName, wallpaperPath, true);
}
function resetScheduleAfterManual() {
if (!serverSchedulingAvailable)
return;
const wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'));
if (screenName && monitorProcessComponent && monitorProcessComponent.status === Component.Ready) {
// Use per-monitor process (same as next, but with prev flag)
var process = monitorProcesses[screenName];
if (!process) {
var newProcesses = Object.assign({}, monitorProcesses);
var newProcess = monitorProcessComponent.createObject(root);
newProcesses[screenName] = newProcess;
monitorProcesses = newProcesses;
process = newProcess;
}
if (process) {
process.command = ["sh", "-c", `find -L "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
process.targetScreenName = screenName;
process.currentWallpaper = currentWallpaper;
process.goToPrevious = true;
process.running = true;
}
} else {
// Use global process for fallback
prevCyclingProcess.command = ["sh", "-c", `find -L "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
prevCyclingProcess.targetScreenName = screenName || "";
prevCyclingProcess.currentWallpaper = currentWallpaper;
prevCyclingProcess.running = true;
}
DMSService.sendRequest("wallpaper.trigger", {
"target": ""
}, null);
}
function cycleNextManually() {
if (SessionData.wallpaperPath) {
cycleToNextWallpaper();
// Restart timers if cycling is active
if (cyclingActive && SessionData.wallpaperCyclingEnabled) {
if (SessionData.wallpaperCyclingMode === "interval") {
intervalTimer.interval = cachedCyclingInterval * 1000;
intervalTimer.restart();
}
}
}
if (!SessionData.wallpaperPath)
return;
cycleToNextWallpaper();
resetScheduleAfterManual();
}
function cyclePrevManually() {
if (SessionData.wallpaperPath) {
cycleToPrevWallpaper();
// Restart timers if cycling is active
if (cyclingActive && SessionData.wallpaperCyclingEnabled) {
if (SessionData.wallpaperCyclingMode === "interval") {
intervalTimer.interval = cachedCyclingInterval * 1000;
intervalTimer.restart();
}
}
}
if (!SessionData.wallpaperPath)
return;
cycleToPrevWallpaper();
resetScheduleAfterManual();
}
function cycleNextForMonitor(screenName) {
@@ -358,138 +219,66 @@ Singleton {
}
}
function checkTimeBasedCycling() {
if (shouldPauseCycling)
function applyCycledWallpaper(text, currentPath, targetScreenName, goToPrevious) {
if (!text || !text.trim())
return;
const currentTime = Qt.formatTime(systemClock.date, "hh:mm");
const files = text.trim().split('\n').filter(file => file.length > 0);
if (files.length <= 1)
return;
const wallpaperList = files.sort();
let currentIndex = wallpaperList.findIndex(path => path === currentPath);
if (currentIndex === -1)
currentIndex = 0;
if (!SessionData.perMonitorWallpaper) {
if (currentTime === cachedCyclingTime && currentTime !== lastTimeCheck) {
lastTimeCheck = currentTime;
cycleToNextWallpaper();
} else if (currentTime !== cachedCyclingTime) {
lastTimeCheck = "";
}
let targetIndex;
if (goToPrevious) {
targetIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1;
} else {
checkPerMonitorTimeBasedCycling(currentTime);
targetIndex = (currentIndex + 1) % wallpaperList.length;
}
}
function checkPerMonitorTimeBasedCycling(currentTime) {
if (typeof Quickshell === "undefined")
const targetWallpaper = wallpaperList[targetIndex];
if (!targetWallpaper || targetWallpaper === currentPath)
return;
var screens = Quickshell.screens;
for (var i = 0; i < screens.length; i++) {
var screenName = screens[i].name;
var settings = SessionData.getMonitorCyclingSettings(screenName);
var wallpaper = SessionData.getMonitorWallpaper(screenName);
if (settings.enabled && settings.mode === "time" && wallpaper && !wallpaper.startsWith("#")) {
var lastCheck = monitorLastTimeChecks[screenName] || "";
if (currentTime === settings.time && currentTime !== lastCheck) {
var newChecks = Object.assign({}, monitorLastTimeChecks);
newChecks[screenName] = currentTime;
monitorLastTimeChecks = newChecks;
cycleNextForMonitor(screenName);
} else if (currentTime !== settings.time) {
var newChecks = Object.assign({}, monitorLastTimeChecks);
newChecks[screenName] = "";
monitorLastTimeChecks = newChecks;
}
}
if (targetScreenName) {
SessionData.setMonitorWallpaper(targetScreenName, targetWallpaper);
} else {
SessionData.setWallpaper(targetWallpaper);
}
}
Timer {
id: intervalTimer
interval: cachedCyclingInterval * 1000
running: false
repeat: true
onTriggered: {
if (shouldPauseCycling)
return;
cycleToNextWallpaper();
}
}
SystemClock {
id: systemClock
precision: SystemClock.Minutes
onDateChanged: {
if ((SessionData.wallpaperCyclingMode === "time" && cyclingActive) || SessionData.perMonitorWallpaper) {
checkTimeBasedCycling();
Component {
id: monitorProcessComponent
Process {
property string targetScreenName: ""
property string currentWallpaper: ""
property bool goToPrevious: false
running: false
stdout: StdioCollector {
onStreamFinished: root.applyCycledWallpaper(text, currentWallpaper, targetScreenName, goToPrevious)
}
}
}
Process {
id: cyclingProcess
property string targetScreenName: ""
property string currentWallpaper: ""
property bool goToPrevious: false
running: false
stdout: StdioCollector {
onStreamFinished: {
if (text && text.trim()) {
const files = text.trim().split('\n').filter(file => file.length > 0);
if (files.length <= 1)
return;
const wallpaperList = files.sort();
const currentPath = cyclingProcess.currentWallpaper;
let currentIndex = wallpaperList.findIndex(path => path === currentPath);
if (currentIndex === -1)
currentIndex = 0;
const nextIndex = (currentIndex + 1) % wallpaperList.length;
const nextWallpaper = wallpaperList[nextIndex];
if (nextWallpaper && nextWallpaper !== currentPath) {
if (cyclingProcess.targetScreenName) {
SessionData.setMonitorWallpaper(cyclingProcess.targetScreenName, nextWallpaper);
} else {
SessionData.setWallpaper(nextWallpaper);
}
}
}
}
onStreamFinished: root.applyCycledWallpaper(text, cyclingProcess.currentWallpaper, cyclingProcess.targetScreenName, cyclingProcess.goToPrevious)
}
}
Process {
id: prevCyclingProcess
property string targetScreenName: ""
property string currentWallpaper: ""
property bool goToPrevious: true
running: false
stdout: StdioCollector {
onStreamFinished: {
if (text && text.trim()) {
const files = text.trim().split('\n').filter(file => file.length > 0);
if (files.length <= 1)
return;
const wallpaperList = files.sort();
const currentPath = prevCyclingProcess.currentWallpaper;
let currentIndex = wallpaperList.findIndex(path => path === currentPath);
if (currentIndex === -1)
currentIndex = 0;
const prevIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1;
const prevWallpaper = wallpaperList[prevIndex];
if (prevWallpaper && prevWallpaper !== currentPath) {
if (prevCyclingProcess.targetScreenName) {
SessionData.setMonitorWallpaper(prevCyclingProcess.targetScreenName, prevWallpaper);
} else {
SessionData.setWallpaper(prevWallpaper);
}
}
}
}
onStreamFinished: root.applyCycledWallpaper(text, prevCyclingProcess.currentWallpaper, prevCyclingProcess.targetScreenName, prevCyclingProcess.goToPrevious)
}
}
}