pragma Singleton pragma ComponentBehavior: Bound import "root:/modules/common" import "root:/modules/common/functions/string_utils.js" as StringUtils import Quickshell; import Quickshell.Io; import Qt.labs.platform import QtQuick; /** * For storing sensitive data in the keyring. * Use this for small data only, since it stores a JSON of the contents directly and doesn't use a database. */ Singleton { id: root property bool loaded: false property var keyringData: ({}) property var properties: { "application": "illogical-impulse", "explanation": qsTr("For storing API keys and other sensitive information"), } property var propertiesAsArgs: Object.keys(root.properties).reduce( function(arr, key) { return arr.concat([key, root.properties[key]]); }, [] ) property string keyringLabel: StringUtils.format(qsTr("{0} Safe Storage"), "illogical-impulse") function setNestedField(path, value) { if (!root.keyringData) root.keyringData = {}; let keys = path; let obj = root.keyringData; let parents = [obj]; // Traverse and collect parent objects for (let i = 0; i < keys.length - 1; ++i) { if (!obj[keys[i]] || typeof obj[keys[i]] !== "object") { obj[keys[i]] = {}; } obj = obj[keys[i]]; parents.push(obj); } // Set the value at the innermost key obj[keys[keys.length - 1]] = value; // Reassign each parent object from the bottom up to trigger change notifications for (let i = keys.length - 2; i >= 0; --i) { let parent = parents[i]; let key = keys[i]; // Shallow clone to change object identity (spread replaced with Object.assign) parent[key] = Object.assign({}, parent[key]); } // Finally, reassign root.keyringData to trigger top-level change root.keyringData = Object.assign({}, root.keyringData); saveKeyringData(); } function fetchKeyringData() { // console.log("[KeyringStorage] Fetching keyring data..."); // console.log("[KeyringStorage] getData command:'" + getData.command.join("' '") + "'"); getData.running = true; } function saveKeyringData() { saveData.stdinEnabled = true; saveData.running = true; } Process { id: saveData command: [ "secret-tool", "store", "--label=" + keyringLabel, ...propertiesAsArgs, ] onRunningChanged: { if (saveData.running) { // console.log("[KeyringStorage] Saving with command: '" + saveData.command.join("' '") + "'"); saveData.write(JSON.stringify(root.keyringData)); stdinEnabled = false // End input stream } } } Process { id: getData command: [ // We need to use echo for a newline so splitparser does parse "bash", "-c", `echo $(secret-tool lookup 'application' 'illogical-impulse')`, ] stdout: SplitParser { onRead: data => { if(data.length === 0) return; try { root.keyringData = JSON.parse(data); // console.log("[KeyringStorage] Keyring data fetched:", JSON.stringify(root.keyringData)); } catch (e) { console.error("[KeyringStorage] Failed to get keyring data, reinitializing."); root.keyringData = {}; saveKeyringData() } } } onExited: (exitCode, exitStatus) => { // console.log("[KeyringStorage] Keyring data fetch process exited with code:", exitCode); if (exitCode !== 0) { console.error("[KeyringStorage] Failed to get keyring data, reinitializing."); root.keyringData = {}; saveKeyringData() } root.loaded = true; } } }