From 5fef9cfe6ba085b97ee22e27ce5b58546b0d2c2d Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Mon, 1 Sep 2025 09:07:23 -0400 Subject: [PATCH] WallpaperService: refactored to a simpler signal based approach. --- Modules/Background/Background.qml | 30 +-- Modules/Background/Overview.qml | 15 +- Modules/SettingsPanel/Tabs/ColorSchemeTab.qml | 3 +- Modules/SettingsPanel/Tabs/GeneralTab.qml | 4 +- .../Tabs/WallpaperSelectorTab.qml | 39 +++- Services/ColorSchemeService.qml | 9 - Services/MatugenService.qml | 11 ++ Services/WallpaperService.qml | 182 +++++++++++++----- 8 files changed, 217 insertions(+), 76 deletions(-) diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index d8589cf..0acde3b 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -18,7 +18,6 @@ Variants { id: root // Internal state management - property bool firstWallpaper: true property string transitionType: "fade" property real transitionProgress: 0 @@ -37,17 +36,26 @@ Variants { property real stripesCount: 16 property real stripesAngle: 0 - // External state management - property string servicedWallpaper: modelData ? WallpaperService.getWallpaper(modelData.name) : "" + // Used to debounce wallpaper changes property string futureWallpaper: "" - onServicedWallpaperChanged: { - // Set wallpaper immediately on startup - if (firstWallpaper) { - firstWallpaper = false - setWallpaperImmediate(servicedWallpaper) - } else { - futureWallpaper = servicedWallpaper - debounceTimer.restart() + + // On startup assign wallpaper immediately + Component.onCompleted: { + var path = modelData ? WallpaperService.getWallpaper(modelData.name) : "" + setWallpaperImmediate(path) + } + + // External state management + Connections { + target: WallpaperService + function onWallpaperChanged(screenName, path) { + if (screenName === modelData.name) { + + // Update wallpaper display + // Set wallpaper immediately on startup + futureWallpaper = path + debounceTimer.restart() + } } } diff --git a/Modules/Background/Overview.qml b/Modules/Background/Overview.qml index a11ed79..1b99a64 100644 --- a/Modules/Background/Overview.qml +++ b/Modules/Background/Overview.qml @@ -14,11 +14,24 @@ Variants { active: Settings.isLoaded && CompositorService.isNiri && modelData + property string wallpaper: "" + sourceComponent: PanelWindow { Component.onCompleted: { if (modelData) { Logger.log("Overview", "Loading Overview component for Niri on", modelData.name) } + wallpaper = modelData ? WallpaperService.getWallpaper(modelData.name) : "" + } + + // External state management + Connections { + target: WallpaperService + function onWallpaperChanged(screenName, path) { + if (screenName === modelData.name) { + wallpaper = path + } + } } color: Color.transparent @@ -38,7 +51,7 @@ Variants { id: bgImage anchors.fill: parent fillMode: Image.PreserveAspectCrop - source: modelData ? WallpaperService.getWallpaper(modelData.name) : "" + source: wallpaper smooth: true mipmap: false cache: false diff --git a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml index c35cd1f..4caa660 100644 --- a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml +++ b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml @@ -63,7 +63,8 @@ ColumnLayout { if (exitCode === 0) { // Matugen exists, enable it Settings.data.colorSchemes.useWallpaperColors = true - ColorSchemeService.changedWallpaper() + Settings.data.colorSchemes.predefinedScheme = "" + MatugenService.generateFromWallpaper() ToastService.showNotice("Matugen", "Enabled") } else { // Matugen not found diff --git a/Modules/SettingsPanel/Tabs/GeneralTab.qml b/Modules/SettingsPanel/Tabs/GeneralTab.qml index b80faa2..7fb4f5f 100644 --- a/Modules/SettingsPanel/Tabs/GeneralTab.qml +++ b/Modules/SettingsPanel/Tabs/GeneralTab.qml @@ -15,8 +15,8 @@ ColumnLayout { // Avatar preview NImageCircled { - width: 64 * scaling - height: 64 * scaling + width: 128 * scaling + height: 128 * scaling imagePath: Settings.data.general.avatarImage fallbackIcon: "person" borderColor: Color.mPrimary diff --git a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml index 0947ea3..5778f66 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml @@ -12,6 +12,35 @@ ColumnLayout { spacing: Style.marginL * scaling + property list wallpapersList: [] + property string currentWallpaper: "" + + Component.onCompleted: { + wallpapersList = screen ? WallpaperService.getWallpapersList(screen.name) : [] + currentWallpaper = screen ? WallpaperService.getWallpaper(screen.name) : "" + } + + Connections { + target: WallpaperService + function onWallpaperChanged(screenName, path) { + if (screenName === screen.name) { + currentWallpaper = WallpaperService.getWallpaper(screen.name) + } + } + function onWallpaperDirectoryChanged(screenName, directory) { + if (screenName === screen.name) { + wallpapersList = WallpaperService.getWallpapersList(screen.name) + currentWallpaper = WallpaperService.getWallpaper(screen.name) + } + } + function onWallpaperListChanged(screenName, count) { + if (screenName === screen.name) { + wallpapersList = WallpaperService.getWallpapersList(screen.name) + currentWallpaper = WallpaperService.getWallpaper(screen.name) + } + } + } + // Current wallpaper display NText { text: "Current Wallpaper" @@ -29,7 +58,7 @@ ColumnLayout { NImageRounded { anchors.fill: parent anchors.margins: Style.marginXS * scaling - imagePath: screen ? WallpaperService.getWallpaper(screen.name) : "" + imagePath: currentWallpaper fallbackIcon: "image" imageRadius: Style.radiusM * scaling } @@ -74,11 +103,9 @@ ColumnLayout { } } - property list wallpapersList: screen ? WallpaperService.getWallpapersList(screen.name) : [] - NToggle { - label: "Assign selection to all monitors" - description: "Set selected wallpaper on all monitors at once." + label: "Apply to all monitors" + description: "Apply selected wallpaper to all monitors at once." checked: Settings.data.wallpaper.setWallpaperOnAllMonitors onToggled: checked => Settings.data.wallpaper.setWallpaperOnAllMonitors = checked visible: (wallpapersList.length > 0) @@ -115,7 +142,7 @@ ColumnLayout { id: wallpaperItem property string wallpaperPath: modelData - property bool isSelected: screen ? (wallpaperPath === WallpaperService.getWallpaper(screen.name)) : false + property bool isSelected: screen ? (wallpaperPath === currentWallpaper) : false width: wallpaperGridView.itemSize height: Math.round(wallpaperGridView.itemSize * 0.67) diff --git a/Services/ColorSchemeService.qml b/Services/ColorSchemeService.qml index 0c18090..624c05e 100644 --- a/Services/ColorSchemeService.qml +++ b/Services/ColorSchemeService.qml @@ -35,15 +35,6 @@ Singleton { schemeReader.path = filePath } - function changedWallpaper() { - if (Settings.data.colorSchemes.useWallpaperColors) { - Logger.log("ColorScheme", "Starting color generation from wallpaper") - MatugenService.generateFromWallpaper() - // Invalidate potential predefined scheme - Settings.data.colorSchemes.predefinedScheme = "" - } - } - FolderListModel { id: folderModel nameFilters: ["*.json"] diff --git a/Services/MatugenService.qml b/Services/MatugenService.qml index 9c40aaf..9b476cb 100644 --- a/Services/MatugenService.qml +++ b/Services/MatugenService.qml @@ -12,6 +12,17 @@ Singleton { property string dynamicConfigPath: Settings.cacheDir + "matugen.dynamic.toml" + // External state management + Connections { + target: WallpaperService + function onWallpaperChanged(screenName, path) { + // Only detect changes on main screen + if (screenName === Screen.name && Settings.data.colorSchemes.useWallpaperColors) { + generateFromWallpaper() + } + } + } + // Build TOML content based on settings function buildConfigToml() { return Matugen.buildConfigToml() diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index b66ce2a..7e9408e 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -11,6 +11,14 @@ Singleton { Component.onCompleted: { Logger.log("Wallpaper", "Service started") + + // Initialize cache from Settings on startup + var monitors = Settings.data.wallpaper.monitors || [] + for (var i = 0; i < monitors.length; i++) { + if (monitors[i].name && monitors[i].wallpaper) { + currentWallpapers[monitors[i].name] = monitors[i].wallpaper + } + } } // All available wallpaper transitions @@ -51,10 +59,45 @@ Singleton { property int scanningCount: 0 readonly property bool scanning: (scanningCount > 0) + // Cache for current wallpapers - can be updated directly since we use signals for notifications + property var currentWallpapers: ({}) + + // Signals for reactive UI updates + signal wallpaperChanged(string screenName, string path) + // Emitted when a wallpaper changes + signal wallpaperDirectoryChanged(string screenName, string directory) + // Emitted when a monitor's directory changes + signal wallpaperListChanged(string screenName, int count) + + // Emitted when available wallpapers list changes Connections { target: Settings.data.wallpaper function onDirectoryChanged() { root.refreshWallpapersList() + // Emit directory change signals for monitors using the default directory + if (!Settings.data.wallpaper.enableMultiMonitorDirectories) { + // All monitors use the main directory + for (var i = 0; i < Quickshell.screens.length; i++) { + root.wallpaperDirectoryChanged(Quickshell.screens[i].name, Settings.data.wallpaper.directory) + } + } else { + // Only monitors without custom directories are affected + for (var i = 0; i < Quickshell.screens.length; i++) { + var screenName = Quickshell.screens[i].name + var monitor = root.getMonitorConfig(screenName) + if (!monitor || !monitor.directory) { + root.wallpaperDirectoryChanged(screenName, Settings.data.wallpaper.directory) + } + } + } + } + function onEnableMultiMonitorDirectoriesChanged() { + root.refreshWallpapersList() + // Notify all monitors about potential directory changes + for (var i = 0; i < Quickshell.screens.length; i++) { + var screenName = Quickshell.screens[i].name + root.wallpaperDirectoryChanged(screenName, root.getMonitorDirectory(screenName)) + } } function onRandomEnabledChanged() { root.toggleRandomWallpaper() @@ -96,26 +139,39 @@ Singleton { // ------------------------------------------------------------------- // Set specific monitor directory function setMonitorDirectory(screenName, directory) { - var monitor = getMonitorConfig(screenName) - if (monitor !== undefined) { - monitor.directory = directory - } else { - Settings.data.wallpaper.monitors.push({ - "name": screenName, - "directory": directory, - "wallpaper": "" - }) + var monitors = Settings.data.wallpaper.monitors || [] + var found = false + + // Create a new array with updated values + var newMonitors = monitors.map(function (monitor) { + if (monitor.name === screenName) { + found = true + return { + "name": screenName, + "directory": directory, + "wallpaper": monitor.wallpaper || "" + } + } + return monitor + }) + + if (!found) { + newMonitors.push({ + "name": screenName, + "directory": directory, + "wallpaper": "" + }) } + + // Update Settings with new array to ensure proper persistence + Settings.data.wallpaper.monitors = newMonitors.slice() + root.wallpaperDirectoryChanged(screenName, directory) } // ------------------------------------------------------------------- - // Get specific monitor wallpaper + // Get specific monitor wallpaper - now from cache function getWallpaper(screenName) { - var monitor = getMonitorConfig(screenName) - if ((monitor !== undefined) && (monitor["wallpaper"] !== undefined)) { - return monitor["wallpaper"] - } - return "" + return currentWallpapers[screenName] || "" } // ------------------------------------------------------------------- @@ -142,30 +198,53 @@ Singleton { } //Logger.log("Wallpaper", "setWallpaper on", screenName, ": ", path) - var wallpaperChanged = false - var monitor = getMonitorConfig(screenName) - if (monitor !== undefined) { - wallpaperChanged = (monitor["wallpaper"] !== path) - monitor["wallpaper"] = path - } else { - wallpaperChanged = true - Settings.data.wallpaper.monitors.push({ - "name": screenName, - "directory": getMonitorDirectory(screenName), - "wallpaper": path - }) + // Check if wallpaper actually changed + var oldPath = currentWallpapers[screenName] || "" + var wallpaperChanged = (oldPath !== path) + + if (!wallpaperChanged) { + // No change needed + return } + // Update cache directly + currentWallpapers[screenName] = path + + // Update Settings - still need immutable update for Settings persistence + // The slice() ensures Settings detects the change and saves properly + var monitors = Settings.data.wallpaper.monitors || [] + var found = false + + var newMonitors = monitors.map(function (monitor) { + if (monitor.name === screenName) { + found = true + return { + "name": screenName, + "directory": monitor.directory || getMonitorDirectory(screenName), + "wallpaper": path + } + } + return monitor + }) + + if (!found) { + newMonitors.push({ + "name": screenName, + "directory": getMonitorDirectory(screenName), + "wallpaper": path + }) + } + + Settings.data.wallpaper.monitors = newMonitors.slice() + + // Emit signal for this specific wallpaper change + root.wallpaperChanged(screenName, path) + // Restart the random wallpaper timer if (randomWallpaperTimer.running) { randomWallpaperTimer.restart() } - - // Notify ColorScheme service if the wallpaper actually changed - if (wallpaperChanged) { - ColorSchemeService.changedWallpaper() - } } // ------------------------------------------------------------------- @@ -200,7 +279,7 @@ Singleton { function toggleRandomWallpaper() { Logger.log("Wallpaper", "toggleRandomWallpaper") if (Settings.data.wallpaper.randomEnabled) { - randomWallpaperTimer.restart() + restartRandomWallpaperTimer() setRandomWallpaper() } } @@ -208,8 +287,7 @@ Singleton { // ------------------------------------------------------------------- function restartRandomWallpaperTimer() { if (Settings.data.wallpaper.isRandom) { - randomWallpaperTimer.stop() - randomWallpaperTimer.start() + randomWallpaperTimer.restart() } } @@ -255,23 +333,35 @@ Singleton { model: Quickshell.screens delegate: FolderListModel { property string screenName: modelData.name + property string currentDirectory: root.getMonitorDirectory(screenName) - folder: "file://" + root.getMonitorDirectory(screenName) + folder: "file://" + currentDirectory nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"] showDirs: false sortField: FolderListModel.Name + + // Watch for directory changes via property binding + onCurrentDirectoryChanged: { + folder = "file://" + currentDirectory + } + + Component.onCompleted: { + // Connect to directory change signal + root.wallpaperDirectoryChanged.connect(function (screen, directory) { + if (screen === screenName) { + currentDirectory = directory + } + }) + } + onStatusChanged: { if (status === FolderListModel.Null) { // Flush the list - var lists = root.wallpaperLists - lists[screenName] = [] - root.wallpaperLists = lists + root.wallpaperLists[screenName] = [] + root.wallpaperListChanged(screenName, 0) } else if (status === FolderListModel.Loading) { // Flush the list - var lists = root.wallpaperLists - lists[screenName] = [] - root.wallpaperLists = lists - + root.wallpaperLists[screenName] = [] scanningCount++ } else if (status === FolderListModel.Ready) { var files = [] @@ -281,12 +371,12 @@ Singleton { files.push(filepath) } - var lists = root.wallpaperLists - lists[screenName] = files - root.wallpaperLists = lists + // Update the list + root.wallpaperLists[screenName] = files scanningCount-- Logger.log("Wallpaper", "List refreshed for", screenName, "count:", files.length) + root.wallpaperListChanged(screenName, files.length) } } }