// Copyright (C) 2026 Danilo M. // // 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 router model preferred for the assistant, "" for none. The daemon // still uses whatever is loaded; the settings page loads this on demand. property string assistantModel: "" function setAssistantModel(id) { if (id === root.assistantModel) return; root.assistantModel = id; const a = Object.assign({}, root.doc.assistant, { model: id }); root.write(Object.assign({}, root.doc, { assistant: a })); } // 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 window's Ask presets page. 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; root.assistantModel = parsed.assistant && typeof parsed.assistant.model === "string" ? parsed.assistant.model : ""; 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; root.assistantModel = ""; } } }