From 18d217c91de86d97e01f061552b533424aeadb75 Mon Sep 17 00:00:00 2001 From: pedroboussiengui Date: Sun, 24 May 2026 16:17:36 -0300 Subject: [PATCH] fix(tray): resolve icons advertised with custom theme path --- modules/bar/components/TrayItem.qml | 11 ++++++++- utils/Icons.qml | 38 ++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/modules/bar/components/TrayItem.qml b/modules/bar/components/TrayItem.qml index fefb532c9..ffeef7f92 100644 --- a/modules/bar/components/TrayItem.qml +++ b/modules/bar/components/TrayItem.qml @@ -26,9 +26,18 @@ MouseArea { ColouredIcon { id: icon + readonly property var candidates: Icons.getTrayIconCandidates(root.modelData.id, root.modelData.icon) + property int candidateIndex: 0 + anchors.fill: parent - source: Icons.getTrayIcon(root.modelData.id, root.modelData.icon) + source: candidates[candidateIndex] ?? "" colour: Colours.palette.m3secondary layer.enabled: Config.bar.tray.recolour + + onCandidatesChanged: candidateIndex = 0 + onStatusChanged: { + if (status === Image.Error && candidateIndex < candidates.length - 1) + candidateIndex++; + } } } diff --git a/utils/Icons.qml b/utils/Icons.qml index c864d5530..9f66554ec 100644 --- a/utils/Icons.qml +++ b/utils/Icons.qml @@ -234,13 +234,43 @@ Singleton { if (sub.id === id) return sub.image ? Qt.resolvedUrl(sub.image) : Quickshell.iconPath(sub.icon); - if (icon.includes("?path=")) { - const [name, path] = icon.split("?path="); - icon = Qt.resolvedUrl(`${path}/${name.slice(name.lastIndexOf("/") + 1)}`); - } + // For `name?path=/theme/root` (e.g. Dropbox), the consumer must walk + // freedesktop subdirs to find the actual file. Return the original + // string so TrayItem can iterate candidates via getTrayIconCandidates. return icon; } + function getTrayIconCandidates(id: string, icon: string): var { + for (const sub of GlobalConfig.bar.tray.iconSubs) + if (sub.id === id) + return [sub.image ? Qt.resolvedUrl(sub.image) : Quickshell.iconPath(sub.icon)]; + + if (!icon.includes("?path=")) + return [icon]; + + const [rawName, path] = icon.split("?path="); + const name = rawName.slice(rawName.lastIndexOf("/") + 1); + const sizes = ["scalable", "symbolic", "48x48", "32x32", "24x24", "22x22", "16x16"]; + const cats = ["status", "apps", "actions", "devices", "categories", "places", "panel"]; + const exts = [".png", ".svg"]; + const out = []; + + // 1. directly in path (legacy behaviour) + for (const ext of exts) + out.push(Qt.resolvedUrl(`${path}/${name}${ext}`)); + + // 2. proper freedesktop layout. The advertised path is the theme root, + // so try `hicolor` subtheme plus path itself as a theme dir. + for (const theme of ["hicolor", ""]) { + const base = theme ? `${path}/${theme}` : path; + for (const size of sizes) + for (const cat of cats) + for (const ext of exts) + out.push(Qt.resolvedUrl(`${base}/${size}/${cat}/${name}${ext}`)); + } + return out; + } + function getBatteryIcon(charge: int): string { if (charge > 0 && charge < 5) return "battery_0_bar";