pragma Singleton // Online accounts, via GNOME Online Accounts. // // The daemon already runs in this session -- gvfs activates it, and accounts // work without gnome-shell anywhere. Only the panel was GNOME's; the accounts // are D-Bus objects anything may read and modify. So listing, per-service // toggles, and removal all happen here, natively. // // Signing in is the exception, and only for OAuth providers. The daemon's // AddAccount takes credentials as an argument rather than obtaining them, and // the code that runs Google's OAuth exchange lives in libgoa-backend, which // Fedora ships without a GIR binding. So that one step is handed to GNOME's // panel and the user comes straight back here. // // Read on demand and after every change: accounts are added and removed by // people, not by the system, so there is nothing to poll for. import Quickshell import Quickshell.Io import QtQuick Singleton { id: root readonly property string helperPath: Quickshell.shellDir + "/scripts/panama-accounts" // [{ path, provider, providerName, identity, needsAttention, services: [{key,label,enabled}] }] property var accounts: [] property bool scanned: false property bool busy: false property string lastError: "" // Accounts whose stored credentials have stopped working -- an expired // token, a changed password. GOA knows, and nothing outside its own panel // ever says so, which is how an account quietly stops syncing for weeks. readonly property int attentionCount: root.accounts.filter(a => a.needsAttention).length readonly property bool available: root.lastError === "" function refresh(): void { if (!list.running) list.running = true; } // Enabling a service clears GOA's "disabled" flag; the helper owns that // inversion so the UI can speak in terms of what is on. function setService(path: string, service: string, enabled: bool): void { if (root.busy) return; root.busy = true; write.command = [root.helperPath, "set", path, service, enabled ? "true" : "false"]; write.running = true; } function remove(path: string): void { if (root.busy) return; root.busy = true; write.command = [root.helperPath, "remove", path]; write.running = true; } Process { id: list command: [root.helperPath, "list"] stdout: StdioCollector { onStreamFinished: { try { const parsed = JSON.parse(this.text); root.accounts = Array.isArray(parsed.accounts) ? parsed.accounts : []; root.lastError = String(parsed.error ?? ""); } catch (error) { root.accounts = []; root.lastError = "Could not read the accounts helper's output."; console.warn("OnlineAccounts: could not parse helper output:", error); } root.scanned = true; } } } Process { id: write stderr: StdioCollector { onStreamFinished: if (this.text.trim() !== "") root.lastError = this.text.trim() } // Re-read rather than assuming the write landed: GOA may refuse, and a // toggle that sprang back is the honest outcome. onExited: { root.busy = false; root.refresh(); } } }