pragma Singleton import QtQuick import Quickshell import Quickshell.Io import "../Helpers/QtObj2JS.js" as QtObj2JS import qs.Services.Power import qs.Services.System import qs.Services.UI // 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 }) // UI state: settings panel, etc. property var ui: ({ settingsSidebarExpanded: true }) // Telemetry state property var telemetry: ({ instanceId: "" }) // Launcher app usage counts property var launcherUsage: ({}) } 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; } } } // Launcher usage function getLauncherUsageCount(key) { const m = adapter.launcherUsage; if (!m) return 0; const v = m[key]; return typeof v === 'number' && isFinite(v) ? v : 0; } function recordLauncherUsage(key) { let counts = Object.assign({}, adapter.launcherUsage || {}); counts[key] = getLauncherUsageCount(key) + 1; adapter.launcherUsage = counts; save(); } // Migrate usage from one key to another, merging counts in a single save function migrateLauncherUsage(fromKey, toKey) { let counts = Object.assign({}, adapter.launcherUsage || {}); const fromCount = typeof counts[fromKey] === 'number' && isFinite(counts[fromKey]) ? counts[fromKey] : 0; const toCount = typeof counts[toKey] === 'number' && isFinite(counts[toKey]) ? counts[toKey] : 0; counts[toKey] = toCount + fromCount; delete counts[fromKey]; adapter.launcherUsage = counts; save(); } // Debounced save timer Timer { id: saveTimer interval: 500 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 }; } // UI state function setUiState(stateData) { adapter.ui = stateData; save(); } function getUiState() { return adapter.ui || { settingsSidebarExpanded: true }; } function setSettingsSidebarExpanded(expanded) { let uiState = getUiState(); uiState.settingsSidebarExpanded = expanded; setUiState(uiState); } function getSettingsSidebarExpanded() { return getUiState().settingsSidebarExpanded !== false; // default to true } // Telemetry state function setTelemetryState(stateData) { adapter.telemetry = stateData; save(); } function getTelemetryState() { return adapter.telemetry || { instanceId: "" }; } function getTelemetryInstanceId() { return getTelemetryState().instanceId || ""; } function setTelemetryInstanceId(instanceId) { let state = getTelemetryState(); state.instanceId = instanceId; setTelemetryState(state); } // ----------------------------------------------------- function buildStateSnapshot() { try { const settingsData = QtObj2JS.qtObjectToPlainObject(Settings.data); const shellStateData = ShellState?.data ? QtObj2JS.qtObjectToPlainObject(ShellState.data) || {} : {}; return { settings: settingsData, state: { doNotDisturb: NotificationService.doNotDisturb, noctaliaPerformanceMode: PowerProfileService.noctaliaPerformanceMode, barVisible: BarService.isVisible, openedPanel: PanelService.openedPanel?.objectName || "", lockScreenActive: PanelService.lockScreen?.active || false, wallpapers: WallpaperService.getWallpapersEffectiveMap(), desktopWidgetsEditMode: DesktopWidgetRegistry.editMode || false, // ------------- display: shellStateData.display || {}, notificationsState: shellStateData.notificationsState || {}, changelogState: shellStateData.changelogState || {}, colorSchemesList: shellStateData.colorSchemesList || {}, ui: shellStateData.ui || {} } }; } catch (error) { Logger.e("Settings", "Failed to build state snapshot:", error); return null; } } }