From 6a047462e87c75b30185f381bde3f87b51bd8d1c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 22 Jul 2026 17:02:52 -0700 Subject: [PATCH] Decode launcher and tray icons at physical resolution Quickshell's IconImage decodes at logical size, so on HiDPI displays PNG icons were rendered from a texture at half the needed resolution and looked blurry next to SVG icons. Use plain Image with sourceSize scaled by Screen.devicePixelRatio, as the notification widgets already do. Co-Authored-By: Claude Fable 5 --- shell/plugins/bar/widgets/Tray.qml | 17 ++++++++++++----- shell/plugins/launcher/Launcher.qml | 10 ++++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/shell/plugins/bar/widgets/Tray.qml b/shell/plugins/bar/widgets/Tray.qml index 06f2734a..0355c93e 100644 --- a/shell/plugins/bar/widgets/Tray.qml +++ b/shell/plugins/bar/widgets/Tray.qml @@ -2,7 +2,6 @@ import Quickshell import QtQuick import QtQuick.Effects import Quickshell.Services.SystemTray -import Quickshell.Widgets import qs.Commons import qs.Ui import "TrayModel.js" as TrayModel @@ -502,15 +501,19 @@ BarWidget { font.pixelSize: Style.font.bodySmall } - IconImage { + Image { id: menuIcon visible: !menuRow.modelData.isSeparator && String(menuRow.modelData.icon || "") !== "" anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: Style.space(24) - implicitSize: Style.space(16) width: Style.space(16) height: Style.space(16) + fillMode: Image.PreserveAspectFit + // Decode at physical pixels: IconImage uses the logical size, + // which leaves PNG icons upscaled and blurry on HiDPI displays. + sourceSize.width: width * Screen.devicePixelRatio + sourceSize.height: height * Screen.devicePixelRatio source: menuRow.modelData.icon } @@ -569,10 +572,14 @@ BarWidget { required property var icon readonly property bool symbolic: root.iconIsSymbolic(icon) - IconImage { + Image { id: trayIconImage anchors.fill: parent - implicitSize: Math.round(Math.min(parent.width, parent.height)) + fillMode: Image.PreserveAspectFit + // Decode at physical pixels: IconImage uses the logical size, + // which leaves PNG icons upscaled and blurry on HiDPI displays. + sourceSize.width: Math.round(Math.min(width, height) * Screen.devicePixelRatio) + sourceSize.height: Math.round(Math.min(width, height) * Screen.devicePixelRatio) source: root.trayIconSource(trayIconRoot.icon) // Kept as a hidden layer so the effect can sample it as a texture. visible: !trayIconRoot.symbolic diff --git a/shell/plugins/launcher/Launcher.qml b/shell/plugins/launcher/Launcher.qml index 5a6370a3..b38b750d 100644 --- a/shell/plugins/launcher/Launcher.qml +++ b/shell/plugins/launcher/Launcher.qml @@ -1,7 +1,6 @@ import Quickshell import Quickshell.Io import Quickshell.Wayland -import Quickshell.Widgets import QtQuick import qs.Commons import qs.Ui @@ -591,15 +590,18 @@ Item { width: root.iconSlotWidth height: parent.height - IconImage { + Image { id: appIcon anchors.centerIn: parent - implicitSize: root.iconSize width: root.iconSize height: root.iconSize + fillMode: Image.PreserveAspectFit + // Decode at physical pixels: IconImage uses the logical size, + // which leaves PNG icons upscaled and blurry on HiDPI displays. + sourceSize.width: root.iconSize * Screen.devicePixelRatio + sourceSize.height: root.iconSize * Screen.devicePixelRatio source: root.iconSource(row.icon) asynchronous: true - mipmap: true } Text {