1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
pragma Singleton
import Quickshell
import Quickshell.Io
import QtQuick
import "island/Controls.js" as Controls
// Preferences that must survive a reboot, in one JSON file under the state
// home. Only the shell writes it, which is why it is not in ~/.config.
//
// Nothing is written until the user changes something. A file that fails to
// parse reads as the defaults and stays on disk as it is, so a hand edit
// with a typo is not replaced by a panel that was merely opened.
Singleton {
id: root
readonly property string path:
(Quickshell.env("XDG_STATE_HOME") || Quickshell.env("HOME") + "/.local/state")
+ "/quickshell/desktop.json"
// Visible control tiles, in display order.
property var controls: Controls.defaults()
// The public IP the user pinned from the performance panel; "" for none.
property string pinnedIp: ""
// The whole parsed object, so a write keeps keys this version does not
// know about.
property var doc: ({})
function setControls(list) {
root.controls = Controls.normalize(list);
root.write(Object.assign({}, root.doc, { controls: root.controls }));
}
function setPinnedIp(ip) {
if (ip === root.pinnedIp) return;
root.pinnedIp = ip;
const perf = Object.assign({}, root.doc.perf, { pinnedIp: ip });
root.write(Object.assign({}, root.doc, { perf: perf }));
}
function write(out) {
root.doc = out;
file.setText(JSON.stringify(out, null, 2) + "\n");
}
function reparse() {
let parsed = {};
try {
const v = JSON.parse(file.text());
if (v && typeof v === "object" && !Array.isArray(v)) parsed = v;
} catch (e) {}
root.doc = parsed;
const next = "controls" in parsed ? Controls.normalize(parsed.controls) : Controls.defaults();
// The watch also fires on our own write; compare so it is a no-op.
if (JSON.stringify(next) !== JSON.stringify(root.controls)) root.controls = next;
const pin = parsed.perf && typeof parsed.perf.pinnedIp === "string" ? parsed.perf.pinnedIp : "";
if (pin !== root.pinnedIp) root.pinnedIp = pin;
}
FileView {
id: file
path: root.path
atomicWrites: true
watchChanges: true
printErrors: false
// printErrors is off for the missing-file read; a failed write still
// has to be heard, or the new order silently vanishes at restart.
onSaveFailed: error => console.warn("Prefs: writing", root.path, "failed:", error)
onFileChanged: reload()
onLoaded: root.reparse()
// Missing file: the defaults, and nothing written.
onLoadFailed: { root.doc = {}; root.controls = Controls.defaults(); root.pinnedIp = ""; }
}
}
|