aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/Prefs.qml
blob: d56b34c145b74c05c4c5967ac22a57ddf6505940 (plain)
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
// 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 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);
        const out = Object.assign({}, root.doc, { controls: root.controls });
        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;
    }

    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(); }
    }
}