Files
dotfiles/.config/quickshell/nucleus-shell/services/WallpaperSlideshow.qml

141 lines
4.7 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import qs.config
Singleton {
id: root
property var wallpapers: []
property bool scanning: false
property string currentFolder: Config.initialized ? Config.runtime.appearance.background.slideshow.folder : "";
property int intervalMinutes: Config.initialized ? Config.runtime.appearance.background.slideshow.interval : 5
property bool enabled: Config.initialized ? Config.runtime.appearance.background.slideshow.enabled : false
property bool includeSubfolders: Config.initialized ? Config.runtime.appearance.background.slideshow.includeSubfolders : true
property bool initializedOnce: false
property bool hydratingFromConfig: true
signal wallpaperChanged(string path)
function nextWallpaper() {
if (wallpapers.length === 0) {
console.warn("WallpaperSlideshow: No wallpapers found in folder");
return;
}
const randomIndex = Math.floor(Math.random() * wallpapers.length);
const selectedPath = "file://" + wallpapers[randomIndex];
Config.updateKey("appearance.background.path", selectedPath);
wallpaperChanged(selectedPath);
// Regenerate colors
if (Config.runtime.appearance.colors.autogenerated) {
Quickshell.execDetached([
"nucleus", "ipc", "call", "global", "regenColors"
]);
}
}
function scanFolder() {
if (!currentFolder || currentFolder === "") {
wallpapers = [];
return;
}
scanning = true;
scanProcess.running = true;
}
// Timer for automatic wallpaper rotation
Timer {
id: slideshowTimer
interval: root.intervalMinutes * 60 * 1000 // Convert minutes into miliseconds
repeat: true
running: root.enabled && root.wallpapers.length > 0 && root.initializedOnce
onTriggered: root.nextWallpaper()
}
// Process to scan folder for images
Process {
id: scanProcess
command: root.includeSubfolders
? ["find", root.currentFolder, "-type", "f", "-iregex", ".*\\.\\(jpg\\|jpeg\\|png\\|webp\\|bmp\\|svg\\)$"]
: ["find", root.currentFolder, "-maxdepth", "1", "-type", "f", "-iregex", ".*\\.\\(jpg\\|jpeg\\|png\\|webp\\|bmp\\|svg\\)$"]
stdout: SplitParser {
splitMarker: ""
onRead: data => {
const lines = data.trim().split("\n").filter(line => line.length > 0);
root.wallpapers = lines;
root.scanning = false;
}
}
onExited: (exitCode, exitStatus) => {
root.scanning = false;
if (exitCode !== 0) {
console.warn("WallpaperSlideshow: Failed to scan folder");
root.wallpapers = [];
}
}
}
// Watch for folder changes - rescan and immediately change wallpaper
onCurrentFolderChanged: {
if (root.hydratingFromConfig)
return;
if (!currentFolder || currentFolder === "")
return;
scanning = true;
folderChangeScanProcess.running = true;
}
// Separate process for folder change scan (triggers immediate wallpaper change)
Process {
id: folderChangeScanProcess
command: root.includeSubfolders
? ["find", root.currentFolder, "-type", "f", "-iregex", ".*\\.\\(jpg\\|jpeg\\|png\\|webp\\|bmp\\|svg\\)$"]
: ["find", root.currentFolder, "-maxdepth", "1", "-type", "f", "-iregex", ".*\\.\\(jpg\\|jpeg\\|png\\|webp\\|bmp\\|svg\\)$"]
stdout: SplitParser {
splitMarker: ""
onRead: data => {
const lines = data.trim().split("\n").filter(line => line.length > 0);
root.wallpapers = lines;
root.scanning = false;
// Immediately change wallpaper when folder is changed
if (root.wallpapers.length > 0) {
root.nextWallpaper();
}
}
}
onExited: (exitCode, exitStatus) => {
root.scanning = false;
if (exitCode !== 0) {
console.warn("WallpaperSlideshow: Failed to scan folder");
root.wallpapers = [];
}
}
}
// Watch for includeSubfolders changes
onIncludeSubfoldersChanged: {
if (currentFolder && currentFolder !== "") {
scanFolder();
}
}
// Initial scan when config is loaded
Connections {
target: Config
function onInitializedChanged() {
if (Config.initialized && root.currentFolder && root.currentFolder !== "") {
root.scanFolder();
}
}
}
}