Files
Panama/config/dot/quickshell/services/OnlineAccounts.qml
T
Gabriel Brown 495fb9b41b Give each online account its provider's icon
Four accounts distinguished only by a line of small grey text are four rows that
have to be read rather than recognised. GOA already knows what each one is.

It hands back a serialised GThemedIcon -- ". GThemedIcon goa-account-google
goa-account goa …" -- a preference-ordered fallback chain. The helper passes the
whole chain on rather than resolving it, because which of those names exists is
a property of the icon theme in use and not something a python script talking to
D-Bus should be deciding.

The page walks the chain and takes the first name the active theme actually has.
Both simpler readings were wrong and looked right: taking the first name blindly
assumes it resolves, and taking the last as a fallback assumes the most generic
name is the most likely to exist. On Adwaita the tails of these very chains --
"mail", "goa-symbolic" -- do not exist at all, so a miss would have drawn
nothing. Checked by asking Quickshell.iconPath directly, which returns empty for
a name the theme lacks; the fallback is avatar-default-symbolic, which is
present.

SettingsCard grew an optional icon for this. It is empty by default and the
header lays out exactly as before when unset, so no other card moves.

Last-sync is not here because there is nothing to show: GOA exposes no
sync-related property at all, on any of these accounts. Better to say so than to
invent a timestamp from when the page last refreshed.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-20 13:28:05 -04:00

97 lines
3.4 KiB
QML

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