diff --git a/config/dot/quickshell/modules/settings/OnlineAccountsPage.qml b/config/dot/quickshell/modules/settings/OnlineAccountsPage.qml index 43d7594..d528781 100644 --- a/config/dot/quickshell/modules/settings/OnlineAccountsPage.qml +++ b/config/dot/quickshell/modules/settings/OnlineAccountsPage.qml @@ -15,6 +15,7 @@ // quietly stops syncing for weeks. import QtQuick +import Quickshell import qs.config import qs.services @@ -57,6 +58,22 @@ SettingsPage { id: accountCard required property var modelData + // GOA hands back a preference-ordered chain; this walks it and takes + // the first name the active icon theme actually has. Taking the + // first name blindly, or the last as a fallback, both looked right + // and were not: on Adwaita the tail of these chains ("mail", + // "goa-symbolic") does not exist, so a miss would have rendered + // nothing at all. + icon: { + const chain = accountCard.modelData.providerIcons ?? []; + for (const name of chain) { + if (Quickshell.iconPath(String(name), true) !== "") + return String(name); + } + return ""; + } + iconFallback: "avatar-default-symbolic" + title: accountCard.modelData.identity || accountCard.modelData.providerName subtitle: accountCard.modelData.needsAttention ? accountCard.modelData.providerName + " · sign-in expired, so this account has stopped syncing" diff --git a/config/dot/quickshell/modules/settings/SettingsCard.qml b/config/dot/quickshell/modules/settings/SettingsCard.qml index b07c7ad..d42ab85 100644 --- a/config/dot/quickshell/modules/settings/SettingsCard.qml +++ b/config/dot/quickshell/modules/settings/SettingsCard.qml @@ -9,6 +9,13 @@ Rectangle { property string title: "" property string subtitle: "" + // An optional themed icon beside the title, for cards that represent a + // specific thing rather than a topic -- an online account is one of four + // that otherwise differ only by a line of small grey text. Empty by + // default, and when empty the header lays out exactly as it did before. + property string icon: "" + property string iconFallback: "dialog-information-symbolic" + width: parent ? parent.width : 680 implicitHeight: body.implicitHeight + 30 radius: Theme.cardRadius + 2 @@ -32,15 +39,38 @@ Rectangle { anchors.margins: 15 spacing: 0 - Text { + Row { width: parent.width visible: root.title !== "" - text: root.title - color: Theme.fg - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeLarge - font.weight: Font.DemiBold - bottomPadding: root.subtitle === "" ? 10 : 3 + spacing: root.icon === "" ? 0 : 10 + + ThemedIcon { + anchors.verticalCenter: parent.verticalCenter + visible: root.icon !== "" + width: root.icon === "" ? 0 : 22 + icon: root.icon + iconFallback: root.iconFallback + size: 22 + } + + Text { + anchors.verticalCenter: parent.verticalCenter + width: parent.width - (root.icon === "" ? 0 : 32) + text: root.title + color: Theme.fg + font.family: Theme.fontFamily + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.DemiBold + elide: Text.ElideRight + } + } + + // The title's bottom padding lived on the title Text; it moves here so + // the Row above can centre an icon against it without the padding + // pushing the icon off-centre. + Item { + width: 1 + height: root.title === "" ? 0 : (root.subtitle === "" ? 10 : 3) } Text { diff --git a/config/dot/quickshell/scripts/panama-accounts b/config/dot/quickshell/scripts/panama-accounts index 9a3be6e..e36a450 100755 --- a/config/dot/quickshell/scripts/panama-accounts +++ b/config/dot/quickshell/scripts/panama-accounts @@ -68,6 +68,11 @@ def describe(obj): "path": obj.get_object_path(), "provider": account.props.provider_type, "providerName": account.props.provider_name, + # GOA hands back a serialised GThemedIcon: ". GThemedIcon name1 name2 …", + # a preference-ordered fallback chain. Passed on as that list rather than + # resolved here, because which of those names exists is a property of the + # icon theme in use, which this has no business deciding. + "providerIcons": themed_icon_names(account.props.provider_icon), # PresentationIdentity is the human one (an email address); Identity is # the internal handle and is not always readable. "identity": account.props.presentation_identity or account.props.identity, @@ -79,6 +84,26 @@ def describe(obj): } +def themed_icon_names(icon) -> list[str]: + """The icon names out of a serialised GThemedIcon, best first. + + The string form is ". GThemedIcon mail-unread-symbolic mail-symbolic mail", + where the leading "." and the type name are structure rather than content. + Anything that is not that shape yields nothing, so a caller gets an empty + list rather than a name that will never resolve. + """ + if icon is None: + return [] + try: + text = icon.to_string() + except Exception: + text = str(icon) + parts = str(text or "").split() + if len(parts) < 3 or parts[0] != "." or parts[1] != "GThemedIcon": + return [] + return [name for name in parts[2:] if name] + + def find(client, path): for obj in client.get_accounts(): if obj.get_object_path() == path: diff --git a/config/dot/quickshell/services/OnlineAccounts.qml b/config/dot/quickshell/services/OnlineAccounts.qml index cb4757e..9b88e2b 100644 --- a/config/dot/quickshell/services/OnlineAccounts.qml +++ b/config/dot/quickshell/services/OnlineAccounts.qml @@ -25,7 +25,8 @@ Singleton { readonly property string helperPath: Quickshell.shellDir + "/scripts/panama-accounts" - // [{ path, provider, providerName, identity, needsAttention, services: [{key,label,enabled}] }] + // [{ path, provider, providerName, providerIcons, identity, needsAttention, + // services: [{key,label,enabled}] }] property var accounts: [] property bool scanned: false property bool busy: false