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 <noreply@anthropic.com>
This commit is contained in:
David Heinemeier Hansson
2026-07-22 17:02:52 -07:00
co-authored by Claude Fable 5
parent 4c38afeac3
commit 6a047462e8
2 changed files with 18 additions and 9 deletions
+12 -5
View File
@@ -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
+6 -4
View File
@@ -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 {