From 0f8e0bc2b41f3d7bea60ef30b25ceabfa4a2518c Mon Sep 17 00:00:00 2001 From: odt <15870533+odtgit@users.noreply.github.com> Date: Sun, 1 Mar 2026 23:31:51 +0100 Subject: [PATCH] refactor(icons): centralize icon resolution into Paths.resolveIconPath/resolveIconUrl (#1880) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Supersedes #1878. Rather than duplicating the moddedAppId + file path substitution pattern inline across 8 files, this introduces two centralized functions in Paths.qml: - resolveIconPath(iconName): for Quickshell.iconPath() callsites, with DesktopService.resolveIconPath() fallback - resolveIconUrl(iconName): for image://icon/ URL callsites All consumer files now use one-line calls. When no substitutions are configured, moddedAppId() returns the original name unchanged (zero cost), so this has no impact on users who don't use the feature. Affected components: - AppIconRenderer (8 lines → 1) - NotificationCard, NotificationPopup, HistoryNotificationCard - DockContextMenu, AppsDockContextMenu - LauncherContent, LauncherTab (×3) Co-authored-by: odtgit --- quickshell/Common/Paths.qml | 35 +++++++++++++++---- .../Modals/DankLauncherV2/LauncherContent.qml | 2 +- .../DankBar/Widgets/AppsDockContextMenu.qml | 2 +- quickshell/Modules/Dock/DockContextMenu.qml | 2 +- .../Center/HistoryNotificationCard.qml | 2 +- .../Notifications/Center/NotificationCard.qml | 4 +-- .../Notifications/Popup/NotificationPopup.qml | 2 +- quickshell/Modules/Settings/LauncherTab.qml | 6 ++-- quickshell/Widgets/AppIconRenderer.qml | 10 +----- 9 files changed, 39 insertions(+), 26 deletions(-) diff --git a/quickshell/Common/Paths.qml b/quickshell/Common/Paths.qml index e0c1f417..ee50cefb 100644 --- a/quickshell/Common/Paths.qml +++ b/quickshell/Common/Paths.qml @@ -71,19 +71,40 @@ Singleton { return appId; } - function getAppIcon(appId: string, desktopEntry: var): string { - if (appId === "org.quickshell") { - return Qt.resolvedUrl("../assets/danklogo.svg"); - } - - const moddedId = moddedAppId(appId); - if (moddedId !== appId) { + function resolveIconPath(iconName: string): string { + if (!iconName) return ""; + const moddedId = moddedAppId(iconName); + if (moddedId !== iconName) { if (moddedId.startsWith("~") || moddedId.startsWith("/")) return toFileUrl(expandTilde(moddedId)); if (moddedId.startsWith("file://")) return moddedId; return Quickshell.iconPath(moddedId, true); } + return Quickshell.iconPath(iconName, true) || DesktopService.resolveIconPath(iconName); + } + + function resolveIconUrl(iconName: string): string { + if (!iconName) return ""; + const moddedId = moddedAppId(iconName); + if (moddedId !== iconName) { + if (moddedId.startsWith("~") || moddedId.startsWith("/")) + return toFileUrl(expandTilde(moddedId)); + if (moddedId.startsWith("file://")) + return moddedId; + return "image://icon/" + moddedId; + } + return "image://icon/" + iconName; + } + + function getAppIcon(appId: string, desktopEntry: var): string { + if (appId === "org.quickshell") { + return Qt.resolvedUrl("../assets/danklogo.svg"); + } + + const moddedId = moddedAppId(appId); + if (moddedId !== appId) + return resolveIconPath(appId); if (desktopEntry && desktopEntry.icon) { return Quickshell.iconPath(desktopEntry.icon, true); diff --git a/quickshell/Modals/DankLauncherV2/LauncherContent.qml b/quickshell/Modals/DankLauncherV2/LauncherContent.qml index b84bbe0b..271888ae 100644 --- a/quickshell/Modals/DankLauncherV2/LauncherContent.qml +++ b/quickshell/Modals/DankLauncherV2/LauncherContent.qml @@ -789,7 +789,7 @@ FocusScope { Image { width: 40 height: 40 - source: editingApp?.icon ? "image://icon/" + editingApp.icon : "image://icon/application-x-executable" + source: Paths.resolveIconUrl(editingApp?.icon || "application-x-executable") sourceSize.width: 40 sourceSize.height: 40 fillMode: Image.PreserveAspectFit diff --git a/quickshell/Modules/DankBar/Widgets/AppsDockContextMenu.qml b/quickshell/Modules/DankBar/Widgets/AppsDockContextMenu.qml index 166befce..e6c6b185 100644 --- a/quickshell/Modules/DankBar/Widgets/AppsDockContextMenu.qml +++ b/quickshell/Modules/DankBar/Widgets/AppsDockContextMenu.qml @@ -273,7 +273,7 @@ PanelWindow { IconImage { anchors.fill: parent - source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : "" + source: modelData.icon ? Paths.resolveIconPath(modelData.icon) : "" smooth: true asynchronous: true visible: status === Image.Ready diff --git a/quickshell/Modules/Dock/DockContextMenu.qml b/quickshell/Modules/Dock/DockContextMenu.qml index fa2a1a36..50a144c3 100644 --- a/quickshell/Modules/Dock/DockContextMenu.qml +++ b/quickshell/Modules/Dock/DockContextMenu.qml @@ -329,7 +329,7 @@ PanelWindow { IconImage { anchors.fill: parent - source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : "" + source: modelData.icon ? Paths.resolveIconPath(modelData.icon) : "" smooth: true asynchronous: true visible: status === Image.Ready diff --git a/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml b/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml index f93f35eb..ce6f527d 100644 --- a/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml +++ b/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml @@ -142,7 +142,7 @@ Rectangle { return appIcon; if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:")) return ""; - return Quickshell.iconPath(appIcon, true); + return Paths.resolveIconPath(appIcon); } hasImage: hasNotificationImage diff --git a/quickshell/Modules/Notifications/Center/NotificationCard.qml b/quickshell/Modules/Notifications/Center/NotificationCard.qml index 0875abed..7f0c7ead 100644 --- a/quickshell/Modules/Notifications/Center/NotificationCard.qml +++ b/quickshell/Modules/Notifications/Center/NotificationCard.qml @@ -220,7 +220,7 @@ Rectangle { return appIcon; if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:")) return ""; - return Quickshell.iconPath(appIcon, true); + return Paths.resolveIconPath(appIcon); } hasImage: hasNotificationImage @@ -557,7 +557,7 @@ Rectangle { return appIcon; if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:")) return ""; - return Quickshell.iconPath(appIcon, true); + return Paths.resolveIconPath(appIcon); } fallbackIcon: { diff --git a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml index f1ee5c4d..d30f00dd 100644 --- a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml +++ b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml @@ -524,7 +524,7 @@ PanelWindow { return appIcon; if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:")) return ""; - return Quickshell.iconPath(appIcon, true); + return Paths.resolveIconPath(appIcon); } hasImage: hasNotificationImage diff --git a/quickshell/Modules/Settings/LauncherTab.qml b/quickshell/Modules/Settings/LauncherTab.qml index 13eabe79..ccdc1b45 100644 --- a/quickshell/Modules/Settings/LauncherTab.qml +++ b/quickshell/Modules/Settings/LauncherTab.qml @@ -897,7 +897,7 @@ Item { Image { width: 24 height: 24 - source: modelData.icon ? "image://icon/" + modelData.icon : "image://icon/application-x-executable" + source: Paths.resolveIconUrl(modelData.icon || "application-x-executable") sourceSize.width: 24 sourceSize.height: 24 fillMode: Image.PreserveAspectFit @@ -1008,7 +1008,7 @@ Item { Image { width: 24 height: 24 - source: modelData.icon ? "image://icon/" + modelData.icon : "image://icon/application-x-executable" + source: Paths.resolveIconUrl(modelData.icon || "application-x-executable") sourceSize.width: 24 sourceSize.height: 24 fillMode: Image.PreserveAspectFit @@ -1154,7 +1154,7 @@ Item { Image { width: 24 height: 24 - source: modelData.icon ? "image://icon/" + modelData.icon : "image://icon/application-x-executable" + source: Paths.resolveIconUrl(modelData.icon || "application-x-executable") sourceSize.width: 24 sourceSize.height: 24 fillMode: Image.PreserveAspectFit diff --git a/quickshell/Widgets/AppIconRenderer.qml b/quickshell/Widgets/AppIconRenderer.qml index 4661892f..9cdb71b3 100644 --- a/quickshell/Widgets/AppIconRenderer.qml +++ b/quickshell/Widgets/AppIconRenderer.qml @@ -49,15 +49,7 @@ Item { readonly property string iconPath: { if (hasSpecialPrefix || !iconValue) return ""; - const moddedId = Paths.moddedAppId(iconValue); - if (moddedId !== iconValue) { - if (moddedId.startsWith("~") || moddedId.startsWith("/")) - return Paths.toFileUrl(Paths.expandTilde(moddedId)); - if (moddedId.startsWith("file://")) - return moddedId; - return Quickshell.iconPath(moddedId, true); - } - return Quickshell.iconPath(iconValue, true) || DesktopService.resolveIconPath(iconValue); + return Paths.resolveIconPath(iconValue); } visible: iconValue !== undefined && iconValue !== ""