From d2e25445290cbc6ebf38e33e16128fce14bc20db Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 23 May 2026 10:25:31 +0200 Subject: [PATCH] Fallback to app icon if there's nothing else --- .../bar/widgets/NotificationCenter.qml | 10 +++++++++- .../components/NotificationCard.qml | 19 ++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/shell/plugins/bar/widgets/NotificationCenter.qml b/shell/plugins/bar/widgets/NotificationCenter.qml index 6742a33e..20788cfe 100644 --- a/shell/plugins/bar/widgets/NotificationCenter.qml +++ b/shell/plugins/bar/widgets/NotificationCenter.qml @@ -44,6 +44,14 @@ BarWidget { .replace(/^\s*(?:https?:\/\/|www\.)?(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d+)?(?:\/\S*)?\s+/i, "") } + function notificationIconSource(icon) { + var value = String(icon || "") + if (value.length === 0) return "" + if (value.indexOf("file://") === 0 || value.indexOf("image://") === 0) return value + if (value.charAt(0) === "/") return Util.fileUrl(value) + return Quickshell.iconPath(value, true) + } + readonly property int pendingCount: notificationService ? notificationService.pendingModel.count : 0 readonly property int pastCount: notificationService ? notificationService.pastModel.count : 0 readonly property bool dnd: notificationService ? notificationService.doNotDisturb : false @@ -284,7 +292,7 @@ BarWidget { readonly property bool hasMedia: image.length > 0 && ( image.indexOf("image://icon//") === 0 || image.indexOf("file://") === 0) - readonly property string smallIconSource: image.length > 0 ? image : appIcon + readonly property string smallIconSource: image.length > 0 ? image : root.notificationIconSource(appIcon) readonly property bool hasIcon: !hasMedia && smallIconSource.length > 0 readonly property string sanitizedBody: root.sanitizeBody(body, app, appIcon) diff --git a/shell/plugins/notifications/components/NotificationCard.qml b/shell/plugins/notifications/components/NotificationCard.qml index 74efcd00..afaf6785 100644 --- a/shell/plugins/notifications/components/NotificationCard.qml +++ b/shell/plugins/notifications/components/NotificationCard.qml @@ -4,6 +4,7 @@ import QtQuick import QtQuick.Layouts +import Quickshell import qs.Commons Rectangle { @@ -31,13 +32,9 @@ Rectangle { signal closeRequested() signal cardClicked() - // Use only what the notification explicitly carries — no themed-icon - // theme-lookup fallback because Quickshell's icon image provider returns - // a placeholder for missing names (rather than erroring), which means - // we'd render Qt's pink "broken image" pattern for any unknown app. - // Apps that send their own icon via `image` (image-data hint) or - // `appIcon` (-i flag) still get one. - readonly property string smallIconSource: image.length > 0 ? image : appIcon + // Prefer per-notification media/avatar data, then fall back to the app icon. + // The `check` flag avoids Qt's missing-texture placeholder for unknown names. + readonly property string smallIconSource: image.length > 0 ? image : iconSource(appIcon) readonly property bool hasGlyph: glyph.length > 0 readonly property bool hasSmallIcon: smallIconSource.length > 0 || hasGlyph readonly property bool summaryStartsWithGlyph: /^\s*\S\s{2,}/.test(summary) @@ -67,6 +64,14 @@ Rectangle { .replace(/^\s*(?:https?:\/\/|www\.)?(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d+)?(?:\/\S*)?\s+/i, "") } + function iconSource(icon) { + var value = String(icon || "") + if (value.length === 0) return "" + if (value.indexOf("file://") === 0 || value.indexOf("image://") === 0) return value + if (value.charAt(0) === "/") return Util.fileUrl(value) + return Quickshell.iconPath(value, true) + } + implicitWidth: Style.space(380) // Add 2 * border.width so mainColumn (inset by border.width on top/left/right) // doesn't push content under the bottom edge.