1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-12 16:38:28 -04:00

settings: diff-only configs, machine-state split, backup CLI

- settings.json/session.json now store only values that differ from
  spec defaults
- Machine-specific state moves out of settings.json into session.json
- Add dms backup create/restore: tar.gz of ~/.config/DankMaterialShell

fixes #3027
This commit is contained in:
bbedward
2026-08-11 15:32:01 -04:00
parent 373a3d3136
commit 7974887295
22 changed files with 748 additions and 252 deletions
+62
View File
@@ -0,0 +1,62 @@
package main
import (
"fmt"
"os"
"github.com/AvengeMedia/DankMaterialShell/core/internal/backup"
"github.com/spf13/cobra"
)
var backupOutputPath string
var backupCmd = &cobra.Command{
Use: "backup",
Short: "Backup and restore DMS configuration",
}
var backupCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a tar.gz backup of ~/.config/DankMaterialShell (settings, plugins, themes)",
Args: cobra.NoArgs,
Run: runBackupCreate,
}
var backupRestoreCmd = &cobra.Command{
Use: "restore <archive>",
Short: "Restore a DMS configuration backup; the current configuration is moved aside first",
Args: cobra.ExactArgs(1),
Run: runBackupRestore,
}
func init() {
backupCreateCmd.Flags().StringVarP(&backupOutputPath, "output", "o", "", "Output archive path (default dms-backup-<timestamp>.tar.gz)")
backupCmd.AddCommand(backupCreateCmd, backupRestoreCmd)
rootCmd.AddCommand(backupCmd)
}
func runBackupCreate(cmd *cobra.Command, args []string) {
output := backupOutputPath
if output == "" {
output = backup.DefaultArchiveName()
}
if err := backup.Create(output); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Println(output)
}
func runBackupRestore(cmd *cobra.Command, args []string) {
previous, err := backup.Restore(args[0])
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
if previous != "" {
fmt.Printf("Previous configuration moved to %s\n", previous)
}
fmt.Println("Restore complete. Restart the shell to apply.")
}
+198
View File
@@ -0,0 +1,198 @@
package backup
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
)
const dirName = "DankMaterialShell"
func ConfigDir() (string, error) {
configDir, err := os.UserConfigDir()
if err != nil {
return "", err
}
return filepath.Join(configDir, dirName), nil
}
func DefaultArchiveName() string {
return fmt.Sprintf("dms-backup-%s.tar.gz", time.Now().Format("20060102-150405"))
}
func Create(outputPath string) error {
srcDir, err := ConfigDir()
if err != nil {
return err
}
if _, err := os.Stat(srcDir); err != nil {
return fmt.Errorf("no DMS configuration found at %s: %w", srcDir, err)
}
out, err := os.Create(outputPath)
if err != nil {
return err
}
defer out.Close()
gz := gzip.NewWriter(out)
defer gz.Close()
tw := tar.NewWriter(gz)
defer tw.Close()
return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
rel, err := filepath.Rel(srcDir, path)
if err != nil {
return err
}
if rel == "." {
return nil
}
var link string
if info.Mode()&os.ModeSymlink != 0 {
if link, err = os.Readlink(path); err != nil {
return err
}
}
header, err := tar.FileInfoHeader(info, link)
if err != nil {
return err
}
header.Name = filepath.ToSlash(filepath.Join(dirName, rel))
if err := tw.WriteHeader(header); err != nil {
return err
}
if !info.Mode().IsRegular() {
return nil
}
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(tw, f)
return err
})
}
func Restore(archivePath string) (string, error) {
dstDir, err := ConfigDir()
if err != nil {
return "", err
}
if err := validateArchive(archivePath); err != nil {
return "", err
}
previous := ""
if _, err := os.Stat(dstDir); err == nil {
previous = dstDir + ".pre-restore-" + time.Now().Format("20060102-150405")
if err := os.Rename(dstDir, previous); err != nil {
return "", fmt.Errorf("failed to move existing configuration aside: %w", err)
}
}
if err := extract(archivePath, filepath.Dir(dstDir)); err != nil {
if previous != "" {
os.RemoveAll(dstDir)
os.Rename(previous, dstDir)
}
return "", err
}
return previous, nil
}
func validateArchive(archivePath string) error {
hasSettings := false
err := walkArchive(archivePath, func(header *tar.Header, _ *tar.Reader) error {
name := filepath.ToSlash(filepath.Clean(header.Name))
if strings.HasPrefix(name, "..") || filepath.IsAbs(header.Name) {
return fmt.Errorf("unsafe path in archive: %s", header.Name)
}
if !strings.HasPrefix(name, dirName+"/") && name != dirName {
return fmt.Errorf("not a DMS backup: unexpected entry %s", header.Name)
}
if name == dirName+"/settings.json" {
hasSettings = true
}
return nil
})
if err != nil {
return err
}
if !hasSettings {
return fmt.Errorf("not a DMS backup: settings.json missing from archive")
}
return nil
}
func extract(archivePath, destParent string) error {
return walkArchive(archivePath, func(header *tar.Header, tr *tar.Reader) error {
target := filepath.Join(destParent, filepath.Clean(header.Name))
switch header.Typeflag {
case tar.TypeDir:
return os.MkdirAll(target, os.FileMode(header.Mode))
case tar.TypeSymlink:
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
return err
}
return os.Symlink(header.Linkname, target)
case tar.TypeReg:
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
return err
}
f, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(header.Mode))
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, tr)
return err
default:
return nil
}
})
}
func walkArchive(archivePath string, visit func(*tar.Header, *tar.Reader) error) error {
f, err := os.Open(archivePath)
if err != nil {
return err
}
defer f.Close()
gz, err := gzip.NewReader(f)
if err != nil {
return fmt.Errorf("not a gzip archive: %w", err)
}
defer gz.Close()
tr := tar.NewReader(gz)
for {
header, err := tr.Next()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
if err := visit(header, tr); err != nil {
return err
}
}
}
+31 -4
View File
@@ -11,7 +11,7 @@ Singleton {
id: root
readonly property var log: Log.scoped("CacheData")
readonly property int cacheConfigVersion: 2
readonly property int cacheConfigVersion: 3
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericCacheLocation)
readonly property string _stateDir: Paths.strip(_stateUrl)
@@ -21,11 +21,15 @@ Singleton {
property int _loadedCacheVersion: 0
readonly property var _pinKeys: ["brightnessDevicePins", "wifiNetworkPins", "bluetoothDevicePins", "audioInputDevicePins", "audioOutputDevicePins"]
readonly property var _dataKeys: ["wallpaperLastPath", "profileLastPath", "fileBrowserSettings"].concat(_pinKeys)
readonly property var _historyKeys: ["browserUsageHistory", "filePickerUsageHistory"]
readonly property var _dataKeys: ["wallpaperLastPath", "profileLastPath", "fileBrowserSettings"].concat(_pinKeys, _historyKeys)
property string wallpaperLastPath: ""
property string profileLastPath: ""
property var browserUsageHistory: ({})
property var filePickerUsageHistory: ({})
property var brightnessDevicePins: ({})
property var wifiNetworkPins: ({})
property var bluetoothDevicePins: ({})
@@ -131,6 +135,29 @@ Singleton {
saveCache();
}
function migrateUsageHistories(histories) {
if (!histories)
return;
if (!_hasLoaded)
loadCache();
let migrated = false;
for (const key of _historyKeys) {
const legacy = histories[key];
if (!legacy || Object.keys(legacy).length === 0)
continue;
if (Object.keys(root[key] || {}).length > 0)
continue;
root[key] = legacy;
migrated = true;
}
if (!migrated)
return;
log.info("Migrated usage histories from settings.json");
saveCache();
}
function parseCache(content) {
_loading = true;
try {
@@ -172,7 +199,7 @@ Singleton {
};
}
for (const key of _pinKeys) {
for (const key of _pinKeys.concat(_historyKeys)) {
root[key] = cache[key] !== undefined ? cache[key] : {};
}
@@ -198,7 +225,7 @@ Singleton {
"fileBrowserSettings": fileBrowserSettings,
"configVersion": cacheConfigVersion
};
for (const key of _pinKeys) {
for (const key of _pinKeys.concat(_historyKeys)) {
data[key] = root[key];
}
cacheFile.setText(JSON.stringify(data, null, 2));
+167 -1
View File
@@ -14,7 +14,7 @@ Singleton {
id: root
readonly property var log: Log.scoped("SessionData")
readonly property int sessionConfigVersion: 3
readonly property int sessionConfigVersion: 4
signal loaded
signal brightnessDisplayHintChanged(string deviceName)
@@ -221,6 +221,17 @@ Singleton {
property string notepadLastMode: ""
property var niriOutputSettings: ({})
property var hyprlandOutputSettings: ({})
property var activeDisplayProfile: ({})
property var activeDisplayProfileModes: ({})
property var desktopWidgetGridSettings: ({})
property var desktopWidgetInstancePositions: ({})
property var builtInPluginState: ({})
property bool greeterSyncPending: false
property var greeterSyncBaseline: ({})
property string lastAppliedIconTheme: ""
property string launcherLastMode: "all"
property string launcherLastFileSearchType: "all"
property string launcherLastQuery: ""
@@ -393,6 +404,161 @@ Singleton {
Spec.set(root, key, value, saveSettings, _hooks);
}
function importFromSettings(payload) {
if (!payload)
return;
if (!_hasLoaded)
loadSettings();
if (_parseError)
return;
let imported = false;
for (const key in payload) {
if (!(key in Spec.SPEC))
continue;
const current = root[key];
const isEmpty = current === Spec.SPEC[key].def || (typeof current === "object" && current !== null && Object.keys(current).length === 0);
if (!isEmpty)
continue;
root[key] = payload[key];
imported = true;
}
if (imported) {
log.info("Imported machine-specific state from settings.json");
saveSettings();
}
}
function getNiriOutputSetting(outputId, key, defaultValue) {
if (!niriOutputSettings[outputId])
return defaultValue;
return niriOutputSettings[outputId][key] !== undefined ? niriOutputSettings[outputId][key] : defaultValue;
}
function setNiriOutputSetting(outputId, key, value) {
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
if (!updated[outputId])
updated[outputId] = {};
updated[outputId][key] = value;
niriOutputSettings = updated;
saveSettings();
}
function getNiriOutputSettings(outputId) {
const settings = niriOutputSettings[outputId];
return settings ? JSON.parse(JSON.stringify(settings)) : {};
}
function getHyprlandOutputSetting(outputId, key, defaultValue) {
if (!hyprlandOutputSettings[outputId])
return defaultValue;
return hyprlandOutputSettings[outputId][key] !== undefined ? hyprlandOutputSettings[outputId][key] : defaultValue;
}
function setHyprlandOutputSetting(outputId, key, value) {
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
if (!updated[outputId])
updated[outputId] = {};
updated[outputId][key] = value;
hyprlandOutputSettings = updated;
saveSettings();
}
function removeHyprlandOutputSetting(outputId, key) {
if (!hyprlandOutputSettings[outputId] || !(key in hyprlandOutputSettings[outputId]))
return;
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
delete updated[outputId][key];
hyprlandOutputSettings = updated;
saveSettings();
}
function getActiveDisplayProfile(compositor) {
return activeDisplayProfile[compositor] || "";
}
function setActiveDisplayProfile(compositor, profileId) {
const updated = JSON.parse(JSON.stringify(activeDisplayProfile));
updated[compositor] = profileId;
activeDisplayProfile = updated;
saveSettings();
}
function setActiveDisplayProfileModes(compositor, modes) {
if (JSON.stringify(activeDisplayProfileModes[compositor] || {}) === JSON.stringify(modes || {}))
return;
const updated = JSON.parse(JSON.stringify(activeDisplayProfileModes));
updated[compositor] = modes;
activeDisplayProfileModes = updated;
saveSettings();
}
function getDesktopWidgetGridSetting(screenKey, property, defaultValue) {
const val = desktopWidgetGridSettings?.[screenKey]?.[property];
return val !== undefined ? val : defaultValue;
}
function setDesktopWidgetGridSetting(screenKey, property, value) {
const allSettings = JSON.parse(JSON.stringify(desktopWidgetGridSettings || {}));
if (!allSettings[screenKey])
allSettings[screenKey] = {};
allSettings[screenKey][property] = value;
desktopWidgetGridSettings = allSettings;
saveSettings();
}
function updateDesktopWidgetInstancePosition(instanceId, screenKey, positionUpdates) {
const updated = JSON.parse(JSON.stringify(desktopWidgetInstancePositions));
if (!updated[instanceId])
updated[instanceId] = {};
updated[instanceId][screenKey] = Object.assign({}, updated[instanceId][screenKey] || {}, positionUpdates);
desktopWidgetInstancePositions = updated;
saveSettings();
}
function syncDesktopWidgetPositionToAllScreens(instanceId) {
const positions = desktopWidgetInstancePositions[instanceId] || {};
const screenKeys = Object.keys(positions).filter(k => k !== "_synced");
if (screenKeys.length === 0)
return;
const sourcePos = positions[screenKeys[0]];
if (!sourcePos)
return;
const screen = Array.from(Quickshell.screens.values()).find(s => SettingsData.getScreenDisplayName(s) === screenKeys[0]);
if (!screen)
return;
const synced = {};
if (sourcePos.x !== undefined)
synced.x = sourcePos.x / screen.width;
if (sourcePos.y !== undefined)
synced.y = sourcePos.y / screen.height;
if (sourcePos.width !== undefined)
synced.width = sourcePos.width;
if (sourcePos.height !== undefined)
synced.height = sourcePos.height;
const updated = JSON.parse(JSON.stringify(desktopWidgetInstancePositions));
updated[instanceId]["_synced"] = synced;
desktopWidgetInstancePositions = updated;
saveSettings();
}
function removeDesktopWidgetInstancePositions(instanceId) {
if (!(instanceId in desktopWidgetInstancePositions))
return;
const updated = JSON.parse(JSON.stringify(desktopWidgetInstancePositions));
delete updated[instanceId];
desktopWidgetInstancePositions = updated;
saveSettings();
}
function setBuiltInPluginState(pluginId, state) {
const updated = JSON.parse(JSON.stringify(builtInPluginState));
updated[pluginId] = state;
builtInPluginState = updated;
saveSettings();
}
function migrateFromUndefinedToV1(settings) {
if (typeof SettingsData !== "undefined") {
if (settings.acMonitorTimeout !== undefined) {
+60 -152
View File
@@ -15,7 +15,7 @@ Singleton {
id: root
readonly property var log: Log.scoped("SettingsData")
readonly property int settingsConfigVersion: 13
readonly property int settingsConfigVersion: 15
enum Position {
Top,
@@ -102,6 +102,10 @@ Singleton {
updated[pluginId] = {};
updated[pluginId][key] = value;
builtInPluginSettings = updated;
if (Store.SESSION_BACKED_PLUGIN_IDS.includes(pluginId)) {
SessionData.setBuiltInPluginState(pluginId, updated[pluginId]);
return;
}
saveSettings();
}
@@ -536,16 +540,12 @@ Singleton {
property string greeterLockDateFormat: ""
property string greeterFontFamily: ""
property string greeterWallpaperFillMode: ""
property bool greeterSyncPending: false
property var greeterSyncBaseline: ({})
property int mediaSize: 1
property string appLauncherViewMode: "list"
property string spotlightModalViewMode: "list"
property string browserPickerViewMode: "grid"
property var browserUsageHistory: ({})
property string appPickerViewMode: "grid"
property var filePickerUsageHistory: ({})
property bool sortAppsAlphabetically: false
property int appLauncherGridColumns: 4
property bool spotlightCloseNiriOverview: true
@@ -673,7 +673,6 @@ Singleton {
property string iconThemeDark: "System Default"
property string iconThemeLight: "System Default"
property bool iconThemePerMode: false
property string lastAppliedIconTheme: ""
readonly property string iconTheme: resolveIconTheme()
property var availableIconThemes: ["System Default"]
property string systemDefaultIconTheme: ""
@@ -1006,11 +1005,7 @@ Singleton {
property string displayNameMode: "system"
property var screenPreferences: ({})
property var showOnLastDisplay: ({})
property var niriOutputSettings: ({})
property var hyprlandOutputSettings: ({})
property var displayProfiles: ({})
property var activeDisplayProfile: ({})
property var activeDisplayProfileModes: ({})
property var displayPreviousRefreshModes: ({})
property bool displayProfileAutoSelect: false
property bool displayShowDisconnected: false
@@ -1117,24 +1112,9 @@ Singleton {
property var systemMonitorDisplayPreferences: ["all"]
property var systemMonitorVariants: []
property var desktopWidgetPositions: ({})
property var desktopWidgetGridSettings: ({})
property var desktopWidgetInstances: []
property var desktopWidgetGroups: []
function getDesktopWidgetGridSetting(screenKey, property, defaultValue) {
const val = desktopWidgetGridSettings?.[screenKey]?.[property];
return val !== undefined ? val : defaultValue;
}
function setDesktopWidgetGridSetting(screenKey, property, value) {
const allSettings = JSON.parse(JSON.stringify(desktopWidgetGridSettings || {}));
if (!allSettings[screenKey])
allSettings[screenKey] = {};
allSettings[screenKey][property] = value;
desktopWidgetGridSettings = allSettings;
saveSettings();
}
function getDesktopWidgetPosition(pluginId, screenKey, property, defaultValue) {
const pos = desktopWidgetPositions?.[pluginId]?.[screenKey]?.[property];
return pos !== undefined ? pos : defaultValue;
@@ -1185,8 +1165,7 @@ Singleton {
widgetType: widgetType,
name: name || widgetType,
enabled: true,
config: config || {},
positions: {}
config: config || {}
};
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
instances.push(instance);
@@ -1215,53 +1194,10 @@ Singleton {
saveSettings();
}
function updateDesktopWidgetInstancePosition(instanceId, screenKey, positionUpdates) {
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
const idx = instances.findIndex(inst => inst.id === instanceId);
if (idx === -1)
return;
if (!instances[idx].positions)
instances[idx].positions = {};
instances[idx].positions[screenKey] = Object.assign({}, instances[idx].positions[screenKey] || {}, positionUpdates);
desktopWidgetInstances = instances;
saveSettings();
}
function removeDesktopWidgetInstance(instanceId) {
const instances = (desktopWidgetInstances || []).filter(inst => inst.id !== instanceId);
desktopWidgetInstances = instances;
saveSettings();
}
function syncDesktopWidgetPositionToAllScreens(instanceId) {
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
const idx = instances.findIndex(inst => inst.id === instanceId);
if (idx === -1)
return;
const positions = instances[idx].positions || {};
const screenKeys = Object.keys(positions).filter(k => k !== "_synced");
if (screenKeys.length === 0)
return;
const sourceKey = screenKeys[0];
const sourcePos = positions[sourceKey];
if (!sourcePos)
return;
const screen = Array.from(Quickshell.screens.values()).find(s => getScreenDisplayName(s) === sourceKey);
if (!screen)
return;
const screenW = screen.width;
const screenH = screen.height;
const synced = {};
if (sourcePos.x !== undefined)
synced.x = sourcePos.x / screenW;
if (sourcePos.y !== undefined)
synced.y = sourcePos.y / screenH;
if (sourcePos.width !== undefined)
synced.width = sourcePos.width;
if (sourcePos.height !== undefined)
synced.height = sourcePos.height;
instances[idx].positions["_synced"] = synced;
desktopWidgetInstances = instances;
SessionData.removeDesktopWidgetInstancePositions(instanceId);
saveSettings();
}
@@ -1275,8 +1211,7 @@ Singleton {
widgetType: source.widgetType,
name: source.name + " (Copy)",
enabled: source.enabled,
config: JSON.parse(JSON.stringify(source.config || {})),
positions: {}
config: JSON.parse(JSON.stringify(source.config || {}))
};
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
instances.push(instance);
@@ -1424,14 +1359,15 @@ Singleton {
iconThemePerMode = false;
iconThemeDark = "System Default";
iconThemeLight = "System Default";
lastAppliedIconTheme = "";
SessionData.lastAppliedIconTheme = "";
SessionData.saveSettings();
saveSettings();
}
function checkIconThemeDrift() {
if (resolveIconTheme() === "System Default")
return;
if (!lastAppliedIconTheme)
if (!SessionData.lastAppliedIconTheme)
return;
const script = `if command -v gsettings >/dev/null 2>&1; then
gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed "s/'//g"
@@ -1443,7 +1379,7 @@ Singleton {
const platform = (output || "").trim();
if (!platform)
return;
if (platform === root.lastAppliedIconTheme || platform === root.iconThemeDark || platform === root.iconThemeLight)
if (platform === SessionData.lastAppliedIconTheme || platform === root.iconThemeDark || platform === root.iconThemeLight)
return;
root.setIconThemeUnmanaged();
ToastService.showWarning(I18n.tr("Icon theme changed outside DMS; switched to System Default", "shown when an external tool overrides the icon theme DMS applied"));
@@ -1515,7 +1451,8 @@ Singleton {
const gtkThemeName = (resolved === "System Default") ? systemDefaultIconTheme : resolved;
if (gtkThemeName === "System Default" || gtkThemeName === "")
return;
lastAppliedIconTheme = gtkThemeName;
SessionData.lastAppliedIconTheme = gtkThemeName;
SessionData.saveSettings();
if (typeof DMSService !== "undefined" && DMSService.apiVersion >= 3 && typeof PortalService !== "undefined") {
PortalService.setSystemIconTheme(gtkThemeName);
}
@@ -1598,26 +1535,28 @@ Singleton {
}
function markGreeterSyncPending(who, key, oldValue) {
if (!(key in greeterSyncBaseline)) {
var baseline = greeterSyncBaseline;
if (!(key in SessionData.greeterSyncBaseline)) {
var baseline = Object.assign({}, SessionData.greeterSyncBaseline);
baseline[key] = oldValue;
greeterSyncBaseline = baseline;
SessionData.greeterSyncBaseline = baseline;
}
greeterSyncPending = true;
SessionData.greeterSyncPending = true;
SessionData.saveSettings();
}
function clearGreeterSyncPending() {
greeterSyncBaseline = {};
greeterSyncPending = false;
saveSettings();
SessionData.greeterSyncBaseline = {};
SessionData.greeterSyncPending = false;
SessionData.saveSettings();
}
function revertGreeterSyncPending() {
for (var key in greeterSyncBaseline) {
root[key] = greeterSyncBaseline[key];
for (var key in SessionData.greeterSyncBaseline) {
root[key] = SessionData.greeterSyncBaseline[key];
}
greeterSyncBaseline = {};
greeterSyncPending = false;
SessionData.greeterSyncBaseline = {};
SessionData.greeterSyncPending = false;
SessionData.saveSettings();
saveSettings();
}
@@ -1650,6 +1589,8 @@ Singleton {
const oldVersion = obj?.configVersion ?? 0;
const legacyPins = oldVersion < 13 ? Store.extractPins(obj) : null;
const sessionPayload = oldVersion < 15 ? Store.extractSessionPayload(obj) : null;
const cachePayload = oldVersion < 15 ? Store.extractCachePayload(obj) : null;
if (oldVersion < settingsConfigVersion) {
const migrated = Store.migrateToVersion(obj, settingsConfigVersion);
if (migrated) {
@@ -1659,6 +1600,14 @@ Singleton {
}
if (legacyPins)
Qt.callLater(() => CacheData.migratePins(legacyPins));
if (cachePayload)
Qt.callLater(() => CacheData.migrateUsageHistories(cachePayload));
if (sessionPayload) {
Qt.callLater(() => {
SessionData.importFromSettings(sessionPayload);
_mergeSessionState();
});
}
if (obj?.lockScreenActiveMonitor !== undefined) {
var oldVal = obj.lockScreenActiveMonitor;
@@ -1697,6 +1646,7 @@ Singleton {
_loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));
_hasLoaded = true;
_mergeSessionState();
applyStoredTheme();
updateCompositorCursor();
Qt.callLater(checkIconThemeDrift);
@@ -1717,6 +1667,28 @@ Singleton {
property var _pendingMigration: null
function _mergeSessionState() {
if (!_hasLoaded || !SessionData._hasLoaded)
return;
const pluginState = SessionData.builtInPluginState || {};
if (Object.keys(pluginState).length > 0) {
const updated = JSON.parse(JSON.stringify(builtInPluginSettings));
for (const id in pluginState) {
updated[id] = pluginState[id];
}
builtInPluginSettings = updated;
}
}
Connections {
target: SessionData
function onLoaded() {
root._mergeSessionState();
}
}
function _checkSettingsWritable() {
settingsWritableCheckProcess.running = true;
}
@@ -3144,50 +3116,6 @@ Singleton {
return settings ? JSON.parse(JSON.stringify(settings)) : {};
}
function getNiriOutputSetting(outputId, key, defaultValue) {
if (!niriOutputSettings[outputId])
return defaultValue;
return niriOutputSettings[outputId][key] !== undefined ? niriOutputSettings[outputId][key] : defaultValue;
}
function setNiriOutputSetting(outputId, key, value) {
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
if (!updated[outputId])
updated[outputId] = {};
updated[outputId][key] = value;
niriOutputSettings = updated;
saveSettings();
}
function getNiriOutputSettings(outputId) {
const settings = niriOutputSettings[outputId];
return settings ? JSON.parse(JSON.stringify(settings)) : {};
}
function getHyprlandOutputSetting(outputId, key, defaultValue) {
if (!hyprlandOutputSettings[outputId])
return defaultValue;
return hyprlandOutputSettings[outputId][key] !== undefined ? hyprlandOutputSettings[outputId][key] : defaultValue;
}
function setHyprlandOutputSetting(outputId, key, value) {
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
if (!updated[outputId])
updated[outputId] = {};
updated[outputId][key] = value;
hyprlandOutputSettings = updated;
saveSettings();
}
function removeHyprlandOutputSetting(outputId, key) {
if (!hyprlandOutputSettings[outputId] || !(key in hyprlandOutputSettings[outputId]))
return;
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
delete updated[outputId][key];
hyprlandOutputSettings = updated;
saveSettings();
}
function removeDisplayProfile(compositor, profileId) {
if (!displayProfiles[compositor] || !displayProfiles[compositor][profileId])
return;
@@ -3197,26 +3125,6 @@ Singleton {
saveSettings();
}
function getActiveDisplayProfile(compositor) {
return activeDisplayProfile[compositor] || "";
}
function setActiveDisplayProfile(compositor, profileId) {
const updated = JSON.parse(JSON.stringify(activeDisplayProfile));
updated[compositor] = profileId;
activeDisplayProfile = updated;
saveSettings();
}
function setActiveDisplayProfileModes(compositor, modes) {
if (JSON.stringify(activeDisplayProfileModes[compositor] || {}) === JSON.stringify(modes || {}))
return;
const updated = JSON.parse(JSON.stringify(activeDisplayProfileModes));
updated[compositor] = modes;
activeDisplayProfileModes = updated;
saveSettings();
}
function setDisplayPreviousRefreshModes(compositor, modes) {
if (JSON.stringify(displayPreviousRefreshModes[compositor] || {}) === JSON.stringify(modes || {}))
return;
+1 -2
View File
@@ -367,8 +367,7 @@ Singleton {
function deferGreeterAutoLoginSyncToPill(details) {
toastCategoryDismissed("greeter-autologin-sync");
if (settingsRoot)
settingsRoot.set("greeterSyncPending", true);
SessionData.set("greeterSyncPending", true);
toastRequested(1, I18n.tr("Auto-login change needs a sync"), I18n.tr("Administrator access is required. Use the Sync button in Settings → Greeter to apply.") + (details ? "\n\n" + details : ""), "dms-greeter sync --autologin", "greeter-autologin-sync");
finishGreeterAutoLoginSync();
}
+11
View File
@@ -91,6 +91,17 @@ var SPEC = {
notepadLastMode: { def: "" },
niriOutputSettings: { def: {} },
hyprlandOutputSettings: { def: {} },
activeDisplayProfile: { def: {} },
activeDisplayProfileModes: { def: {} },
desktopWidgetGridSettings: { def: {} },
desktopWidgetInstancePositions: { def: {} },
builtInPluginState: { def: {} },
greeterSyncPending: { def: false },
greeterSyncBaseline: { def: {} },
lastAppliedIconTheme: { def: "" },
launcherLastMode: { def: "all" },
launcherLastFileSearchType: { def: "all" },
launcherLastQuery: { def: "" },
+11 -1
View File
@@ -1,6 +1,7 @@
.pragma library
.import "./SessionSpec.js" as SpecModule
.import "./SpecUtil.js" as Util
function parse(root, jsonObj) {
var SPEC = SpecModule.SPEC;
@@ -9,7 +10,7 @@ function parse(root, jsonObj) {
for (var k in SPEC) {
if (!(k in jsonObj)) {
root[k] = SPEC[k].def;
root[k] = Util.cloneDef(SPEC[k].def);
}
}
@@ -27,6 +28,7 @@ function toJson(root) {
var out = {};
for (var k in SPEC) {
if (SPEC[k].persist === false) continue;
if (Util.isDefault(root[k], SPEC[k].def)) continue;
out[k] = root[k];
}
out.configVersion = root.sessionConfigVersion;
@@ -73,5 +75,13 @@ function migrateToVersion(obj, targetVersion, settingsData) {
session.configVersion = 3;
}
if (currentVersion < 4) {
console.info("SessionData: Migrating session to version 4");
console.info("SessionData: Dropping keys that match defaults; session.json now stores only changed values");
Util.stripDefaults(session, SpecModule.SPEC);
session.configVersion = 4;
}
return session;
}
+3 -11
View File
@@ -1,5 +1,7 @@
.pragma library
.import "./SpecUtil.js" as Util
function percentToUnit(v) {
if (v === undefined || v === null) return undefined;
return v > 1 ? v / 100 : v;
@@ -270,16 +272,12 @@ var SPEC = {
greeterFontFamily: { def: "", onChange: "markGreeterSyncPending" },
greeterWallpaperFillMode: { def: "", onChange: "markGreeterSyncPending" },
greeterPamExternallyManaged: { def: false, onChange: "markGreeterSyncPending" },
greeterSyncPending: { def: false },
greeterSyncBaseline: { def: {} },
mediaSize: { def: 1 },
appLauncherViewMode: { def: "list" },
spotlightModalViewMode: { def: "list" },
browserPickerViewMode: { def: "grid" },
browserUsageHistory: { def: {} },
appPickerViewMode: { def: "grid" },
filePickerUsageHistory: { def: {} },
sortAppsAlphabetically: { def: false },
appLauncherGridColumns: { def: 4 },
spotlightCloseNiriOverview: { def: true },
@@ -312,7 +310,6 @@ var SPEC = {
iconThemeDark: { def: "System Default", onChange: "applyStoredIconTheme" },
iconThemeLight: { def: "System Default", onChange: "applyStoredIconTheme" },
iconThemePerMode: { def: false, onChange: "applyStoredIconTheme" },
lastAppliedIconTheme: { def: "" },
availableIconThemes: { def: ["System Default"], persist: false },
systemDefaultIconTheme: { def: "", persist: false },
@@ -558,11 +555,7 @@ var SPEC = {
displayNameMode: { def: "system" },
screenPreferences: { def: {} },
showOnLastDisplay: { def: {} },
niriOutputSettings: { def: {} },
hyprlandOutputSettings: { def: {} },
displayProfiles: { def: {} },
activeDisplayProfile: { def: {} },
activeDisplayProfileModes: { def: {} },
displayPreviousRefreshModes: { def: {} },
displayProfileAutoSelect: { def: false },
displayShowDisconnected: { def: false },
@@ -668,7 +661,6 @@ var SPEC = {
systemMonitorDisplayPreferences: { def: ["all"] },
systemMonitorVariants: { def: [] },
desktopWidgetPositions: { def: {} },
desktopWidgetGridSettings: { def: {} },
desktopWidgetInstances: { def: [] },
@@ -710,7 +702,7 @@ function getValidKeys() {
function set(root, key, value, saveFn, hooks) {
if (!(key in SPEC)) return;
if (value === undefined || value === null) value = SPEC[key].def;
if (value === undefined || value === null) value = Util.cloneDef(SPEC[key].def);
var oldValue = root[key];
root[key] = value;
var hookName = SPEC[key].onChange;
+102 -2
View File
@@ -1,9 +1,78 @@
.pragma library
.import "./SettingsSpec.js" as SpecModule
.import "./SpecUtil.js" as Util
var PIN_KEYS = ["brightnessDevicePins", "wifiNetworkPins", "bluetoothDevicePins", "audioInputDevicePins", "audioOutputDevicePins"];
var SESSION_MOVED_KEYS = ["niriOutputSettings", "hyprlandOutputSettings", "activeDisplayProfile", "activeDisplayProfileModes", "desktopWidgetGridSettings", "greeterSyncPending", "greeterSyncBaseline", "lastAppliedIconTheme"];
var CACHE_MOVED_KEYS = ["browserUsageHistory", "filePickerUsageHistory"];
var SESSION_BACKED_PLUGIN_IDS = ["dankNotepadModule"];
// Superseded by desktopWidgetInstances at v4; nothing has written them since
var STALE_WIDGET_KEYS = ["desktopClockEnabled", "desktopClockStyle", "desktopClockTransparency", "desktopClockColorMode", "desktopClockCustomColor", "desktopClockShowDate", "desktopClockShowAnalogNumbers", "desktopClockShowAnalogSeconds", "desktopClockX", "desktopClockY", "desktopClockWidth", "desktopClockHeight", "desktopClockDisplayPreferences", "systemMonitorEnabled", "systemMonitorShowHeader", "systemMonitorTransparency", "systemMonitorColorMode", "systemMonitorCustomColor", "systemMonitorShowCpu", "systemMonitorShowCpuGraph", "systemMonitorShowCpuTemp", "systemMonitorShowGpuTemp", "systemMonitorGpuPciId", "systemMonitorShowMemory", "systemMonitorShowMemoryGraph", "systemMonitorShowNetwork", "systemMonitorShowNetworkGraph", "systemMonitorShowDisk", "systemMonitorShowTopProcesses", "systemMonitorTopProcessCount", "systemMonitorTopProcessSortBy", "systemMonitorGraphInterval", "systemMonitorLayoutMode", "systemMonitorX", "systemMonitorY", "systemMonitorWidth", "systemMonitorHeight", "systemMonitorDisplayPreferences", "systemMonitorVariants", "desktopWidgetPositions"];
function withoutInstancePositions(instances) {
if (!Array.isArray(instances)) return instances;
return instances.map(function (inst) {
if (!inst || !inst.positions) return inst;
var copy = Object.assign({}, inst);
delete copy.positions;
return copy;
});
}
function withoutSessionBackedPluginState(pluginSettings) {
if (!pluginSettings) return pluginSettings;
var copy = Object.assign({}, pluginSettings);
for (var i = 0; i < SESSION_BACKED_PLUGIN_IDS.length; i++) {
delete copy[SESSION_BACKED_PLUGIN_IDS[i]];
}
return copy;
}
function extractSessionPayload(obj) {
if (!obj) return null;
var payload = {};
for (var i = 0; i < SESSION_MOVED_KEYS.length; i++) {
var key = SESSION_MOVED_KEYS[i];
if (key in obj) payload[key] = obj[key];
}
var positions = {};
var instances = Array.isArray(obj.desktopWidgetInstances) ? obj.desktopWidgetInstances : [];
for (var i = 0; i < instances.length; i++) {
var inst = instances[i];
if (inst && inst.id && inst.positions && Object.keys(inst.positions).length > 0) {
positions[inst.id] = inst.positions;
}
}
if (Object.keys(positions).length > 0) payload.desktopWidgetInstancePositions = positions;
var pluginState = {};
for (var i = 0; i < SESSION_BACKED_PLUGIN_IDS.length; i++) {
var id = SESSION_BACKED_PLUGIN_IDS[i];
if (obj.builtInPluginSettings && obj.builtInPluginSettings[id]) {
pluginState[id] = obj.builtInPluginSettings[id];
}
}
if (Object.keys(pluginState).length > 0) payload.builtInPluginState = pluginState;
return Object.keys(payload).length > 0 ? payload : null;
}
function extractCachePayload(obj) {
if (!obj) return null;
var payload = {};
for (var i = 0; i < CACHE_MOVED_KEYS.length; i++) {
var key = CACHE_MOVED_KEYS[i];
if (obj[key] && Object.keys(obj[key]).length > 0) payload[key] = obj[key];
}
return Object.keys(payload).length > 0 ? payload : null;
}
function extractPins(obj) {
if (!obj) return null;
@@ -28,7 +97,7 @@ function parse(root, jsonObj) {
// would wipe values set by detection processes on every reload.
if (SPEC[k].persist === false) continue;
if (!(k in jsonObj)) {
root[k] = SPEC[k].def;
root[k] = Util.cloneDef(SPEC[k].def);
}
}
@@ -48,7 +117,11 @@ function toJson(root) {
for (var k in SPEC) {
if (SPEC[k].persist === false) continue;
if (k === "pluginSettings") continue;
out[k] = root[k];
var value = root[k];
if (k === "desktopWidgetInstances") value = withoutInstancePositions(value);
if (k === "builtInPluginSettings") value = withoutSessionBackedPluginState(value);
if (Util.isDefault(value, SPEC[k].def)) continue;
out[k] = value;
}
out.configVersion = root.settingsConfigVersion;
return out;
@@ -289,5 +362,32 @@ function migrateToVersion(obj, targetVersion) {
settings.configVersion = 13;
}
if (currentVersion < 14) {
console.info("Migrating settings from version", currentVersion, "to version 14");
console.info("Dropping keys that match defaults; settings.json now stores only changed values");
Util.stripDefaults(settings, SpecModule.SPEC);
settings.configVersion = 14;
}
if (currentVersion < 15) {
console.info("Migrating settings from version", currentVersion, "to version 15");
console.info("Moving machine-specific state to session.json and usage histories to cache.json");
var movedKeys = SESSION_MOVED_KEYS.concat(CACHE_MOVED_KEYS, STALE_WIDGET_KEYS);
for (var i = 0; i < movedKeys.length; i++) {
delete settings[movedKeys[i]];
}
if (Array.isArray(settings.desktopWidgetInstances)) {
settings.desktopWidgetInstances = withoutInstancePositions(settings.desktopWidgetInstances);
}
if (settings.builtInPluginSettings) {
settings.builtInPluginSettings = withoutSessionBackedPluginState(settings.builtInPluginSettings);
}
settings.configVersion = 15;
}
return settings;
}
+30
View File
@@ -0,0 +1,30 @@
.pragma library
function stableStringify(value) {
if (value === null || typeof value !== "object")
return JSON.stringify(value);
if (Array.isArray(value))
return "[" + value.map(stableStringify).join(",") + "]";
return "{" + Object.keys(value).sort().map(function (k) {
return JSON.stringify(k) + ":" + stableStringify(value[k]);
}).join(",") + "}";
}
function isDefault(value, def) {
return stableStringify(value) === stableStringify(def);
}
function cloneDef(def) {
if (def === null || typeof def !== "object")
return def;
return JSON.parse(JSON.stringify(def));
}
function stripDefaults(obj, SPEC) {
for (var k in SPEC) {
if (!(k in obj))
continue;
if (isDefault(obj[k], SPEC[k].def))
delete obj[k];
}
}
+3 -3
View File
@@ -1897,7 +1897,7 @@ Item {
IpcHandler {
function listProfiles(): string {
const profiles = DisplayConfigState.validatedProfiles;
const activeId = SettingsData.getActiveDisplayProfile(CompositorService.compositor);
const activeId = SessionData.getActiveDisplayProfile(CompositorService.compositor);
const matchedId = DisplayConfigState.matchedProfile;
const lines = [];
@@ -1952,7 +1952,7 @@ Item {
if (ids.length === 0)
return "ERROR: No profiles configured";
const activeId = SettingsData.getActiveDisplayProfile(CompositorService.compositor);
const activeId = SessionData.getActiveDisplayProfile(CompositorService.compositor);
const idx = ids.indexOf(activeId);
const nextId = ids[(idx + 1) % ids.length];
DisplayConfigState.activateProfile(nextId);
@@ -1969,7 +1969,7 @@ Item {
function status(): string {
const auto = SettingsData.displayProfileAutoSelect ? "on" : "off";
const activeId = SettingsData.getActiveDisplayProfile(CompositorService.compositor);
const activeId = SessionData.getActiveDisplayProfile(CompositorService.compositor);
const matchedId = DisplayConfigState.matchedProfile;
const profiles = DisplayConfigState.validatedProfiles;
const activeName = profiles[activeId]?.name || "none";
+3 -3
View File
@@ -91,7 +91,7 @@ DankModal {
function updateApplicationList() {
applicationsModel.clear();
const apps = AppSearchService.applications;
const usageHistory = usageHistoryKey && SettingsData[usageHistoryKey] ? SettingsData[usageHistoryKey] : {};
const usageHistory = usageHistoryKey && CacheData[usageHistoryKey] ? CacheData[usageHistoryKey] : {};
const hasCategoryFilter = categoryFilter.length > 0;
const hasMime = mimeType.length > 0;
const hasMimeMatches = mimeMatchedAppIds.length > 0;
@@ -557,14 +557,14 @@ DankModal {
root.applicationSelected(app, root.targetData);
if (usageHistoryKey && app.appId) {
const usageHistory = SettingsData[usageHistoryKey] || {};
const usageHistory = CacheData[usageHistoryKey] || {};
const currentCount = usageHistory[app.appId] ? usageHistory[app.appId].count : 0;
usageHistory[app.appId] = {
count: currentCount + 1,
lastUsed: Date.now(),
name: app.name
};
SettingsData.set(usageHistoryKey, usageHistory);
CacheData.set(usageHistoryKey, usageHistory);
}
root.close();
@@ -21,7 +21,7 @@ PluginComponent {
return result;
}
readonly property bool autoMode: SettingsData.displayProfileAutoSelect
readonly property string activeProfileId: SettingsData.getActiveDisplayProfile(CompositorService.compositor)
readonly property string activeProfileId: SessionData.getActiveDisplayProfile(CompositorService.compositor)
readonly property var activeProfile: allProfiles[activeProfileId] || null
readonly property string activeProfileName: activeProfile?.name ?? ""
readonly property string displayProfileLabel: {
@@ -44,7 +44,7 @@ PluginComponent {
function setAutoMode(enabled) {
SettingsData.displayProfileAutoSelect = enabled;
if (!enabled)
SettingsData.setActiveDisplayProfile(CompositorService.compositor, "");
SessionData.setActiveDisplayProfile(CompositorService.compositor, "");
SettingsData.saveSettings();
if (enabled)
DisplayConfigState.applyAutoConfig();
@@ -108,6 +108,7 @@ Item {
}
readonly property string screenKey: SettingsData.getScreenDisplayName(screen)
readonly property string positionKey: syncPositionAcrossScreens ? "_synced" : screenKey
readonly property var storedPositions: SessionData.desktopWidgetInstancePositions[instanceId] ?? null
readonly property int screenWidth: screen?.width ?? 1920
readonly property int screenHeight: screen?.height ?? 1080
@@ -121,7 +122,7 @@ Item {
readonly property bool hasSavedPosition: {
if (isInstance)
return instanceData?.positions?.[positionKey]?.x !== undefined;
return storedPositions?.[positionKey]?.x !== undefined;
if (usePluginService)
return pluginService.loadPluginData(pluginId, "desktopX_" + positionKey, null) !== null;
return SettingsData.getDesktopWidgetPosition(pluginId, positionKey, "x", null) !== null;
@@ -129,7 +130,7 @@ Item {
readonly property bool hasSavedSize: {
if (isInstance)
return instanceData?.positions?.[positionKey]?.width !== undefined;
return storedPositions?.[positionKey]?.width !== undefined;
if (usePluginService)
return pluginService.loadPluginData(pluginId, "desktopWidth_" + positionKey, null) !== null;
return SettingsData.getDesktopWidgetPosition(pluginId, positionKey, "width", null) !== null;
@@ -137,7 +138,7 @@ Item {
property real savedX: {
if (isInstance) {
const val = instanceData?.positions?.[positionKey]?.x;
const val = storedPositions?.[positionKey]?.x;
if (val === undefined)
return screenWidth / 2 - savedWidth / 2;
return syncPositionAcrossScreens ? val * screenWidth : val;
@@ -155,7 +156,7 @@ Item {
}
property real savedY: {
if (isInstance) {
const val = instanceData?.positions?.[positionKey]?.y;
const val = storedPositions?.[positionKey]?.y;
if (val === undefined)
return screenHeight / 2 - savedHeight / 2;
return syncPositionAcrossScreens ? val * screenHeight : val;
@@ -173,7 +174,7 @@ Item {
}
property real savedWidth: {
if (isInstance) {
const val = instanceData?.positions?.[positionKey]?.width;
const val = storedPositions?.[positionKey]?.width;
if (val === undefined)
return 280;
return val;
@@ -191,7 +192,7 @@ Item {
}
property real savedHeight: {
if (isInstance) {
const val = instanceData?.positions?.[positionKey]?.height;
const val = storedPositions?.[positionKey]?.height;
if (val === undefined)
return forceSquare ? savedWidth : 180;
return forceSquare ? savedWidth : val;
@@ -236,14 +237,14 @@ Item {
property bool acceptsKeyboardFocus: contentLoader.item?.acceptsKeyboardFocus ?? false
property bool isInteracting: dragArea.pressed || resizeArea.pressed
property var _gridSettingsTrigger: SettingsData.desktopWidgetGridSettings
property var _gridSettingsTrigger: SessionData.desktopWidgetGridSettings
readonly property int gridSize: {
void _gridSettingsTrigger;
return SettingsData.getDesktopWidgetGridSetting(screenKey, "size", 40);
return SessionData.getDesktopWidgetGridSetting(screenKey, "size", 40);
}
readonly property bool gridEnabled: {
void _gridSettingsTrigger;
return SettingsData.getDesktopWidgetGridSetting(screenKey, "enabled", false);
return SessionData.getDesktopWidgetGridSetting(screenKey, "enabled", false);
}
function snapToGrid(value) {
@@ -254,7 +255,7 @@ Item {
const xVal = syncPositionAcrossScreens ? finalX / screenWidth : finalX;
const yVal = syncPositionAcrossScreens ? finalY / screenHeight : finalY;
if (isInstance && instanceData) {
SettingsData.updateDesktopWidgetInstancePosition(instanceId, positionKey, {
SessionData.updateDesktopWidgetInstancePosition(instanceId, positionKey, {
x: xVal,
y: yVal
});
@@ -275,7 +276,7 @@ Item {
const sizeVal = forceSquare ? Math.max(finalW, finalH) : finalW;
const heightVal = forceSquare ? sizeVal : finalH;
if (isInstance && instanceData) {
SettingsData.updateDesktopWidgetInstancePosition(instanceId, positionKey, {
SessionData.updateDesktopWidgetInstancePosition(instanceId, positionKey, {
width: sizeVal,
height: heightVal
});
@@ -342,15 +343,15 @@ Item {
return;
switch (event.key) {
case Qt.Key_G:
SettingsData.setDesktopWidgetGridSetting(root.screenKey, "enabled", !root.gridEnabled);
SessionData.setDesktopWidgetGridSetting(root.screenKey, "enabled", !root.gridEnabled);
event.accepted = true;
break;
case Qt.Key_Z:
SettingsData.setDesktopWidgetGridSetting(root.screenKey, "size", Math.max(10, root.gridSize - 10));
SessionData.setDesktopWidgetGridSetting(root.screenKey, "size", Math.max(10, root.gridSize - 10));
event.accepted = true;
break;
case Qt.Key_X:
SettingsData.setDesktopWidgetGridSetting(root.screenKey, "size", Math.min(200, root.gridSize + 10));
SessionData.setDesktopWidgetGridSetting(root.screenKey, "size", Math.min(200, root.gridSize + 10));
event.accepted = true;
break;
}
@@ -362,7 +362,7 @@ SettingsCard {
if (!root.instanceId)
return;
if (isChecked)
SettingsData.syncDesktopWidgetPositionToAllScreens(root.instanceId);
SessionData.syncDesktopWidgetPositionToAllScreens(root.instanceId);
SettingsData.updateDesktopWidgetInstanceConfig(root.instanceId, {
syncPositionAcrossScreens: isChecked
});
@@ -234,8 +234,8 @@ Singleton {
default:
parsed = {};
}
const niriSettings = SettingsData.niriOutputSettings || {};
const hyprSettings = SettingsData.hyprlandOutputSettings || {};
const niriSettings = SessionData.niriOutputSettings || {};
const hyprSettings = SessionData.hyprlandOutputSettings || {};
const profileOutputs = {};
for (const outputName in parsed) {
const od = parsed[outputName];
@@ -267,7 +267,7 @@ Singleton {
function publishActiveProfileModes() {
const compositor = CompositorService.compositor;
const profileId = SettingsData.getActiveDisplayProfile(compositor);
const profileId = SessionData.getActiveDisplayProfile(compositor);
const profile = profileId ? validatedProfiles[profileId] : null;
const outputs = profile?.outputs || {};
const modes = {};
@@ -280,7 +280,7 @@ Singleton {
};
}
SettingsData.setActiveDisplayProfileModes(compositor, modes);
SessionData.setActiveDisplayProfileModes(compositor, modes);
}
function generateProfileId() {
@@ -561,7 +561,7 @@ Singleton {
}
};
const onWriteSuccess = () => {
SettingsData.setActiveDisplayProfile(CompositorService.compositor, configId);
SessionData.setActiveDisplayProfile(CompositorService.compositor, configId);
publishActiveProfileModes();
if (isManual) {
profilesLoading = false;
@@ -651,7 +651,7 @@ Singleton {
validatedProfiles = updated;
currentOutputSet = buildCurrentOutputSet();
matchedProfile = findMatchingProfile();
SettingsData.setActiveDisplayProfile(CompositorService.compositor, id);
SessionData.setActiveDisplayProfile(CompositorService.compositor, id);
publishActiveProfileModes();
profileSaved(id, profileName);
});
@@ -680,7 +680,7 @@ Singleton {
function deleteProfile(profileId) {
const compositor = CompositorService.compositor;
const isActive = SettingsData.getActiveDisplayProfile(compositor) === profileId;
const isActive = SessionData.getActiveDisplayProfile(compositor) === profileId;
profilesLoading = true;
readMonitorsJson(data => {
@@ -691,7 +691,7 @@ Singleton {
profilesLoading = false;
SettingsData.removeDisplayProfile(compositor, profileId);
if (isActive) {
SettingsData.setActiveDisplayProfile(compositor, "");
SessionData.setActiveDisplayProfile(compositor, "");
backendWriteOutputsConfig(allOutputs);
}
const updated = JSON.parse(JSON.stringify(validatedProfiles));
@@ -774,7 +774,7 @@ Singleton {
const match = findConfigEntryByFingerprint(data, currentOutputSet, false);
if (match) {
if (configEntryMatchesLiveLayout(match.entry)) {
SettingsData.setActiveDisplayProfile(CompositorService.compositor, match.entry.id);
SessionData.setActiveDisplayProfile(CompositorService.compositor, match.entry.id);
return;
}
applyConfigEntry(match.entry, match.entry.id, "", false);
@@ -968,7 +968,7 @@ Singleton {
}
function initHyprlandSettingsFromConfig(parsedOutputs) {
const current = JSON.parse(JSON.stringify(SettingsData.hyprlandOutputSettings));
const current = JSON.parse(JSON.stringify(SessionData.hyprlandOutputSettings));
let changed = false;
for (const outputName in parsedOutputs) {
@@ -997,13 +997,13 @@ Singleton {
}
if (changed) {
SettingsData.hyprlandOutputSettings = current;
SettingsData.saveSettings();
SessionData.hyprlandOutputSettings = current;
SessionData.saveSettings();
}
}
function syncHyprlandVrrFromConfig(parsedOutputs) {
const current = JSON.parse(JSON.stringify(SettingsData.hyprlandOutputSettings));
const current = JSON.parse(JSON.stringify(SessionData.hyprlandOutputSettings));
let changed = false;
for (const outputName in parsedOutputs) {
const settings = parsedOutputs[outputName]?.hyprlandSettings;
@@ -1020,28 +1020,24 @@ Singleton {
changed = true;
}
if (changed) {
SettingsData.hyprlandOutputSettings = current;
SettingsData.saveSettings();
SessionData.hyprlandOutputSettings = current;
SessionData.saveSettings();
}
}
function syncNiriVrrFromConfig(parsedOutputs) {
let changed = false;
for (const outputName in parsedOutputs) {
const output = parsedOutputs[outputName];
const current = SettingsData.getNiriOutputSetting(outputName, "vrrOnDemand", false);
const current = SessionData.getNiriOutputSetting(outputName, "vrrOnDemand", false);
const fromConfig = output.vrr_on_demand ?? false;
if (current === fromConfig)
continue;
SettingsData.setNiriOutputSetting(outputName, "vrrOnDemand", fromConfig || undefined);
changed = true;
SessionData.setNiriOutputSetting(outputName, "vrrOnDemand", fromConfig || undefined);
}
if (changed)
SettingsData.saveSettings();
}
function syncHyprlandDisabledFromConfig(parsedOutputs) {
const current = JSON.parse(JSON.stringify(SettingsData.hyprlandOutputSettings));
const current = JSON.parse(JSON.stringify(SessionData.hyprlandOutputSettings));
let changed = false;
for (const outputName in parsedOutputs) {
const settings = parsedOutputs[outputName]?.hyprlandSettings;
@@ -1058,24 +1054,20 @@ Singleton {
changed = true;
}
if (changed) {
SettingsData.hyprlandOutputSettings = current;
SettingsData.saveSettings();
SessionData.hyprlandOutputSettings = current;
SessionData.saveSettings();
}
}
function syncNiriDisabledFromConfig(parsedOutputs) {
let changed = false;
for (const outputName in parsedOutputs) {
const output = parsedOutputs[outputName];
const fromConfig = output.disabled ?? false;
const current = SettingsData.getNiriOutputSetting(outputName, "disabled", false);
const current = SessionData.getNiriOutputSetting(outputName, "disabled", false);
if (current === fromConfig)
continue;
SettingsData.setNiriOutputSetting(outputName, "disabled", fromConfig || undefined);
changed = true;
SessionData.setNiriOutputSetting(outputName, "disabled", fromConfig || undefined);
}
if (changed)
SettingsData.saveSettings();
}
function filterDisconnectedOnly(parsedOutputs) {
@@ -1965,7 +1957,7 @@ Singleton {
const pending = pendingNiriChanges[identifier];
if (pending && pending[key] !== undefined)
return pending[key];
return SettingsData.getNiriOutputSetting(identifier, key, defaultValue);
return SessionData.getNiriOutputSetting(identifier, key, defaultValue);
}
function setNiriSetting(output, outputName, key, value) {
@@ -1983,7 +1975,7 @@ Singleton {
function initOriginalNiriSettings() {
if (originalNiriSettings)
return;
originalNiriSettings = JSON.parse(JSON.stringify(SettingsData.niriOutputSettings));
originalNiriSettings = JSON.parse(JSON.stringify(SessionData.niriOutputSettings));
}
function getHyprlandOutputIdentifier(output, outputName) {
@@ -2001,7 +1993,7 @@ Singleton {
const val = pending[key];
return (val !== null && val !== undefined) ? val : defaultValue;
}
return SettingsData.getHyprlandOutputSetting(identifier, key, defaultValue);
return SessionData.getHyprlandOutputSetting(identifier, key, defaultValue);
}
function setHyprlandSetting(output, outputName, key, value) {
@@ -2019,7 +2011,7 @@ Singleton {
function initOriginalHyprlandSettings() {
if (originalHyprlandSettings)
return;
originalHyprlandSettings = JSON.parse(JSON.stringify(SettingsData.hyprlandOutputSettings));
originalHyprlandSettings = JSON.parse(JSON.stringify(SessionData.hyprlandOutputSettings));
}
function initOriginalOutputs() {
@@ -2259,7 +2251,7 @@ Singleton {
}
function buildMergedNiriSettings() {
const merged = JSON.parse(JSON.stringify(SettingsData.niriOutputSettings));
const merged = JSON.parse(JSON.stringify(SessionData.niriOutputSettings));
for (const outputId in pendingNiriChanges) {
if (!merged[outputId])
merged[outputId] = {};
@@ -2278,20 +2270,20 @@ Singleton {
function commitNiriSettingsChanges() {
for (const outputId in pendingNiriChanges) {
for (const key in pendingNiriChanges[outputId]) {
SettingsData.setNiriOutputSetting(outputId, key, pendingNiriChanges[outputId][key]);
SessionData.setNiriOutputSetting(outputId, key, pendingNiriChanges[outputId][key]);
}
}
// Clear stale disabled from SettingsData so NiriService reads clean state
if (Object.keys(outputs).length <= 1) {
for (const id in SettingsData.niriOutputSettings) {
if (SettingsData.niriOutputSettings[id]?.disabled)
SettingsData.setNiriOutputSetting(id, "disabled", null);
for (const id in SessionData.niriOutputSettings) {
if (SessionData.niriOutputSettings[id]?.disabled)
SessionData.setNiriOutputSetting(id, "disabled", null);
}
}
}
function buildMergedHyprlandSettings() {
const merged = JSON.parse(JSON.stringify(SettingsData.hyprlandOutputSettings));
const merged = JSON.parse(JSON.stringify(SessionData.hyprlandOutputSettings));
for (const outputId in pendingHyprlandChanges) {
if (!merged[outputId])
merged[outputId] = {};
@@ -2316,16 +2308,16 @@ Singleton {
for (const key in pendingHyprlandChanges[outputId]) {
const val = pendingHyprlandChanges[outputId][key];
if (val === null || val === undefined)
SettingsData.removeHyprlandOutputSetting(outputId, key);
SessionData.removeHyprlandOutputSetting(outputId, key);
else
SettingsData.setHyprlandOutputSetting(outputId, key, val);
SessionData.setHyprlandOutputSetting(outputId, key, val);
}
}
// Clear stale disabled from SettingsData so HyprlandService reads clean state
if (Object.keys(outputs).length <= 1) {
for (const id in SettingsData.hyprlandOutputSettings) {
if (SettingsData.hyprlandOutputSettings[id]?.disabled)
SettingsData.removeHyprlandOutputSetting(id, "disabled");
for (const id in SessionData.hyprlandOutputSettings) {
if (SessionData.hyprlandOutputSettings[id]?.disabled)
SessionData.removeHyprlandOutputSetting(id, "disabled");
}
}
}
@@ -2372,13 +2364,13 @@ Singleton {
}
if (hadNiriChanges) {
SettingsData.niriOutputSettings = JSON.parse(JSON.stringify(originalNiriSettings));
SettingsData.saveSettings();
SessionData.niriOutputSettings = JSON.parse(JSON.stringify(originalNiriSettings));
SessionData.saveSettings();
}
if (hadHyprlandChanges) {
SettingsData.hyprlandOutputSettings = JSON.parse(JSON.stringify(originalHyprlandSettings));
SettingsData.saveSettings();
SessionData.hyprlandOutputSettings = JSON.parse(JSON.stringify(originalHyprlandSettings));
SessionData.saveSettings();
}
pendingHyprlandChanges = {};
@@ -12,7 +12,7 @@ Item {
LayoutMirroring.childrenInherit: true
property string selectedProfileId: {
const id = SettingsData.activeDisplayProfile[CompositorService.compositor] || "";
const id = SessionData.activeDisplayProfile[CompositorService.compositor] || "";
if (!SettingsData.displayProfileAutoSelect) {
const profile = DisplayConfigState.validatedProfiles[id];
if (profile && profile.name === "")
@@ -169,7 +169,7 @@ Item {
onToggled: checked => {
SettingsData.displayProfileAutoSelect = checked;
if (!checked)
SettingsData.setActiveDisplayProfile(CompositorService.compositor, "");
SessionData.setActiveDisplayProfile(CompositorService.compositor, "");
SettingsData.saveSettings();
if (checked)
DisplayConfigState.applyAutoConfig();
+1 -1
View File
@@ -714,7 +714,7 @@ Item {
Rectangle {
id: syncPendingPill
readonly property bool shown: SettingsData.greeterSyncPending && root.greeterInstalled
readonly property bool shown: SessionData.greeterSyncPending && root.greeterInstalled
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
+2 -2
View File
@@ -377,7 +377,7 @@ Singleton {
function applyWlrModeOverrides(modeOverrides, callback) {
if (CompositorService.isHyprland) {
HyprlandService.generateOutputsConfig(buildWlrOutputsData(modeOverrides), SettingsData.hyprlandOutputSettings, callback);
HyprlandService.generateOutputsConfig(buildWlrOutputsData(modeOverrides), SessionData.hyprlandOutputSettings, callback);
return;
}
@@ -519,7 +519,7 @@ Singleton {
function findActiveProfileMode(outputName, output) {
const compositor = CompositorService.compositor;
const profileModes = SettingsData.activeDisplayProfileModes?.[compositor] || {};
const profileModes = SessionData.activeDisplayProfileModes?.[compositor] || {};
if (Object.keys(profileModes).length === 0)
return "";
+1 -1
View File
@@ -180,7 +180,7 @@ Singleton {
return;
}
const settings = hyprlandSettings || SettingsData.hyprlandOutputSettings;
const settings = hyprlandSettings || SessionData.hyprlandOutputSettings;
let lines = ["-- Auto-generated by DMS — do not edit manually", ""];
for (const outputName in outputsData) {
+1 -1
View File
@@ -1446,7 +1446,7 @@ window-rule {
const identifier = getOutputIdentifier(output, outputName);
if (niriSettings)
return niriSettings[identifier] || niriSettings[outputName] || {};
return SettingsData.getNiriOutputSettings(identifier);
return SessionData.getNiriOutputSettings(identifier);
}
function transformToNiri(transform) {