diff --git a/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml b/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml index 4d7e4bb1..aa65722f 100644 --- a/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml +++ b/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml @@ -89,7 +89,17 @@ Rectangle { DankCircularImage { id: iconContainer - readonly property bool hasNotificationImage: historyItem.image && historyItem.image !== "" + readonly property string rawImage: historyItem.image || "" + readonly property string iconFromImage: { + if (rawImage.startsWith("image://icon/")) + return rawImage.substring(13); + return ""; + } + readonly property bool imageHasSpecialPrefix: { + const icon = iconFromImage; + return icon.startsWith("material:") || icon.startsWith("svg:") || icon.startsWith("unicode:") || icon.startsWith("image:"); + } + readonly property bool hasNotificationImage: rawImage !== "" && !rawImage.startsWith("image://icon/") width: iconSize height: iconSize @@ -99,17 +109,24 @@ Rectangle { imageSource: { if (hasNotificationImage) return historyItem.image; - if (historyItem.appIcon) { - const appIcon = historyItem.appIcon; - if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://")) - return appIcon; - return Quickshell.iconPath(appIcon, true); - } - return ""; + if (imageHasSpecialPrefix) + return ""; + const appIcon = historyItem.appIcon; + if (!appIcon) + return iconFromImage ? "image://icon/" + iconFromImage : ""; + if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/")) + return appIcon; + if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:")) + return ""; + return Quickshell.iconPath(appIcon, true); } hasImage: hasNotificationImage - fallbackIcon: "" + fallbackIcon: { + if (imageHasSpecialPrefix) + return iconFromImage; + return historyItem.appIcon || iconFromImage || ""; + } fallbackText: { const appName = historyItem.appName || "?"; return appName.charAt(0).toUpperCase(); diff --git a/quickshell/Modules/Notifications/Center/NotificationCard.qml b/quickshell/Modules/Notifications/Center/NotificationCard.qml index 331b4565..3a00bab8 100644 --- a/quickshell/Modules/Notifications/Center/NotificationCard.qml +++ b/quickshell/Modules/Notifications/Center/NotificationCard.qml @@ -113,7 +113,17 @@ Rectangle { DankCircularImage { id: iconContainer - readonly property bool hasNotificationImage: notificationGroup?.latestNotification?.image && notificationGroup.latestNotification.image !== "" + readonly property string rawImage: notificationGroup?.latestNotification?.image || "" + readonly property string iconFromImage: { + if (rawImage.startsWith("image://icon/")) + return rawImage.substring(13); + return ""; + } + readonly property bool imageHasSpecialPrefix: { + const icon = iconFromImage; + return icon.startsWith("material:") || icon.startsWith("svg:") || icon.startsWith("unicode:") || icon.startsWith("image:"); + } + readonly property bool hasNotificationImage: rawImage !== "" && !rawImage.startsWith("image://icon/") width: iconSize height: iconSize @@ -123,17 +133,24 @@ Rectangle { imageSource: { if (hasNotificationImage) return notificationGroup.latestNotification.cleanImage; - if (notificationGroup?.latestNotification?.appIcon) { - const appIcon = notificationGroup.latestNotification.appIcon; - if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://")) - return appIcon; - return Quickshell.iconPath(appIcon, true); - } - return ""; + if (imageHasSpecialPrefix) + return ""; + const appIcon = notificationGroup?.latestNotification?.appIcon; + if (!appIcon) + return iconFromImage ? "image://icon/" + iconFromImage : ""; + if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/")) + return appIcon; + if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:")) + return ""; + return Quickshell.iconPath(appIcon, true); } hasImage: hasNotificationImage - fallbackIcon: "" + fallbackIcon: { + if (imageHasSpecialPrefix) + return iconFromImage; + return notificationGroup?.latestNotification?.appIcon || iconFromImage || ""; + } fallbackText: { const appName = notificationGroup?.appName || "?"; return appName.charAt(0).toUpperCase(); @@ -360,7 +377,17 @@ Rectangle { DankCircularImage { id: messageIcon - readonly property bool hasNotificationImage: modelData?.image && modelData.image !== "" + readonly property string rawImage: modelData?.image || "" + readonly property string iconFromImage: { + if (rawImage.startsWith("image://icon/")) + return rawImage.substring(13); + return ""; + } + readonly property bool imageHasSpecialPrefix: { + const icon = iconFromImage; + return icon.startsWith("material:") || icon.startsWith("svg:") || icon.startsWith("unicode:") || icon.startsWith("image:"); + } + readonly property bool hasNotificationImage: rawImage !== "" && !rawImage.startsWith("image://icon/") width: expandedIconSize height: expandedIconSize @@ -371,18 +398,23 @@ Rectangle { imageSource: { if (hasNotificationImage) return modelData.cleanImage; - - if (modelData?.appIcon) { - const appIcon = modelData.appIcon; - if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://")) - return appIcon; - - return Quickshell.iconPath(appIcon, true); - } - return ""; + if (imageHasSpecialPrefix) + return ""; + const appIcon = modelData?.appIcon; + if (!appIcon) + return iconFromImage ? "image://icon/" + iconFromImage : ""; + if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/")) + return appIcon; + if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:")) + return ""; + return Quickshell.iconPath(appIcon, true); } - fallbackIcon: "" + fallbackIcon: { + if (imageHasSpecialPrefix) + return iconFromImage; + return modelData?.appIcon || iconFromImage || ""; + } fallbackText: { const appName = modelData?.appName || "?"; diff --git a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml index 047b0a45..90b98bcf 100644 --- a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml +++ b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml @@ -384,8 +384,18 @@ PanelWindow { DankCircularImage { id: iconContainer - readonly property bool hasNotificationImage: notificationData && notificationData.image && notificationData.image !== "" - readonly property bool needsImagePersist: hasNotificationImage && notificationData.image.startsWith("image://qsimage/") && !notificationData.persistedImagePath + readonly property string rawImage: notificationData?.image || "" + readonly property string iconFromImage: { + if (rawImage.startsWith("image://icon/")) + return rawImage.substring(13); + return ""; + } + readonly property bool imageHasSpecialPrefix: { + const icon = iconFromImage; + return icon.startsWith("material:") || icon.startsWith("svg:") || icon.startsWith("unicode:") || icon.startsWith("image:"); + } + readonly property bool hasNotificationImage: rawImage !== "" && !rawImage.startsWith("image://icon/") + readonly property bool needsImagePersist: hasNotificationImage && rawImage.startsWith("image://qsimage/") && !notificationData.persistedImagePath width: popupIconSize height: popupIconSize @@ -395,22 +405,26 @@ PanelWindow { imageSource: { if (!notificationData) return ""; - if (hasNotificationImage) return notificationData.cleanImage || ""; - - if (notificationData.appIcon) { - const appIcon = notificationData.appIcon; - if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://")) - return appIcon; - - return Quickshell.iconPath(appIcon, true); - } - return ""; + if (imageHasSpecialPrefix) + return ""; + const appIcon = notificationData.appIcon; + if (!appIcon) + return iconFromImage ? "image://icon/" + iconFromImage : ""; + if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/")) + return appIcon; + if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:")) + return ""; + return Quickshell.iconPath(appIcon, true); } hasImage: hasNotificationImage - fallbackIcon: "" + fallbackIcon: { + if (imageHasSpecialPrefix) + return iconFromImage; + return notificationData?.appIcon || iconFromImage || ""; + } fallbackText: { const appName = notificationData?.appName || "?"; return appName.charAt(0).toUpperCase(); diff --git a/quickshell/Widgets/AppIconRenderer.qml b/quickshell/Widgets/AppIconRenderer.qml index e23e9c59..bb962ae9 100644 --- a/quickshell/Widgets/AppIconRenderer.qml +++ b/quickshell/Widgets/AppIconRenderer.qml @@ -20,17 +20,17 @@ Item { property real materialIconSizeAdjustment: Theme.spacingM property real unicodeIconScale: 0.7 property real fallbackTextScale: 0.4 - property alias iconMargins: iconImg.anchors.margins + property real iconMargins: 0 property real fallbackLeftMargin: 0 property real fallbackRightMargin: 0 property real fallbackTopMargin: 0 property real fallbackBottomMargin: 0 - readonly property bool isMaterial: iconValue.startsWith("material:") - readonly property bool isUnicode: iconValue.startsWith("unicode:") - readonly property bool isSvgCorner: iconValue.startsWith("svg+corner:") - readonly property bool isSvg: !isSvgCorner && iconValue.startsWith("svg:") - readonly property bool isImage: iconValue.startsWith("image:") + readonly property bool isMaterial: iconValue && iconValue.startsWith("material:") + readonly property bool isUnicode: iconValue && iconValue.startsWith("unicode:") + readonly property bool isSvgCorner: iconValue && iconValue.startsWith("svg+corner:") + readonly property bool isSvg: iconValue && !isSvgCorner && iconValue.startsWith("svg:") + readonly property bool isImage: iconValue && iconValue.startsWith("image:") readonly property bool hasColorOverride: colorOverride.a > 0 readonly property string materialName: isMaterial ? iconValue.substring(9) : "" readonly property string unicodeChar: isUnicode ? iconValue.substring(8) : "" @@ -45,7 +45,12 @@ Item { return ""; } readonly property string svgCornerIcon: isSvgCorner ? (iconValue.substring(11).split("|")[1] || "") : "" - readonly property string iconPath: isMaterial || isUnicode || isSvg || isSvgCorner || isImage ? "" : Quickshell.iconPath(iconValue, true) || DesktopService.resolveIconPath(iconValue) + readonly property bool hasSpecialPrefix: isMaterial || isUnicode || isSvg || isSvgCorner || isImage + readonly property string iconPath: { + if (hasSpecialPrefix || !iconValue) + return ""; + return Quickshell.iconPath(iconValue, true) || DesktopService.resolveIconPath(iconValue); + } visible: iconValue !== undefined && iconValue !== "" @@ -85,15 +90,19 @@ Item { visible: root.isImage && status === Image.Ready } - IconImage { - id: iconImg - + Loader { + id: iconImgLoader anchors.fill: parent - source: root.iconPath - backer.sourceSize: Qt.size(root.iconSize, root.iconSize) - mipmap: true - asynchronous: true - visible: !root.isMaterial && !root.isUnicode && !root.isSvg && !root.isSvgCorner && !root.isImage && root.iconPath !== "" && status === Image.Ready + anchors.margins: root.iconMargins + active: !root.hasSpecialPrefix && root.iconPath !== "" + sourceComponent: IconImage { + anchors.fill: parent + source: root.iconPath + backer.sourceSize: Qt.size(root.iconSize, root.iconSize) + mipmap: true + asynchronous: true + visible: status === Image.Ready + } } Rectangle { @@ -104,7 +113,7 @@ Item { anchors.rightMargin: root.fallbackRightMargin anchors.topMargin: root.fallbackTopMargin anchors.bottomMargin: root.fallbackBottomMargin - visible: !root.isMaterial && !root.isUnicode && !root.isSvg && !root.isSvgCorner && !root.isImage && (root.iconPath === "" || iconImg.status !== Image.Ready) + visible: !root.hasSpecialPrefix && (root.iconPath === "" || !iconImgLoader.item || iconImgLoader.item.status !== Image.Ready) color: root.fallbackBackgroundColor radius: Theme.cornerRadius border.width: 0 diff --git a/quickshell/Widgets/DankCircularImage.qml b/quickshell/Widgets/DankCircularImage.qml index 81d2c786..5e6fcb11 100644 --- a/quickshell/Widgets/DankCircularImage.qml +++ b/quickshell/Widgets/DankCircularImage.qml @@ -71,12 +71,18 @@ Rectangle { } } - DankIcon { + AppIconRenderer { anchors.centerIn: parent - name: root.fallbackIcon - size: parent.width * 0.5 - color: Theme.surfaceVariantText + width: parent.width * 0.75 + height: width visible: (internalImage.status !== Image.Ready || root.imageSource === "") && root.fallbackIcon !== "" + iconValue: root.fallbackIcon + iconSize: width + iconColor: Theme.surfaceVariantText + materialIconSizeAdjustment: 0 + fallbackText: root.fallbackText + fallbackBackgroundColor: "transparent" + fallbackTextColor: Theme.surfaceVariantText } StyledText {