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:
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user