diff --git a/config/quickshell/.config/quickshell/.vscode/settings.json b/config/quickshell/.config/quickshell/.vscode/settings.json index 7c2feb7..ad92582 100644 --- a/config/quickshell/.config/quickshell/.vscode/settings.json +++ b/config/quickshell/.config/quickshell/.vscode/settings.json @@ -1,3 +1,3 @@ { - "editor.formatOnSave": false + "editor.formatOnSave": true } diff --git a/config/quickshell/.config/quickshell/Modules/Background/Background.qml b/config/quickshell/.config/quickshell/Modules/Background/Background.qml index 1630641..1c833af 100644 --- a/config/quickshell/.config/quickshell/Modules/Background/Background.qml +++ b/config/quickshell/.config/quickshell/Modules/Background/Background.qml @@ -1,4 +1,5 @@ import QtQuick +import QtQuick.Effects import Quickshell import Quickshell.Wayland import qs.Constants @@ -8,9 +9,20 @@ Variants { model: Quickshell.screens Item { + id: root + property var modelData + readonly property color tintColor: BackgroundService.tintColor + readonly property real tintOpacity: BackgroundService.tintOpacity + readonly property real blurPercentage: BackgroundService.blurPercentage + readonly property real blurRadius: BackgroundService.blurRadius PanelWindow { + id: bgWindow + + readonly property bool doBlur: BarService.focusMode && (BackgroundService.previewPath === "") + readonly property string imagePath: BackgroundService.previewPath || BackgroundService.cachedPath + screen: modelData WlrLayershell.namespace: "quickshell-background" WlrLayershell.layer: WlrLayer.Background @@ -28,50 +40,81 @@ Variants { color: Colors.mSurface Item { - id: bgManager - - property string activeSource: BackgroundService.previewPath || (BarService.focusMode ? BackgroundService.cachedBlurredPath : BackgroundService.cachedPath) - property bool showFirst: true - anchors.fill: parent - onActiveSourceChanged: { - showFirst = !showFirst; - if (showFirst) - img1.source = activeSource; - else - img2.source = activeSource; - } - Component.onCompleted: { - if (showFirst) - img1.source = activeSource; - else - img2.source = activeSource; - } - Image { - id: img1 + Item { + id: bgManager + + property string activeSource: bgWindow.imagePath + property bool showFirst: true anchors.fill: parent - fillMode: Image.PreserveAspectCrop - opacity: (bgManager.showFirst && status === Image.Ready) ? 1 : 0 + visible: false + onActiveSourceChanged: { + showFirst = !showFirst; + if (showFirst) + bgImg1.source = activeSource; + else + bgImg2.source = activeSource; + } + Component.onCompleted: { + if (showFirst) + bgImg1.source = activeSource; + else + bgImg2.source = activeSource; + } + + Image { + id: bgImg1 + + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + opacity: (bgManager.showFirst && status === Image.Ready) ? 1 : 0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationSlow + } + + } + + } + + Image { + id: bgImg2 + + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + opacity: (!bgManager.showFirst && status === Image.Ready) ? 1 : 0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationSlow + } - Behavior on opacity { - NumberAnimation { - duration: Style.animationSlow } } } - Image { - id: img2 + MultiEffect { + source: bgManager + anchors.fill: bgManager + colorizationColor: root.tintColor + colorization: bgWindow.doBlur ? root.tintOpacity : 0 + blurEnabled: true + blur: bgWindow.doBlur ? root.blurPercentage : 0 + blurMax: root.blurRadius - anchors.fill: parent - fillMode: Image.PreserveAspectCrop - opacity: (!bgManager.showFirst && status === Image.Ready) ? 1 : 0 + Behavior on blur { + NumberAnimation { + duration: Style.animationSlow + } - Behavior on opacity { + } + + Behavior on colorization { NumberAnimation { duration: Style.animationSlow } @@ -87,6 +130,11 @@ Variants { } PanelWindow { + id: bdWindow + + property bool doBlur: true + property string imagePath: BackgroundService.cachedPath + screen: modelData WlrLayershell.namespace: "quickshell-backdrop" WlrLayershell.layer: WlrLayer.Background @@ -104,50 +152,81 @@ Variants { color: Colors.mSurface Item { - id: backdropManager - - property string activeSource: BackgroundService.cachedBlurredPath - property bool showFirst: true - anchors.fill: parent - onActiveSourceChanged: { - showFirst = !showFirst; - if (showFirst) - backImg1.source = activeSource; - else - backImg2.source = activeSource; - } - Component.onCompleted: { - if (showFirst) - backImg1.source = activeSource; - else - backImg2.source = activeSource; - } - Image { - id: backImg1 + Item { + id: backdropManager + + property string activeSource: bdWindow.imagePath + property bool showFirst: true anchors.fill: parent - fillMode: Image.PreserveAspectCrop - opacity: (backdropManager.showFirst && status === Image.Ready) ? 1 : 0 + visible: false + onActiveSourceChanged: { + showFirst = !showFirst; + if (showFirst) + bdImg1.source = activeSource; + else + bdImg2.source = activeSource; + } + Component.onCompleted: { + if (showFirst) + bdImg1.source = activeSource; + else + bdImg2.source = activeSource; + } + + Image { + id: bdImg1 + + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + opacity: (backdropManager.showFirst && status === Image.Ready) ? 1 : 0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationSlow + } + + } + + } + + Image { + id: bdImg2 + + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + opacity: (!backdropManager.showFirst && status === Image.Ready) ? 1 : 0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationSlow + } - Behavior on opacity { - NumberAnimation { - duration: Style.animationSlow } } } - Image { - id: backImg2 + MultiEffect { + source: backdropManager + anchors.fill: backdropManager + colorizationColor: root.tintColor + colorization: bdWindow.doBlur ? root.tintOpacity : 0 + blurEnabled: true + blur: bdWindow.doBlur ? root.blurPercentage : 0 + blurMax: root.blurRadius - anchors.fill: parent - fillMode: Image.PreserveAspectCrop - opacity: (!backdropManager.showFirst && status === Image.Ready) ? 1 : 0 + Behavior on blur { + NumberAnimation { + duration: Style.animationSlow + } - Behavior on opacity { + } + + Behavior on colorization { NumberAnimation { duration: Style.animationSlow } diff --git a/config/quickshell/.config/quickshell/Modules/Sidebar/Modules/ConnectionCard.qml b/config/quickshell/.config/quickshell/Modules/Sidebar/Modules/ConnectionCard.qml index ecb368d..75e7711 100644 --- a/config/quickshell/.config/quickshell/Modules/Sidebar/Modules/ConnectionCard.qml +++ b/config/quickshell/.config/quickshell/Modules/Sidebar/Modules/ConnectionCard.qml @@ -8,7 +8,7 @@ import qs.Services UBox { id: root - property string currentPanel: ShellState.leftSiderbarTab // "bluetooth", "wifi" + readonly property string currentPanel: ShellState.leftSiderbarTab // "bluetooth", "wifi" implicitHeight: (root.currentPanel === "bluetooth" ? btContentLoader.implicitHeight : wifiContentLoader.implicitHeight) + toggleGroup.implicitHeight + Style.marginXS * 2 + Style.marginS * 2 diff --git a/config/quickshell/.config/quickshell/Modules/Sidebar/Modules/NotificationNoteToggleCard.qml b/config/quickshell/.config/quickshell/Modules/Sidebar/Modules/NotificationNoteToggleCard.qml index 0e42e0e..5942da5 100644 --- a/config/quickshell/.config/quickshell/Modules/Sidebar/Modules/NotificationNoteToggleCard.qml +++ b/config/quickshell/.config/quickshell/Modules/Sidebar/Modules/NotificationNoteToggleCard.qml @@ -8,7 +8,7 @@ import qs.Services Item { id: root - property string currentPanel: ShellState.rightSiderbarTab // "notifications", "notes" + readonly property string currentPanel: ShellState.rightSiderbarTab // "notifications", "notes" ColumnLayout { anchors.fill: parent diff --git a/config/quickshell/.config/quickshell/Services/BackgroundService.qml b/config/quickshell/.config/quickshell/Services/BackgroundService.qml index dfa015a..93bb070 100644 --- a/config/quickshell/.config/quickshell/Services/BackgroundService.qml +++ b/config/quickshell/.config/quickshell/Services/BackgroundService.qml @@ -8,14 +8,15 @@ pragma Singleton Singleton { id: root - property string backgroundWidth: "2560" - property string backgroundHeight: "1440" + readonly property string backgroundWidth: "2560" + readonly property string backgroundHeight: "1440" property string cachedPath: "" - property string cachedBlurredPath: "" property string previewPath: "" // Preserved for getBlurredOverview - property string tintColor: Colors.mSurface - property bool isDarkMode: false + readonly property string tintColor: Colors.mSurface + readonly property real tintOpacity: 0.5 + readonly property real blurPercentage: 1 + readonly property real blurRadius: 32 function loadBackground() { if (!SettingsService.backgroundPath) { @@ -29,14 +30,6 @@ Singleton { } cachedPath = path; Logger.i("BackgroundService", "Loaded background image as cached path: " + path); - ImageCacheService.getBlurredOverview(SettingsService.backgroundPath, backgroundWidth, backgroundHeight, tintColor, isDarkMode, function(blurredPath) { - if (!blurredPath) { - Logger.e("BackgroundService", "Failed to load blurred background image from path: " + SettingsService.backgroundPath); - return ; - } - cachedBlurredPath = blurredPath; - Logger.i("BackgroundService", "Loaded blurred background image as cached blurred path: " + blurredPath); - }); }); } @@ -63,17 +56,19 @@ Singleton { if (!exists) return ; - SettingsService.backgroundPath = path; + loadWallpaperDebouncer.pendingPath = path; loadWallpaperDebouncer.start(); }); } Component.onCompleted: { + loadWallpaperDebouncer.pendingPath = SettingsService.backgroundPath; loadWallpaperDebouncer.start(); } Connections { function onBackgroundPathChanged() { + loadWallpaperDebouncer.pendingPath = SettingsService.backgroundPath; loadWallpaperDebouncer.start(); } @@ -83,10 +78,13 @@ Singleton { Timer { id: loadWallpaperDebouncer + property string pendingPath: "" + interval: 200 running: false repeat: false onTriggered: { + SettingsService.backgroundPath = pendingPath; root.loadBackground(); } } diff --git a/config/quickshell/.config/quickshell/Services/ImageCacheService.qml b/config/quickshell/.config/quickshell/Services/ImageCacheService.qml index 786dddd..517dd60 100644 --- a/config/quickshell/.config/quickshell/Services/ImageCacheService.qml +++ b/config/quickshell/.config/quickshell/Services/ImageCacheService.qml @@ -17,11 +17,8 @@ Singleton { property bool initialized: false // Cache directories readonly property string baseDir: Paths.cacheDir + "images/" - readonly property string wpThumbDir: baseDir + "wallpapers/thumbnails/" readonly property string wpLargeDir: baseDir + "wallpapers/large/" - readonly property string wpOverviewDir: baseDir + "wallpapers/overview/" readonly property string notificationsDir: baseDir + "notifications/" - readonly property string contributorsDir: baseDir + "contributors/" // Supported image formats - extended list when ImageMagick is available readonly property var basicImageFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.bmp"] readonly property var extendedImageFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.bmp", "*.webp", "*.avif", "*.heic", "*.heif", "*.tiff", "*.tif", "*.pnm", "*.pgm", "*.ppm", "*.pbm", "*.svg", "*.svgz", "*.ico", "*.icns", "*.jxl", "*.jp2", "*.j2k", "*.exr", "*.hdr", "*.dds", "*.tga"] @@ -67,41 +64,18 @@ Singleton { } function createDirectories() { - Quickshell.execDetached(["mkdir", "-p", wpThumbDir]); Quickshell.execDetached(["mkdir", "-p", wpLargeDir]); - Quickshell.execDetached(["mkdir", "-p", wpOverviewDir]); Quickshell.execDetached(["mkdir", "-p", notificationsDir]); - Quickshell.execDetached(["mkdir", "-p", contributorsDir]); } function cleanupOldCache() { - const dirs = [wpThumbDir, wpLargeDir, wpOverviewDir, notificationsDir, contributorsDir]; + const dirs = [wpLargeDir, notificationsDir]; dirs.forEach(function(dir) { Quickshell.execDetached(["find", dir, "-type", "f", "-mtime", "+30", "-delete"]); }); Logger.d("ImageCache", "Cleanup triggered for files older than 30 days"); } - // ------------------------------------------------- - // Public API: Get Thumbnail (384x384) - // ------------------------------------------------- - function getThumbnail(sourcePath, callback) { - if (!sourcePath || sourcePath === "") { - callback("", false); - return ; - } - getMtime(sourcePath, function(mtime) { - const cacheKey = generateThumbnailKey(sourcePath, mtime); - const cachedPath = wpThumbDir + cacheKey + ".png"; - processRequest(cacheKey, cachedPath, sourcePath, callback, function() { - if (imageMagickAvailable) - startThumbnailProcessing(sourcePath, cachedPath, cacheKey); - else - queueFallbackProcessing(sourcePath, cachedPath, cacheKey, 384); - }); - }); - } - // ------------------------------------------------- // Public API: Get Large Image (scaled to specified dimensions) // ------------------------------------------------- @@ -209,57 +183,9 @@ Singleton { }); } - // ------------------------------------------------- - // Public API: Get Circular Avatar (256x256) - // ------------------------------------------------- - function getCircularAvatar(url, username, callback) { - if (!url || !username) { - callback("", false); - return ; - } - const cacheKey = username; - const cachedPath = contributorsDir + username + "_circular.png"; - processRequest(cacheKey, cachedPath, url, callback, function() { - if (imageMagickAvailable) { - downloadAndProcessAvatar(url, username, cachedPath, cacheKey); - } else { - // No fallback for circular avatars without ImageMagick - Logger.w("ImageCache", "Circular avatars require ImageMagick"); - notifyCallbacks(cacheKey, "", false); - } - }); - } - - // ------------------------------------------------- - // Public API: Get Blurred Overview (for Niri overview background) - // ------------------------------------------------- - function getBlurredOverview(sourcePath, width, height, tintColor, isDarkMode, callback) { - if (!sourcePath || sourcePath === "") { - callback("", false); - return ; - } - if (!imageMagickAvailable) { - Logger.d("ImageCache", "ImageMagick not available for overview blur, using original:", sourcePath); - callback(sourcePath, false); - return ; - } - getMtime(sourcePath, function(mtime) { - const cacheKey = generateOverviewKey(sourcePath, width, height, tintColor, isDarkMode, mtime); - const cachedPath = wpOverviewDir + cacheKey + ".png"; - processRequest(cacheKey, cachedPath, sourcePath, callback, function() { - startOverviewProcessing(sourcePath, cachedPath, width, height, tintColor, isDarkMode, cacheKey); - }); - }); - } - // ------------------------------------------------- // Cache Key Generation // ------------------------------------------------- - function generateThumbnailKey(sourcePath, mtime) { - const keyString = sourcePath + "@384x384@" + (mtime || "unknown"); - return Checksum.sha256(keyString); - } - function generateLargeKey(sourcePath, width, height, mtime) { const keyString = sourcePath + "@" + width + "x" + height + "@" + (mtime || "unknown"); return Checksum.sha256(keyString); @@ -272,11 +198,6 @@ Singleton { return Checksum.sha256(imageUri); } - function generateOverviewKey(sourcePath, width, height, tintColor, isDarkMode, mtime) { - const keyString = sourcePath + "@" + width + "x" + height + "@" + tintColor + "@" + (isDarkMode ? "dark" : "light") + "@" + (mtime || "unknown"); - return Checksum.sha256(keyString); - } - // ------------------------------------------------- // Request Processing (with coalescing) // ------------------------------------------------- @@ -325,17 +246,6 @@ Singleton { processingFailed(cacheKey, "Processing failed"); } - // ------------------------------------------------- - // ImageMagick Processing: Thumbnail - // ------------------------------------------------- - function startThumbnailProcessing(sourcePath, outputPath, cacheKey) { - const srcEsc = sourcePath.replace(/'/g, "'\\''"); - const dstEsc = outputPath.replace(/'/g, "'\\''"); - // Use Lanczos filter for high-quality downscaling, subtle unsharp mask, and PNG for lossless output - const command = `magick '${srcEsc}' -auto-orient -filter Lanczos -resize '384x384^' -gravity center -extent 384x384 -unsharp 0x0.5 '${dstEsc}'`; - runProcess(command, cacheKey, outputPath, sourcePath); - } - // ------------------------------------------------- // ImageMagick Processing: Large // ------------------------------------------------- @@ -347,79 +257,6 @@ Singleton { runProcess(command, cacheKey, outputPath, sourcePath); } - // ------------------------------------------------- - // ImageMagick Processing: Blurred Overview - // ------------------------------------------------- - function startOverviewProcessing(sourcePath, outputPath, width, height, tintColor, isDarkMode, cacheKey) { - const srcEsc = sourcePath.replace(/'/g, "'\\''"); - const dstEsc = outputPath.replace(/'/g, "'\\''"); - // Resize, blur, then tint overlay - const command = `magick '${srcEsc}' -auto-orient -resize '${width}x${height}' -gravity center -blur 0x20 \\( +clone -fill '${tintColor}' -colorize 100 -alpha set -channel A -evaluate set 50% +channel \\) -composite '${dstEsc}'`; - runProcess(command, cacheKey, outputPath, sourcePath); - } - - // ------------------------------------------------- - // ImageMagick Processing: Circular Avatar - // ------------------------------------------------- - function downloadAndProcessAvatar(url, username, outputPath, cacheKey) { - const tempPath = contributorsDir + username + "_temp.png"; - const tempEsc = tempPath.replace(/'/g, "'\\''"); - const urlEsc = url.replace(/'/g, "'\\''"); - // Download first (uses utility queue since curl/wget are lightweight) - const downloadCmd = `curl -L -s -o '${tempEsc}' '${urlEsc}' || wget -q -O '${tempEsc}' '${urlEsc}'`; - const processString = ` - import QtQuick - import Quickshell.Io - Process { - command: ["sh", "-c", "${downloadCmd.replace(/"/g, '\\"')}"] - stdout: StdioCollector {} - stderr: StdioCollector {} - } - `; - queueUtilityProcess({ - "name": "DownloadProcess_" + cacheKey, - "processString": processString, - "onComplete": function(exitCode) { - if (exitCode !== 0) { - Logger.e("ImageCache", "Failed to download avatar for", username); - notifyCallbacks(cacheKey, "", false); - return ; - } - // Now process with ImageMagick - processCircularAvatar(tempPath, outputPath, cacheKey); - }, - "onError": function() { - notifyCallbacks(cacheKey, "", false); - } - }); - } - - function processCircularAvatar(inputPath, outputPath, cacheKey) { - const srcEsc = inputPath.replace(/'/g, "'\\''"); - const dstEsc = outputPath.replace(/'/g, "'\\''"); - // ImageMagick command for circular crop with alpha - const command = `magick '${srcEsc}' -resize 256x256^ -gravity center -extent 256x256 -alpha set \\( +clone -channel A -evaluate set 0 +channel -fill white -draw 'circle 128,128 128,0' \\) -compose DstIn -composite '${dstEsc}'`; - queueImageMagickProcess({ - "command": command, - "cacheKey": cacheKey, - "onComplete": function(exitCode) { - // Clean up temp file - Quickshell.execDetached(["rm", "-f", inputPath]); - if (exitCode !== 0) { - Logger.e("ImageCache", "Failed to create circular avatar"); - notifyCallbacks(cacheKey, "", false); - } else { - Logger.d("ImageCache", "Circular avatar created:", outputPath); - notifyCallbacks(cacheKey, outputPath, true); - } - }, - "onError": function() { - Quickshell.execDetached(["rm", "-f", inputPath]); - notifyCallbacks(cacheKey, "", false); - } - }); - } - // Queue an ImageMagick process and run it when a slot is available function queueImageMagickProcess(request) { imageMagickQueue.push(request); @@ -616,13 +453,6 @@ Singleton { // ------------------------------------------------- // Cache Invalidation // ------------------------------------------------- - function invalidateThumbnail(sourcePath) { - Logger.i("ImageCache", "Invalidating thumbnail for:", sourcePath); - // Since cache keys include hash, we'd need to track mappings - // For simplicity, clear all thumbnails - clearThumbnails(); - } - function invalidateLarge(sourcePath) { Logger.i("ImageCache", "Invalidating large for:", sourcePath); clearLarge(); @@ -633,26 +463,13 @@ Singleton { Quickshell.execDetached(["rm", "-f", path]); } - function invalidateAvatar(username) { - const path = contributorsDir + username + "_circular.png"; - Quickshell.execDetached(["rm", "-f", path]); - } - // ------------------------------------------------- // Clear Cache Functions // ------------------------------------------------- function clearAll() { Logger.i("ImageCache", "Clearing all cache"); - clearThumbnails(); clearLarge(); clearNotifications(); - clearContributors(); - } - - function clearThumbnails() { - Logger.i("ImageCache", "Clearing thumbnails cache"); - Quickshell.execDetached(["rm", "-rf", wpThumbDir]); - Quickshell.execDetached(["mkdir", "-p", wpThumbDir]); } function clearLarge() { @@ -667,82 +484,6 @@ Singleton { Quickshell.execDetached(["mkdir", "-p", notificationsDir]); } - function clearContributors() { - Logger.i("ImageCache", "Clearing contributors cache"); - Quickshell.execDetached(["rm", "-rf", contributorsDir]); - Quickshell.execDetached(["mkdir", "-p", contributorsDir]); - } - - // ------------------------------------------------- - // Qt Fallback Renderer - // ------------------------------------------------- - PanelWindow { - id: fallbackRenderer - - implicitWidth: 0 - implicitHeight: 0 - WlrLayershell.exclusionMode: ExclusionMode.Ignore - WlrLayershell.namespace: "noctalia-image-cache-renderer" - color: "transparent" - - Image { - id: fallbackImage - - property string cacheKey: "" - property string destPath: "" - property int targetSize: 256 - - function processNextFallback() { - cacheKey = ""; - destPath = ""; - source = ""; - if (fallbackQueue.length > 0) { - const next = fallbackQueue.shift(); - cacheKey = next.cacheKey; - destPath = next.destPath; - targetSize = next.size; - source = next.sourcePath; - } else { - fallbackProcessing = false; - } - } - - width: targetSize - height: targetSize - visible: true - cache: false - asynchronous: true - fillMode: Image.PreserveAspectCrop - mipmap: true - antialiasing: true - onStatusChanged: { - if (!cacheKey) - return ; - - if (status === Image.Ready) { - grabToImage(function(result) { - if (result.saveToFile(destPath)) { - Logger.d("ImageCache", "Fallback cache created:", destPath); - root.notifyCallbacks(cacheKey, destPath, true); - } else { - Logger.e("ImageCache", "Failed to save fallback cache"); - root.notifyCallbacks(cacheKey, "", false); - } - processNextFallback(); - }); - } else if (status === Image.Error) { - Logger.e("ImageCache", "Fallback image load failed"); - root.notifyCallbacks(cacheKey, "", false); - processNextFallback(); - } - } - } - - mask: Region { - } - - } - // ------------------------------------------------- // ImageMagick Detection // ------------------------------------------------- diff --git a/config/quickshell/.config/quickshell/Services/LyricsService.qml b/config/quickshell/.config/quickshell/Services/LyricsService.qml index c732355..542c06d 100644 --- a/config/quickshell/.config/quickshell/Services/LyricsService.qml +++ b/config/quickshell/.config/quickshell/Services/LyricsService.qml @@ -21,7 +21,7 @@ Singleton { // line 2 <- current line // line 3 property var lyrics: Array(linesCount).fill(" ") - property bool showLyricsBar: ShellState.lyricsState.showLyricsBar || false + readonly property bool showLyricsBar: ShellState.lyricsState.showLyricsBar || false function toggleLyricsBar() { ShellState.lyricsState = { diff --git a/config/quickshell/.config/quickshell/Services/NotificationService.qml b/config/quickshell/.config/quickshell/Services/NotificationService.qml index 48c2860..d162324 100644 --- a/config/quickshell/.config/quickshell/Services/NotificationService.qml +++ b/config/quickshell/.config/quickshell/Services/NotificationService.qml @@ -20,8 +20,8 @@ Singleton { property string historyFile: Paths.cacheDir + "notifications.json" // State - property real lastSeenTs: ShellState.notificationsState.lastSeenTs || 0 - property bool doNotDisturb: ShellState.notificationsState.doNotDisturb || false + readonly property real lastSeenTs: ShellState.notificationsState.lastSeenTs || 0 + readonly property bool doNotDisturb: ShellState.notificationsState.doNotDisturb || false // Models property ListModel activeList: ListModel {} diff --git a/config/quickshell/.config/quickshell/Services/SunsetService.qml b/config/quickshell/.config/quickshell/Services/SunsetService.qml index 6348ba2..ea368e6 100644 --- a/config/quickshell/.config/quickshell/Services/SunsetService.qml +++ b/config/quickshell/.config/quickshell/Services/SunsetService.qml @@ -11,7 +11,7 @@ Singleton { property double _latitude: -1 property double _longitude: -1 property int temperature: 0 - property bool isEnabled: ShellState.sunsetState.enabled || false + readonly property bool isEnabled: ShellState.sunsetState.enabled || false function toggleSunset() { ShellState.sunsetState = {