From 1edc8f468ed537bf7f85084c032b6e4002051313 Mon Sep 17 00:00:00 2001 From: purian23 Date: Thu, 22 Jan 2026 17:45:38 -0500 Subject: [PATCH] feat: Pinnable DMS coreApps w/Color options --- .../Modals/DankLauncherV2/LauncherContent.qml | 2 - .../DankLauncherV2/LauncherContextMenu.qml | 40 ++++++--- quickshell/Modules/Dock/DockAppButton.qml | 61 ++++++++++++-- quickshell/Modules/Dock/DockApps.qml | 81 +++++++++++++++++-- quickshell/Widgets/AppIconRenderer.qml | 13 ++- 5 files changed, 167 insertions(+), 30 deletions(-) diff --git a/quickshell/Modals/DankLauncherV2/LauncherContent.qml b/quickshell/Modals/DankLauncherV2/LauncherContent.qml index f42e3487..472a32e4 100644 --- a/quickshell/Modals/DankLauncherV2/LauncherContent.qml +++ b/quickshell/Modals/DankLauncherV2/LauncherContent.qml @@ -76,8 +76,6 @@ FocusScope { function showContextMenu(item, x, y, fromKeyboard) { if (!item) return; - if (item.isCore) - return; if (!contextMenu.hasContextMenuActions(item)) return; contextMenu.show(x, y, item, fromKeyboard); diff --git a/quickshell/Modals/DankLauncherV2/LauncherContextMenu.qml b/quickshell/Modals/DankLauncherV2/LauncherContextMenu.qml index d42c48a5..74f4f50a 100644 --- a/quickshell/Modals/DankLauncherV2/LauncherContextMenu.qml +++ b/quickshell/Modals/DankLauncherV2/LauncherContextMenu.qml @@ -20,7 +20,7 @@ Popup { function hasContextMenuActions(spotlightItem) { if (!spotlightItem) return false; - if (spotlightItem.type === "app" && !spotlightItem.isCore) + if (spotlightItem.type === "app") return true; if (spotlightItem.type === "plugin" && spotlightItem.pluginId) { var instance = PluginService.pluginInstances[spotlightItem.pluginId]; @@ -34,9 +34,16 @@ Popup { return false; } - readonly property var desktopEntry: item?.data ?? null - readonly property string appId: desktopEntry?.id || desktopEntry?.execString || "" - readonly property bool isPinned: SessionData.isPinnedApp(appId) + readonly property bool isCoreApp: item?.type === "app" && item?.isCore + readonly property var coreAppData: isCoreApp ? item?.data ?? null : null + readonly property var desktopEntry: !isCoreApp ? (item?.data ?? null) : null + readonly property string appId: { + if (isCoreApp) { + return item?.id || coreAppData?.builtInPluginId || ""; + } + return desktopEntry?.id || desktopEntry?.execString || ""; + } + readonly property bool isPinned: appId ? SessionData.isPinnedApp(appId) : false readonly property bool isRegularApp: item?.type === "app" && !item.isCore && desktopEntry readonly property bool isPluginItem: item?.type === "plugin" @@ -82,15 +89,14 @@ Popup { return items; } - if (!desktopEntry) - return items; - - items.push({ - type: "item", - icon: isPinned ? "keep_off" : "push_pin", - text: isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock"), - action: togglePin - }); + if (item?.type === "app") { + items.push({ + type: "item", + icon: isPinned ? "keep_off" : "push_pin", + text: isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock"), + action: togglePin + }); + } if (isRegularApp) { items.push({ @@ -200,6 +206,14 @@ Popup { } function launchApp() { + if (isCoreApp) { + if (!coreAppData) + return; + AppSearchService.executeCoreApp(coreAppData); + controller?.itemExecuted(); + hide(); + return; + } if (!desktopEntry) return; SessionService.launchDesktopEntry(desktopEntry); diff --git a/quickshell/Modules/Dock/DockAppButton.qml b/quickshell/Modules/Dock/DockAppButton.qml index 1562808c..619a1d27 100644 --- a/quickshell/Modules/Dock/DockAppButton.qml +++ b/quickshell/Modules/Dock/DockAppButton.qml @@ -29,12 +29,29 @@ Item { property bool showTooltip: mouseArea.containsMouse && !dragging property var cachedDesktopEntry: null property real actualIconSize: 40 + readonly property string coreIconColorOverride: SettingsData.dockLauncherLogoColorOverride + readonly property bool coreIconHasCustomColor: coreIconColorOverride !== "" && coreIconColorOverride !== "primary" && coreIconColorOverride !== "surface" + readonly property color effectiveCoreIconColor: { + if (coreIconColorOverride === "primary") + return Theme.primary; + if (coreIconColorOverride === "surface") + return Theme.surfaceText; + if (coreIconColorOverride !== "") + return coreIconColorOverride; + return Theme.surfaceText; + } + readonly property real effectiveCoreIconBrightness: coreIconHasCustomColor ? SettingsData.dockLauncherLogoBrightness : 0.0 + readonly property real effectiveCoreIconContrast: coreIconHasCustomColor ? SettingsData.dockLauncherLogoContrast : 0.0 function updateDesktopEntry() { if (!appData || appData.appId === "__SEPARATOR__") { cachedDesktopEntry = null; return; } + if (appData.isCoreApp) { + cachedDesktopEntry = null; + return; + } const moddedId = Paths.moddedAppId(appData.appId); cachedDesktopEntry = DesktopEntries.heuristicLookup(moddedId); } @@ -85,7 +102,12 @@ Item { return ""; } - const appName = Paths.getAppName(appData.appId, cachedDesktopEntry); + let appName; + if (appData.isCoreApp && appData.coreAppData) { + appName = appData.coreAppData.name || appData.appId; + } else { + appName = Paths.getAppName(appData.appId, cachedDesktopEntry); + } if ((appData.type === "window" && showWindowTitle) || (appData.type === "grouped" && appData.windowTitle)) { const title = appData.type === "window" ? windowTitle : appData.windowTitle; @@ -227,6 +249,10 @@ Item { case "pinned": if (!appData.appId) return; + if (appData.isCoreApp && appData.coreAppData) { + AppSearchService.executeCoreApp(appData.coreAppData); + return; + } const pinnedEntry = cachedDesktopEntry; if (pinnedEntry) { AppUsageHistoryData.addAppUsage({ @@ -248,6 +274,10 @@ Item { if (appData.windowCount === 0) { if (!appData.appId) return; + if (appData.isCoreApp && appData.coreAppData) { + AppSearchService.executeCoreApp(appData.coreAppData); + return; + } const groupedEntry = cachedDesktopEntry; if (groupedEntry) { AppUsageHistoryData.addAppUsage({ @@ -374,6 +404,19 @@ Item { z: -1 } + AppIconRenderer { + id: coreIcon + + anchors.centerIn: parent + iconSize: actualIconSize + iconValue: appData && appData.isCoreApp && appData.coreAppData ? (appData.coreAppData.icon || "") : "" + colorOverride: effectiveCoreIconColor + brightnessOverride: effectiveCoreIconBrightness + contrastOverride: effectiveCoreIconContrast + fallbackText: "?" + visible: iconValue !== "" + } + IconImage { id: iconImg @@ -383,12 +426,15 @@ Item { if (!appData || appData.appId === "__SEPARATOR__") { return ""; } + if (appData.isCoreApp && appData.coreAppData) { + return ""; + } return Paths.getAppIcon(appData.appId, cachedDesktopEntry); } mipmap: true smooth: true asynchronous: true - visible: status === Image.Ready + visible: status === Image.Ready && !coreIcon.visible layer.enabled: appData && appData.appId === "org.quickshell" layer.smooth: true layer.mipmap: true @@ -403,7 +449,7 @@ Item { width: actualIconSize height: actualIconSize anchors.centerIn: parent - visible: iconImg.status !== Image.Ready && appData && appData.appId && !Paths.isSteamApp(appData.appId) + visible: !coreIcon.visible && iconImg.status !== Image.Ready && appData && appData.appId && !Paths.isSteamApp(appData.appId) color: Theme.surfaceLight radius: Theme.cornerRadius border.width: 1 @@ -416,7 +462,12 @@ Item { return "?"; } - const appName = Paths.getAppName(appData.appId, cachedDesktopEntry); + let appName; + if (appData.isCoreApp && appData.coreAppData) { + appName = appData.coreAppData.name || appData.appId; + } else { + appName = Paths.getAppName(appData.appId, cachedDesktopEntry); + } return appName.charAt(0).toUpperCase(); } font.pixelSize: Math.max(8, parent.width * 0.35) @@ -430,7 +481,7 @@ Item { size: actualIconSize name: "sports_esports" color: Theme.surfaceText - visible: iconImg.status !== Image.Ready && appData && appData.appId && Paths.isSteamApp(appData.appId) + visible: !coreIcon.visible && iconImg.status !== Image.Ready && appData && appData.appId && Paths.isSteamApp(appData.appId) } Loader { diff --git a/quickshell/Modules/Dock/DockApps.qml b/quickshell/Modules/Dock/DockApps.qml index af2cec9f..c05dfe35 100644 --- a/quickshell/Modules/Dock/DockApps.qml +++ b/quickshell/Modules/Dock/DockApps.qml @@ -91,6 +91,34 @@ Item { return false; } + function getCoreAppData(appId) { + if (typeof AppSearchService === "undefined") + return null; + + const coreApps = AppSearchService.coreApps || []; + for (let i = 0; i < coreApps.length; i++) { + const app = coreApps[i]; + if (app.builtInPluginId === appId) { + return app; + } + } + return null; + } + + function getCoreAppDataByTitle(windowTitle) { + if (typeof AppSearchService === "undefined" || !windowTitle) + return null; + + const coreApps = AppSearchService.coreApps || []; + for (let i = 0; i < coreApps.length; i++) { + const app = coreApps[i]; + if (app.name === windowTitle) { + return app; + } + } + return null; + } + function insertLauncher(targetArray) { if (!SettingsData.dockLauncherEnabled) return; @@ -119,21 +147,35 @@ Item { pinnedApps.forEach(rawAppId => { const appId = Paths.moddedAppId(rawAppId); + const coreAppData = getCoreAppData(appId); appGroups.set(appId, { appId: appId, isPinned: true, - windows: [] + windows: [], + isCoreApp: coreAppData !== null, + coreAppData: coreAppData }); }); sortedToplevels.forEach((toplevel, index) => { const rawAppId = toplevel.appId || "unknown"; - const appId = Paths.moddedAppId(rawAppId); + let appId = Paths.moddedAppId(rawAppId); + + let coreAppData = null; + if (rawAppId === "org.quickshell") { + coreAppData = getCoreAppDataByTitle(toplevel.title); + if (coreAppData) { + appId = coreAppData.builtInPluginId; + } + } + if (!appGroups.has(appId)) { appGroups.set(appId, { appId: appId, isPinned: false, - windows: [] + windows: [], + isCoreApp: coreAppData !== null, + coreAppData: coreAppData }); } @@ -157,7 +199,9 @@ Item { isPinned: group.isPinned, isRunning: group.windows.length > 0, windowCount: group.windows.length, - allWindows: group.windows + allWindows: group.windows, + isCoreApp: group.isCoreApp || false, + coreAppData: group.coreAppData || null }; if (group.isPinned) { @@ -187,13 +231,16 @@ Item { } else { pinnedApps.forEach(rawAppId => { const appId = Paths.moddedAppId(rawAppId); + const coreAppData = getCoreAppData(appId); items.push({ uniqueKey: "pinned_" + appId, type: "pinned", appId: appId, toplevel: null, isPinned: true, - isRunning: false + isRunning: false, + isCoreApp: coreAppData !== null, + coreAppData: coreAppData }); }); @@ -224,13 +271,31 @@ Item { } } + const rawAppId = toplevel.appId || "unknown"; + const moddedAppId = Paths.moddedAppId(rawAppId); + + // Check if this is a core app window (e.g., Settings modal with appId "org.quickshell") + let coreAppData = null; + let isCoreApp = false; + if (rawAppId === "org.quickshell") { + coreAppData = getCoreAppDataByTitle(toplevel.title); + if (coreAppData) { + isCoreApp = true; + } + } + + const finalAppId = isCoreApp ? coreAppData.builtInPluginId : moddedAppId; + const isPinned = pinnedApps.indexOf(finalAppId) !== -1; + items.push({ uniqueKey: uniqueKey, type: "window", - appId: Paths.moddedAppId(toplevel.appId), + appId: finalAppId, toplevel: toplevel, - isPinned: false, - isRunning: true + isPinned: isPinned, + isRunning: true, + isCoreApp: isCoreApp, + coreAppData: coreAppData }); }); } diff --git a/quickshell/Widgets/AppIconRenderer.qml b/quickshell/Widgets/AppIconRenderer.qml index 96fe3a26..e23e9c59 100644 --- a/quickshell/Widgets/AppIconRenderer.qml +++ b/quickshell/Widgets/AppIconRenderer.qml @@ -11,6 +11,10 @@ Item { required property int iconSize property string fallbackText: "A" property color iconColor: Theme.surfaceText + property color colorOverride: "transparent" + property real brightnessOverride: 0.0 + property real contrastOverride: 0.0 + property real saturationOverride: 0.0 property color fallbackBackgroundColor: Theme.surfaceLight property color fallbackTextColor: Theme.primary property real materialIconSizeAdjustment: Theme.spacingM @@ -27,6 +31,7 @@ Item { readonly property bool isSvgCorner: iconValue.startsWith("svg+corner:") readonly property bool isSvg: !isSvgCorner && iconValue.startsWith("svg:") readonly property bool isImage: iconValue.startsWith("image:") + readonly property bool hasColorOverride: colorOverride.a > 0 readonly property string materialName: isMaterial ? iconValue.substring(9) : "" readonly property string unicodeChar: isUnicode ? iconValue.substring(8) : "" readonly property string imagePath: isImage ? iconValue.substring(6) : "" @@ -48,7 +53,7 @@ Item { anchors.centerIn: parent name: root.materialName size: root.iconSize - root.materialIconSizeAdjustment - color: root.iconColor + color: root.hasColorOverride ? root.colorOverride : root.iconColor visible: root.isMaterial } @@ -56,7 +61,7 @@ Item { anchors.centerIn: parent text: root.unicodeChar font.pixelSize: root.iconSize * root.unicodeIconScale - color: root.iconColor + color: root.hasColorOverride ? root.colorOverride : root.iconColor visible: root.isUnicode } @@ -65,6 +70,10 @@ Item { source: root.svgSource size: root.iconSize cornerIcon: root.svgCornerIcon + colorOverride: root.colorOverride + brightnessOverride: root.brightnessOverride + contrastOverride: root.contrastOverride + saturationOverride: root.saturationOverride visible: root.isSvg || root.isSvgCorner }