aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/Prefs.qml
blob: 1cc83cd64080ee75514cdd8de60761e8be9999bd (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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// 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

// 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"

    // The public IP the user pinned from the performance panel; "" for none.
    property string pinnedIp: ""

    // The assistant's Ask presets as saved, or null for the defaults
    // (assistant/Presets.js, which also validates what is read here).
    property var presets: null

    // The settings window's sidebar width, dragged by its edge.
    readonly property int sidebarMin: 160
    readonly property int sidebarMax: 420
    property int settingsSidebar: 200

    // The whole parsed object, so a write keeps keys this version does not
    // know about.
    property var doc: ({})

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

    // For the settings page that will edit the presets.
    function setPresets(list) {
        root.presets = list;
        const a = Object.assign({}, root.doc.assistant, { presets: list });
        root.write(Object.assign({}, root.doc, { assistant: a }));
    }
    function resetPresets() {
        root.presets = null;
        const a = Object.assign({}, root.doc.assistant);
        delete a.presets;
        root.write(Object.assign({}, root.doc, { assistant: a }));
    }

    function setSettingsSidebar(w) {
        w = Math.round(Math.max(root.sidebarMin, Math.min(root.sidebarMax, w)));
        if (w === root.settingsSidebar) return;
        root.settingsSidebar = w;
        const s = Object.assign({}, root.doc.settings, { sidebar: w });
        root.write(Object.assign({}, root.doc, { settings: s }));
    }

    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 pin = parsed.perf && typeof parsed.perf.pinnedIp === "string" ? parsed.perf.pinnedIp : "";
        if (pin !== root.pinnedIp) root.pinnedIp = pin;
        root.presets = parsed.assistant && parsed.assistant.presets !== undefined
            ? parsed.assistant.presets : null;
        const w = parsed.settings ? parsed.settings.sidebar : undefined;
        root.settingsSidebar = typeof w === "number" && w >= root.sidebarMin && w <= root.sidebarMax
            ? Math.round(w) : 200;
    }

    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 pin 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.pinnedIp = ""; root.presets = null; root.settingsSidebar = 200; }
    }
}