diff --git a/config/dot/quickshell/config/DesktopPreferences.qml b/config/dot/quickshell/config/DesktopPreferences.qml index fba6f16..a42f17d 100644 --- a/config/dot/quickshell/config/DesktopPreferences.qml +++ b/config/dot/quickshell/config/DesktopPreferences.qml @@ -130,6 +130,14 @@ Singleton { printErrors: false atomicWrites: true + // Adopt writes made from outside the shell -- a hand edit, a script, + // a restored snapshot -- instead of holding a stale copy in memory + // and silently erasing them at the next save. A change made anywhere + // must survive everywhere; the shell is the editor, not the owner. + // The shell's own atomic writes land here too and reload as a no-op. + watchChanges: true + onFileChanged: this.reload() + onLoaded: root.load() // No file yet is the normal first-run case, not an error. onLoadFailed: root.load() @@ -145,7 +153,27 @@ Singleton { Timer { id: persistTimer interval: 0 - onTriggered: preferencesFile.setText(JSON.stringify(root.values, null, 2) + "\n") + // Merge with what is on disk rather than overwriting it. This model + // was loaded at startup; a key written to the file since then -- a + // hand edit, a script, another shell instance during a session + // handoff -- would otherwise be erased by the next unrelated save, + // which is how a setting "changed itself back". Keys this shell has + // set win; keys it has never seen survive. + onTriggered: { + let disk = {}; + try { + const text = preferencesFile.text(); + if (text && text.trim().length > 0) + disk = JSON.parse(text); + } catch (error) { + // An unreadable file loses the merge, never the write. + } + if (!disk || typeof disk !== "object") + disk = {}; + const merged = Object.assign({}, disk, root.values); + root.values = merged; + preferencesFile.setText(JSON.stringify(merged, null, 2) + "\n"); + } } // One-time move from the pre-Stage-1 location inside Quickshell's state