feat(wallpaper): allow recursive cycling through subdirectories (#3739)

* feat(wallpaper): allow recursive cycling through subdirectories

* fix(wallpaper): persist root folder when recursively cycling without folder set
This commit is contained in:
Huỳnh Thiện Lộc
2026-10-09 22:53:29 -04:00
committed by GitHub
parent be26a06c2a
commit 400f62316c
6 changed files with 81 additions and 8 deletions
+11
View File
@@ -218,6 +218,7 @@ Singleton {
property bool wallpaperCyclingEnabled: false
property bool wallpaperCyclingRandom: false
property bool wallpaperCyclingRecursive: false
property string wallpaperCyclingMode: "interval"
property int wallpaperCyclingInterval: 300
property string wallpaperCyclingTime: "06:00"
@@ -1243,6 +1244,11 @@ Singleton {
saveSettings();
}
function setWallpaperCyclingRecursive(recursive) {
wallpaperCyclingRecursive = recursive;
saveSettings();
}
function setWallpaperCyclingMode(mode) {
wallpaperCyclingMode = mode;
saveSettings();
@@ -1287,6 +1293,10 @@ Singleton {
updateMonitorCyclingSetting(screenName, "random", random);
}
function setMonitorCyclingRecursive(screenName, recursive) {
updateMonitorCyclingSetting(screenName, "recursive", recursive);
}
function setMonitorCyclingMode(screenName, mode) {
updateMonitorCyclingSetting(screenName, "mode", mode);
}
@@ -1852,6 +1862,7 @@ Singleton {
var defaults = {
"enabled": false,
"random": false,
"recursive": false,
"mode": "interval",
"interval": 300,
"time": "06:00",
@@ -60,6 +60,9 @@ var LOCAL_SPEC = {
wallpaperCyclingRandom: {
def: false
},
wallpaperCyclingRecursive: {
def: false
},
wallpaperCyclingMode: {
def: "interval"
},
@@ -21,6 +21,7 @@ Item {
}
readonly property bool enabled: perMonitor ? monitorSettings.enabled : SessionData.wallpaperCyclingEnabled
readonly property bool random: perMonitor ? monitorSettings.random : SessionData.wallpaperCyclingRandom
readonly property bool recursive: perMonitor ? monitorSettings.recursive : SessionData.wallpaperCyclingRecursive
readonly property string mode: perMonitor ? monitorSettings.mode : SessionData.wallpaperCyclingMode
readonly property int interval: perMonitor ? monitorSettings.interval : SessionData.wallpaperCyclingInterval
readonly property string time: perMonitor ? monitorSettings.time : SessionData.wallpaperCyclingTime
@@ -84,6 +85,14 @@ Item {
SessionData.setWallpaperCyclingRandom(value);
}
function setRecursive(value) {
if (perMonitor) {
SessionData.setMonitorCyclingRecursive(selectedScreen, value);
return;
}
SessionData.setWallpaperCyclingRecursive(value);
}
function setMode(value) {
if (perMonitor) {
SessionData.setMonitorCyclingMode(selectedScreen, value);
@@ -197,6 +206,15 @@ Item {
}
}
SettingsToggleRow {
tab: "wallpaper"
tags: ["cycling", "subfolders", "recursive", "directories", "folders"]
settingKey: "wallpaperCyclingRecursive"
text: I18n.tr("Include subfolders")
checked: root.recursive
onToggled: toggled => root.setRecursive(toggled)
}
SettingsToggleRow {
tab: "wallpaper"
tags: ["cycling", "automatic", "random", "shuffle"]
@@ -139,8 +139,9 @@ Singleton {
}, null);
}
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" -o -iname "*.svg" \\) 2>/dev/null | sort`];
function findCommand(wallpaperDir, recursive) {
const depthArg = recursive ? "" : "-maxdepth 1 ";
return ["sh", "-c", `find -L "${wallpaperDir}" ${depthArg}-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" -o -iname "*.svg" \\) 2>/dev/null | sort`];
}
function monitorProcessFor(screenName) {
@@ -158,25 +159,34 @@ Singleton {
if (!currentWallpaper || currentWallpaper.startsWith("#"))
return;
let wallpaperDir;
let recursive = false;
if (screenName) {
const monitorSettings = SessionData.getMonitorCyclingSettings(screenName);
recursive = !!monitorSettings.recursive;
if (monitorSettings.folderPath && currentWallpaper.startsWith(monitorSettings.folderPath + "/")) {
wallpaperDir = monitorSettings.folderPath;
} else {
wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'));
if (recursive && wallpaperDir)
SessionData.setMonitorCyclingFolderPath(screenName, wallpaperDir);
}
} else {
recursive = !!SessionData.wallpaperCyclingRecursive;
if (SessionData.wallpaperCyclingFolderPath && currentWallpaper.startsWith(SessionData.wallpaperCyclingFolderPath + "/")) {
wallpaperDir = SessionData.wallpaperCyclingFolderPath;
} else {
wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'));
if (recursive && wallpaperDir) {
SessionData.wallpaperCyclingFolderPath = wallpaperDir;
SessionData.saveSettings();
}
}
}
if (screenName && monitorProcessComponent.status === Component.Ready) {
var process = monitorProcessFor(screenName);
process.command = findCommand(wallpaperDir);
process.command = findCommand(wallpaperDir, recursive);
process.targetScreenName = screenName;
process.currentWallpaper = currentWallpaper;
process.goToPrevious = goToPrevious;
@@ -186,7 +196,7 @@ Singleton {
}
var globalProcess = goToPrevious ? prevCyclingProcess : cyclingProcess;
globalProcess.command = findCommand(wallpaperDir);
globalProcess.command = findCommand(wallpaperDir, recursive);
globalProcess.targetScreenName = screenName || "";
globalProcess.currentWallpaper = currentWallpaper;
globalProcess.revision = SessionData.wallpaperRequestRevision(screenName || "");
@@ -205,16 +215,19 @@ Singleton {
if (!folderPath)
return;
let recursive = false;
if (screenName) {
SessionData.setMonitorCyclingFolderPath(screenName, folderPath);
recursive = !!SessionData.getMonitorCyclingSettings(screenName).recursive;
} else {
SessionData.wallpaperCyclingFolderPath = folderPath;
SessionData.saveSettings();
recursive = !!SessionData.wallpaperCyclingRecursive;
}
if (screenName && monitorProcessComponent.status === Component.Ready) {
var process = monitorProcessFor(screenName);
process.command = findCommand(folderPath);
process.command = findCommand(folderPath, recursive);
process.targetScreenName = screenName;
process.currentWallpaper = "";
process.goToPrevious = false;
@@ -224,7 +237,7 @@ Singleton {
}
var globalProcess = cyclingProcess;
globalProcess.command = findCommand(folderPath);
globalProcess.command = findCommand(folderPath, recursive);
globalProcess.targetScreenName = screenName || "";
globalProcess.currentWallpaper = "";
globalProcess.goToPrevious = false;
@@ -4,7 +4,7 @@ import vm from "node:vm";
import test from "node:test";
const source = readFileSync(new URL("../Common/SessionData.qml", import.meta.url), "utf8");
const setters = ["setMonitorCyclingEnabled", "setMonitorCyclingRandom", "setMonitorCyclingMode", "setMonitorCyclingInterval", "setMonitorCyclingTime", "setMonitorCyclingFolderPath"];
const setters = ["setMonitorCyclingEnabled", "setMonitorCyclingRandom", "setMonitorCyclingRecursive", "setMonitorCyclingMode", "setMonitorCyclingInterval", "setMonitorCyclingTime", "setMonitorCyclingFolderPath"];
const helpers = ["getMonitorCyclingSettings", "_findMonitorValue", "_screenByName", "updateMonitorCyclingSetting"];
function session(initial) {
@@ -38,7 +38,7 @@ test("a setter writes its field under the display identifier and keeps other scr
const result = plain(context.monitorCyclingSettings);
assert.deepEqual(Object.keys(result).sort(), ["HDMI-A-1", "LG 27"]);
assert.deepEqual(result["HDMI-A-1"], { enabled: true, interval: 10 });
assert.deepEqual(result["LG 27"], { enabled: false, random: false, mode: "interval", interval: 60, time: "06:00", folderPath: "" });
assert.deepEqual(result["LG 27"], { enabled: false, random: false, recursive: false, mode: "interval", interval: 60, time: "06:00", folderPath: "" });
assert.equal(saved(), 1);
});
@@ -16039,6 +16039,34 @@
],
"parentLabel": "Wallpaper & colors"
},
{
"section": "wallpaperCyclingRecursive",
"label": "Include subfolders",
"tabIndex": 52,
"category": "Automatic cycling",
"keywords": [
"automatic",
"background",
"bg",
"colors",
"colour",
"colours",
"cycling",
"desktop",
"directories",
"folders",
"hue",
"image",
"include",
"palette",
"picture",
"recursive",
"subfolders",
"tint",
"wallpaper"
],
"parentLabel": "Wallpaper & colors"
},
{
"section": "wallpaperCyclingInterval",
"label": "Interval",