The Dock's pinned applications were a sixteen-entry literal in Settings.qml, so changing what sits in the Dock meant editing QML. They are now an ordered list in the shared store, with move up, move down, unpin, and a filtered picker for adding installed applications. Keeping them in the shared store rather than a file of their own means they are covered by Restore defaults like everything else. This needed a "json" schema type for values the schema stores and resets but does not validate field by field. It exists so structured settings can live in the one file rather than growing a fourth preference store; the owning service validates the contents. Snapshots make the settings app safe to experiment with. The whole configuration is one file, so a backup is a copy and a restore is an overwrite, and restoring snapshots what it replaces so it is itself undoable. A snapshot is validated as JSON before it can be restored over a working configuration, and a name that is not a plain snapshot filename from the backup directory is refused. Snapshot names carry milliseconds. At one-second resolution a save followed promptly by a restore produced the same filename twice, and the restore's own safety snapshot overwrote the file it was about to read -- found by the contract, which restores immediately after saving. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
91 lines
2.8 KiB
QML
91 lines
2.8 KiB
QML
pragma Singleton
|
|
|
|
// Snapshots of the settings store.
|
|
//
|
|
// The whole desktop configuration is one JSON file, so a backup is a copy and a
|
|
// restore is an overwrite. Worth exposing now that the settings app changes
|
|
// real things -- compositor geometry, idle timeouts, the dock -- because being
|
|
// able to return to a known-good state is what makes experimenting feel safe.
|
|
//
|
|
// Restoring rewrites the file underneath the running shell, so the store is
|
|
// told to re-read afterwards rather than waiting for the next change.
|
|
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import QtQuick
|
|
import qs.config
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
readonly property string helperPath: Quickshell.shellDir + "/scripts/panama-settings-backup"
|
|
|
|
property var snapshots: []
|
|
property string lastError: ""
|
|
property string lastAction: ""
|
|
|
|
readonly property bool busy: listQuery.running || actionRun.running
|
|
|
|
Process {
|
|
id: listQuery
|
|
command: [root.helperPath, "list"]
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
try {
|
|
const parsed = JSON.parse(this.text);
|
|
root.snapshots = Array.isArray(parsed) ? parsed : [];
|
|
root.lastError = "";
|
|
} catch (error) {
|
|
root.lastError = "Could not read the list of snapshots.";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: actionRun
|
|
property bool restoring: false
|
|
onExited: (exitCode, exitStatus) => {
|
|
if (exitCode !== 0) {
|
|
root.lastError = actionRun.restoring
|
|
? "That snapshot could not be restored."
|
|
: "The settings could not be backed up.";
|
|
return;
|
|
}
|
|
root.lastError = "";
|
|
root.lastAction = actionRun.restoring ? "restored" : "saved";
|
|
if (actionRun.restoring)
|
|
DesktopPreferences.reload();
|
|
root.refresh();
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: root.refresh()
|
|
|
|
function refresh(): void {
|
|
if (!listQuery.running)
|
|
listQuery.running = true;
|
|
}
|
|
|
|
function save(): void {
|
|
if (actionRun.running)
|
|
return;
|
|
actionRun.restoring = false;
|
|
actionRun.exec([root.helperPath, "save"]);
|
|
}
|
|
|
|
// The name is matched against the snapshot list rather than trusted, so no
|
|
// caller-supplied path reaches the helper even though it validates as well.
|
|
function restore(name: string): bool {
|
|
if (actionRun.running)
|
|
return false;
|
|
if (!root.snapshots.some(snapshot => snapshot.name === name)) {
|
|
root.lastError = "That snapshot is not in the list.";
|
|
return false;
|
|
}
|
|
actionRun.restoring = true;
|
|
actionRun.exec([root.helperPath, "restore", name]);
|
|
return true;
|
|
}
|
|
}
|