pragma Singleton import QtQuick import Quickshell import Quickshell.Io // Centralized shell state management for small cache files Singleton { id: root property string stateFile: "" property bool isLoaded: false // State properties for different services readonly property alias data: adapter // Signals for state changes signal displayStateChanged signal notificationsStateChanged signal changelogStateChanged signal colorSchemesListChanged Component.onCompleted: { // Setup state file path (needs Settings to be available) Qt.callLater(() => { if (typeof Settings !== 'undefined' && Settings.cacheDir) { stateFile = Settings.cacheDir + "shell-state.json"; stateFileView.path = stateFile; } }); } // FileView for shell state FileView { id: stateFileView printErrors: false watchChanges: false adapter: JsonAdapter { id: adapter // CompositorService: display scales property var display: ({}) // NotificationService: notification state property var notificationsState: ({ lastSeenTs: 0 }) // UpdateService: changelog state property var changelogState: ({ lastSeenVersion: "" }) // SchemeDownloader: color schemes list property var colorSchemesList: ({ schemes: [], timestamp: 0 }) // WallpaperService: current wallpapers per screen property var wallpapers: ({}) } onLoaded: { root.isLoaded = true; Logger.d("ShellState", "Loaded state file"); } onLoadFailed: error => { if (error === 2) { // File doesn't exist, will be created on first write root.isLoaded = true; Logger.d("ShellState", "State file doesn't exist, will create on first write"); } else { Logger.e("ShellState", "Failed to load state file:", error); root.isLoaded = true; } } } // Debounced save timer Timer { id: saveTimer interval: 300 onTriggered: performSave() } property bool saveQueued: false function save() { saveQueued = true; saveTimer.restart(); } function performSave() { if (!saveQueued || !stateFile) { return; } saveQueued = false; try { // Ensure cache directory exists Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]); Qt.callLater(() => { try { stateFileView.writeAdapter(); Logger.d("ShellState", "Saved state file"); } catch (writeError) { Logger.e("ShellState", "Failed to write state file:", writeError); } }); } catch (error) { Logger.e("ShellState", "Failed to save state:", error); } } // Convenience functions for each service // Display state (CompositorService) function setDisplay(displayData) { adapter.display = displayData; save(); displayStateChanged(); } function getDisplay() { return adapter.display || {}; } // Notifications state (NotificationService) function setNotificationsState(stateData) { adapter.notificationsState = stateData; save(); notificationsStateChanged(); } function getNotificationsState() { return adapter.notificationsState || { lastSeenTs: 0 }; } // Changelog state (UpdateService) function setChangelogState(stateData) { adapter.changelogState = stateData; save(); changelogStateChanged(); } function getChangelogState() { return adapter.changelogState || { lastSeenVersion: "" }; } // Color schemes list (SchemeDownloader) function setColorSchemesList(listData) { adapter.colorSchemesList = listData; save(); colorSchemesListChanged(); } function getColorSchemesList() { return adapter.colorSchemesList || { schemes: [], timestamp: 0 }; } // Wallpapers (WallpaperService) function setWallpapers(wallpapersData) { adapter.wallpapers = wallpapersData; save(); } function getWallpapers() { return adapter.wallpapers || {}; } }