pragma Singleton import QtQuick import Quickshell import qs.modules.functions import qs.config /* This registry is only used to get app details for wm classes. */ Singleton { id: registry property var apps: [] property var classToIcon: ({}) property var desktopIdToIcon: ({}) property var nameToIcon: ({}) signal ready() function iconForDesktopIcon(icon) { if (!icon) return "" // If it's already a URL, keep it if (icon.startsWith("file://") || icon.startsWith("qrc:/")) return icon // Absolute filesystem path → convert to file URL if (icon.startsWith("/")) return "file://" + icon // Otherwise treat as theme icon name return Quickshell.iconPath(icon) } // Try very aggressive matching so the running app always gets the same icon as launcher function iconForClass(id) { if (!id) return "" const lower = id.toLowerCase() // direct hits first if (classToIcon[lower]) return iconForDesktopIcon(classToIcon[lower]) if (desktopIdToIcon[lower]) return iconForDesktopIcon(desktopIdToIcon[lower]) if (nameToIcon[lower]) return iconForDesktopIcon(nameToIcon[lower]) // fuzzy contains match against wmClass map for (let key in classToIcon) { if (lower.includes(key) || key.includes(lower)) return iconForDesktopIcon(classToIcon[key]) } // fuzzy against desktop ids for (let key in desktopIdToIcon) { if (lower.includes(key) || key.includes(lower)) return iconForDesktopIcon(desktopIdToIcon[key]) } // fuzzy against names for (let key in nameToIcon) { if (lower.includes(key) || key.includes(lower)) return iconForDesktopIcon(nameToIcon[key]) } // final fallback to theme resolution const resolved = FileUtils.resolveIcon(id) return iconForDesktopIcon(resolved) } // Extra helper: resolve icon using any metadata we might have (Hyprland, Niri, etc.) function iconForAppMeta(meta) { if (!meta) return Quickshell.iconPath("application-x-executable") const candidates = [ meta.appId, meta.class, meta.initialClass, meta.desktopId, meta.title, meta.name ] for (let c of candidates) { const icon = iconForClass(c) if (icon !== "") return icon } // fallback: try compositor provided icon name if (meta.icon) return iconForDesktopIcon(meta.icon) // hard fallback icons (guaranteed to exist in most themes) const fallbacks = [ "application-x-executable", "application-default-icon", "window" ] for (let f of fallbacks) { const resolved = Quickshell.iconPath(f) if (resolved) return resolved } return "" } function registerApp(displayName, comment, icon, exec, wmClass, desktopId) { const entry = { name: displayName, comment: comment, icon: icon, exec: exec, wmClass: wmClass, desktopId: desktopId } apps.push(entry) if (wmClass) classToIcon[wmClass.toLowerCase()] = icon if (desktopId) desktopIdToIcon[desktopId.toLowerCase()] = icon if (displayName) nameToIcon[displayName.toLowerCase()] = icon // Hard aliases for apps with messy WM_CLASS values if (displayName.toLowerCase().includes("visual studio code") || icon.toLowerCase().includes("code")) { classToIcon["code"] = icon classToIcon["code-oss"] = icon classToIcon["code-url-handler"] = icon desktopIdToIcon["code.desktop"] = icon desktopIdToIcon["code-oss.desktop"] = icon } } function buildRegistry() { const entries = DesktopEntries.applications.values for (let entry of entries) { if (entry.noDisplay) continue registry.registerApp( entry.name || "", entry.comment || "", entry.icon || "", entry.execString || "", entry.startupWMClass || "", entry.id || "" ) } registry.ready() } Component.onCompleted: buildRegistry() }