1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-03 20:32:07 -04:00

feat: improve icon resolution and align switcher fallback styling (#1823)

- Implement deep search icon resolution in DesktopService with runtime caching.
- Update Paths.getAppIcon to utilize enhanced resolution for mismatched app IDs.
- Align Workspace Switcher fallback icons with AppsDock visual style.
- Synchronize fallback text logic between Switcher and Dock using app names.
This commit is contained in:
null
2026-02-24 15:40:15 +01:00
committed by bbedward
parent 74e4f8ea1e
commit 2b08e800e8
3 changed files with 155 additions and 44 deletions

View File

@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import QtCore import QtCore
import qs.Services
Singleton { Singleton {
id: root id: root
@@ -83,7 +84,12 @@ Singleton {
if (desktopEntry && desktopEntry.icon) { if (desktopEntry && desktopEntry.icon) {
return Quickshell.iconPath(desktopEntry.icon, true); return Quickshell.iconPath(desktopEntry.icon, true);
} }
return Quickshell.iconPath(appId, true);
const icon = Quickshell.iconPath(appId, true);
if (icon && icon !== "")
return icon;
return DesktopService.resolveIconPath(appId);
} }
function getAppName(appId: string, desktopEntry: var): string { function getAppName(appId: string, desktopEntry: var): string {

View File

@@ -290,6 +290,7 @@ Item {
const moddedId = Paths.moddedAppId(keyBase); const moddedId = Paths.moddedAppId(keyBase);
const desktopEntry = DesktopEntries.heuristicLookup(moddedId); const desktopEntry = DesktopEntries.heuristicLookup(moddedId);
const icon = Paths.getAppIcon(keyBase, desktopEntry); const icon = Paths.getAppIcon(keyBase, desktopEntry);
const appName = Paths.getAppName(keyBase, desktopEntry);
byApp[key] = { byApp[key] = {
"type": "icon", "type": "icon",
"icon": icon, "icon": icon,
@@ -298,7 +299,7 @@ Item {
"active": !!((w.activated || w.is_focused) || (CompositorService.isNiri && w.is_focused)), "active": !!((w.activated || w.is_focused) || (CompositorService.isNiri && w.is_focused)),
"count": 1, "count": 1,
"windowId": w.address || w.id, "windowId": w.address || w.id,
"fallbackText": w.appId || w.class || w.title || "" "fallbackText": appName || ""
}; };
} else { } else {
byApp[key].count++; byApp[key].count++;
@@ -1473,9 +1474,44 @@ Item {
IconImage { IconImage {
id: rowAppIcon id: rowAppIcon
anchors.fill: parent anchors.fill: parent
source: modelData.icon source: modelData.icon || ""
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6 opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
visible: !modelData.isQuickshell && !modelData.isSteamApp visible: !modelData.isQuickshell && !modelData.isSteamApp && status === Image.Ready
}
Rectangle {
anchors.fill: parent
visible: !modelData.isQuickshell && !modelData.isSteamApp && rowAppIcon.status !== Image.Ready
color: Theme.surfaceContainer
radius: Theme.cornerRadius * (root.appIconSize / 40)
border.width: 1
border.color: Theme.primarySelected
opacity: (modelData.active || isActive) ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
StyledText {
anchors.centerIn: parent
text: (modelData.fallbackText || "?").charAt(0).toUpperCase()
font.pixelSize: parent.width * 0.45
color: Theme.primary
font.weight: Font.Bold
}
}
Rectangle {
anchors.fill: parent
visible: !modelData.isQuickshell && modelData.isSteamApp && rowSteamIcon.status !== Image.Ready
color: Theme.surfaceContainer
radius: Theme.cornerRadius * (root.appIconSize / 40)
border.width: 1
border.color: Theme.primarySelected
opacity: (modelData.active || isActive) ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
DankIcon {
anchors.centerIn: parent
size: parent.width * 0.7
name: "sports_esports"
color: Theme.primary
}
} }
IconImage { IconImage {
@@ -1592,9 +1628,44 @@ Item {
IconImage { IconImage {
id: colAppIcon id: colAppIcon
anchors.fill: parent anchors.fill: parent
source: modelData.icon source: modelData.icon || ""
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6 opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
visible: !modelData.isQuickshell && !modelData.isSteamApp visible: !modelData.isQuickshell && !modelData.isSteamApp && status === Image.Ready
}
Rectangle {
anchors.fill: parent
visible: !modelData.isQuickshell && !modelData.isSteamApp && colAppIcon.status !== Image.Ready
color: Theme.surfaceContainer
radius: Theme.cornerRadius * (root.appIconSize / 40)
border.width: 1
border.color: Theme.primarySelected
opacity: (modelData.active || isActive) ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
StyledText {
anchors.centerIn: parent
text: (modelData.fallbackText || "?").charAt(0).toUpperCase()
font.pixelSize: parent.width * 0.45
color: Theme.primary
font.weight: Font.Bold
}
}
Rectangle {
anchors.fill: parent
visible: !modelData.isQuickshell && modelData.isSteamApp && colSteamIcon.status !== Image.Ready
color: Theme.surfaceContainer
radius: Theme.cornerRadius * (root.appIconSize / 40)
border.width: 1
border.color: Theme.primarySelected
opacity: (modelData.active || isActive) ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
DankIcon {
anchors.centerIn: parent
size: parent.width * 0.7
name: "sports_esports"
color: Theme.primary
}
} }
IconImage { IconImage {

View File

@@ -8,52 +8,86 @@ import Quickshell.Io
Singleton { Singleton {
id: root id: root
property var _cache: ({})
function resolveIconPath(moddedAppId) { function resolveIconPath(moddedAppId) {
const entry = DesktopEntries.heuristicLookup(moddedAppId) if (!moddedAppId)
const appIds = [moddedAppId, moddedAppId.toLowerCase()]; return "";
const lastPart = moddedAppId.split('.').pop(); if (_cache[moddedAppId] !== undefined)
if (lastPart && lastPart !== moddedAppId) { return _cache[moddedAppId];
appIds.push(lastPart);
const firstChar = lastPart.charAt(0); const result = (function() {
const rest = lastPart.slice(1); // 1. Try heuristic lookup (standard)
let toggled; const entry = DesktopEntries.heuristicLookup(moddedAppId);
let icon = Quickshell.iconPath(entry?.icon, true);
if (icon && icon !== "")
return icon;
if (firstChar === firstChar.toLowerCase()) { // 2. Try the appId itself as an icon name
toggled = firstChar.toUpperCase() + rest; icon = Quickshell.iconPath(moddedAppId, true);
} else { if (icon && icon !== "")
toggled = firstChar.toLowerCase() + rest; return icon;
}
if (toggled !== lastPart) { // 3. Try variations of the appId (lowercase, last part)
appIds.push(toggled); const appIds = [moddedAppId.toLowerCase()];
} const lastPart = moddedAppId.split('.').pop();
} if (lastPart && lastPart !== moddedAppId) {
for (const appId of appIds){ appIds.push(lastPart);
let icon = Quickshell.iconPath(entry?.icon, true) appIds.push(lastPart.toLowerCase());
if (icon && icon !== "") return icon }
let execPath = entry?.execString?.replace(/\/bin.*/, "") for (const id of appIds) {
if (!execPath) continue icon = Quickshell.iconPath(id, true);
if (icon && icon !== "")
return icon;
}
//Check that the app is installed with nix/guix // 4. Deep search in all desktop entries (if the above fail)
if (execPath.startsWith("/nix/store/") || execPath.startsWith("/gnu/store/")) { // This is slow-ish but only happens once for failed icons
const basePath = execPath const strippedId = moddedAppId.replace(/-bin$/, "").toLowerCase();
const sizes = ["256x256", "128x128", "64x64", "48x48", "32x32", "24x24", "16x16"] const allEntries = DesktopEntries.applications.values;
for (let i = 0; i < allEntries.length; i++) {
const e = allEntries[i];
const eId = (e.id || "").toLowerCase();
const eName = (e.name || "").toLowerCase();
const eExec = (e.execString || "").toLowerCase();
let iconPath = `${basePath}/share/icons/hicolor/scalable/apps/${appId}.svg` if (eId.includes(strippedId) || eName.includes(strippedId) || eExec.includes(strippedId)) {
icon = Quickshell.iconPath(iconPath, true) icon = Quickshell.iconPath(e.icon, true);
if (icon && icon !== "") return icon if (icon && icon !== "")
return icon;
}
}
for (const size of sizes) { // 5. Nix/Guix specific store check (as a last resort)
iconPath = `${basePath}/share/icons/hicolor/${size}/apps/${appId}.png` for (const appId of appIds) {
icon = Quickshell.iconPath(iconPath, true) let execPath = entry?.execString?.replace(/\/bin.*/, "");
if (icon && icon !== "") return icon if (!execPath)
} continue;
}
if (execPath.startsWith("/nix/store/") || execPath.startsWith("/gnu/store/")) {
const basePath = execPath;
const sizes = ["256x256", "128x128", "64x64", "48x48", "32x32", "24x24", "16x16"];
let iconPath = `${basePath}/share/icons/hicolor/scalable/apps/${appId}.svg`;
icon = Quickshell.iconPath(iconPath, true);
if (icon && icon !== "")
return icon;
for (const size of sizes) {
iconPath = `${basePath}/share/icons/hicolor/${size}/apps/${appId}.png`;
icon = Quickshell.iconPath(iconPath, true);
if (icon && icon !== "")
return icon;
}
}
}
})();
_cache[moddedAppId] = result;
return result;
} }
return ""
}
} }