import Quickshell import Quickshell.Widgets import QtQuick import qs.config // Renders a desktop-entry icon, which may be either an icon-theme name or an // absolute path. Falls back to a lettered tile so an entry with a broken or // missing icon still gets something clickable. Item { id: root property string icon: "" property string fallbackLabel: "" // When set, shown instead of the lettered tile if the icon cannot resolve. property string fallbackGlyph: "" property color glyphColor: Theme.text property int size: Theme.iconSize implicitWidth: size implicitHeight: size readonly property string resolved: root.resolveIcon(root.icon) function resolveIcon(name: string): string { if (!name) return ""; if (name.startsWith("file://") || name.startsWith("image://")) return name; if (name.startsWith("/")) return "file://" + name; // iconPath with checkExists returns "" when the theme has no such icon, // so these can be chained as fallbacks. The last segment of a // reverse-DNS app id ("org.qutebrowser.qutebrowser") is very often the // real icon name, so try that before giving up. const lower = name.toLowerCase(); const tail = lower.split(".").pop(); return Quickshell.iconPath(name, true) || Quickshell.iconPath(lower, true) || (tail !== lower ? Quickshell.iconPath(tail, true) : ""); } IconImage { id: image anchors.fill: parent source: root.resolved visible: root.resolved !== "" && image.status !== Image.Error } Text { anchors.centerIn: parent visible: !image.visible && root.fallbackGlyph !== "" text: root.fallbackGlyph color: root.glyphColor font.family: Theme.fontFamily font.pixelSize: Math.max(10, root.size * 0.8) } Rectangle { anchors.fill: parent visible: !image.visible && root.fallbackGlyph === "" radius: Math.max(2, root.size / 5) color: Theme.surfaceActive Text { anchors.centerIn: parent text: (root.fallbackLabel || "?").charAt(0).toUpperCase() color: Theme.textDim font.family: Theme.fontFamily font.pixelSize: Math.max(8, root.size * 0.55) font.bold: true } } }