From cc02d09c4da0154cbb214950f2ce07d39abcdbcc Mon Sep 17 00:00:00 2001 From: bbedward Date: Mon, 10 Nov 2025 12:26:14 -0500 Subject: [PATCH] dock: track hyprland addresses, fix closing, use ScriptModel --- Modules/Dock/DockAppButton.qml | 64 +------- Modules/Dock/DockApps.qml | 253 +++++++++++++++---------------- Modules/Dock/DockContextMenu.qml | 23 +-- 3 files changed, 135 insertions(+), 205 deletions(-) diff --git a/Modules/Dock/DockAppButton.qml b/Modules/Dock/DockAppButton.qml index 7b2d5275..5677306c 100644 --- a/Modules/Dock/DockAppButton.qml +++ b/Modules/Dock/DockAppButton.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Controls import Quickshell import Quickshell.Wayland +import Quickshell.Hyprland import Quickshell.Widgets import qs.Common import qs.Services @@ -91,56 +92,11 @@ Item { } function getToplevelObject() { - if (!appData) { - return null - } - - const sortedToplevels = CompositorService.sortedToplevels - if (!sortedToplevels) { - return null - } - - if (appData.type === "window") { - if (appData.uniqueId) { - for (var i = 0; i < sortedToplevels.length; i++) { - const toplevel = sortedToplevels[i] - const checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i - if (checkId === appData.uniqueId) { - return toplevel - } - } - } - - if (appData.windowId !== undefined && appData.windowId !== null && appData.windowId >= 0) { - if (appData.windowId < sortedToplevels.length) { - return sortedToplevels[appData.windowId] - } - } - } else if (appData.type === "grouped") { - if (appData.windowId !== undefined && appData.windowId !== null && appData.windowId >= 0) { - if (appData.windowId < sortedToplevels.length) { - return sortedToplevels[appData.windowId] - } - } - } - - return null + return appData?.toplevel || null } function getGroupedToplevels() { - if (!appData || appData.type !== "grouped") { - return [] - } - - const toplevels = [] - const allToplevels = ToplevelManager.toplevels.values - for (let i = 0; i < allToplevels.length; i++) { - const toplevel = allToplevels[i] - if (toplevel.appId === appData.appId) { - toplevels.push(toplevel) - } - } - return toplevels + return appData?.allWindows?.map(w => w.toplevel).filter(t => t !== null) || [] } onIsHoveredChanged: { if (mouseArea.pressed) return @@ -325,17 +281,9 @@ Item { } } } else if (mouse.button === Qt.MiddleButton) { - if (appData && appData.type === "window") { - const sortedToplevels = CompositorService.sortedToplevels - for (var i = 0; i < sortedToplevels.length; i++) { - const toplevel = sortedToplevels[i] - const checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i - if (checkId === appData.uniqueId) { - toplevel.close() - break - } - } - } else if (appData && appData.type === "grouped") { + if (appData?.type === "window") { + appData?.toplevel?.close() + } else if (appData?.type === "grouped") { if (contextMenu) { contextMenu.showForButton(root, appData, root.height, false, cachedDesktopEntry, parentDockScreen) } diff --git a/Modules/Dock/DockApps.qml b/Modules/Dock/DockApps.qml index f2b6e843..102dfaaf 100644 --- a/Modules/Dock/DockApps.qml +++ b/Modules/Dock/DockApps.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Controls import Quickshell import Quickshell.Wayland +import Quickshell.Hyprland import qs.Common import qs.Services import qs.Widgets @@ -54,158 +55,149 @@ Item { Repeater { id: repeater - model: ListModel { - id: dockModel - Component.onCompleted: updateModel() + property var dockItems: [] - function updateModel() { - clear() + model: ScriptModel { + values: repeater.dockItems + objectProp: "uniqueKey" + } - const items = [] - const pinnedApps = [...(SessionData.pinnedApps || [])] - const sortedToplevels = CompositorService.sortedToplevels + Component.onCompleted: updateModel() - if (root.groupByApp) { - // Group windows by appId - const appGroups = new Map() + function updateModel() { + const items = [] + const pinnedApps = [...(SessionData.pinnedApps || [])] + const sortedToplevels = CompositorService.sortedToplevels - // Add pinned apps first (even if they have no windows) - pinnedApps.forEach(appId => { + if (root.groupByApp) { + const appGroups = new Map() + + pinnedApps.forEach(appId => { + appGroups.set(appId, { + appId: appId, + isPinned: true, + windows: [] + }) + }) + + sortedToplevels.forEach((toplevel, index) => { + const appId = toplevel.appId || "unknown" + if (!appGroups.has(appId)) { appGroups.set(appId, { appId: appId, - isPinned: true, + isPinned: false, windows: [] }) - }) - - // Group all running windows by appId - sortedToplevels.forEach((toplevel, index) => { - const appId = toplevel.appId || "unknown" - if (!appGroups.has(appId)) { - appGroups.set(appId, { - appId: appId, - isPinned: false, - windows: [] - }) - } - const title = toplevel.title || "(Unnamed)" - const truncatedTitle = title.length > 50 ? title.substring(0, 47) + "..." : title - const uniqueId = toplevel.title + "|" + (toplevel.appId || "") + "|" + index - - appGroups.get(appId).windows.push({ - windowId: index, - windowTitle: truncatedTitle, - uniqueId: uniqueId - }) - }) - - // Sort groups: pinned first, then unpinned - const pinnedGroups = [] - const unpinnedGroups = [] - - Array.from(appGroups.entries()).forEach(([appId, group]) => { - // For grouped apps, just show the first window info but track all windows - const firstWindow = group.windows.length > 0 ? group.windows[0] : null - - const item = { - "type": "grouped", - "appId": appId, - "windowId": firstWindow ? firstWindow.windowId : -1, - "windowTitle": firstWindow ? firstWindow.windowTitle : "", - "workspaceId": -1, - "isPinned": group.isPinned, - "isRunning": group.windows.length > 0, - "windowCount": group.windows.length, - "uniqueId": firstWindow ? firstWindow.uniqueId : "", - "allWindows": group.windows - } - - if (group.isPinned) { - pinnedGroups.push(item) - } else { - unpinnedGroups.push(item) - } - }) - - // Add items in order - pinnedGroups.forEach(item => items.push(item)) - - // Add separator if needed - if (pinnedGroups.length > 0 && unpinnedGroups.length > 0) { - items.push({ - "type": "separator", - "appId": "__SEPARATOR__", - "windowId": -1, - "windowTitle": "", - "workspaceId": -1, - "isPinned": false, - "isRunning": false - }) } - unpinnedGroups.forEach(item => items.push(item)) - root.pinnedAppCount = pinnedGroups.length - } else { - pinnedApps.forEach(appId => { - items.push({ - "type": "pinned", - "appId": appId, - "windowId": -1, - "windowTitle": "", - "workspaceId": -1, - "isPinned": true, - "isRunning": false - }) + appGroups.get(appId).windows.push({ + toplevel: toplevel, + index: index }) + }) - root.pinnedAppCount = pinnedApps.length + const pinnedGroups = [] + const unpinnedGroups = [] - if (pinnedApps.length > 0 && sortedToplevels.length > 0) { - items.push({ - "type": "separator", - "appId": "__SEPARATOR__", - "windowId": -1, - "windowTitle": "", - "workspaceId": -1, - "isPinned": false, - "isRunning": false, - "isFocused": false - }) + Array.from(appGroups.entries()).forEach(([appId, group]) => { + const firstWindow = group.windows.length > 0 ? group.windows[0] : null + + const item = { + uniqueKey: "grouped_" + appId, + type: "grouped", + appId: appId, + toplevel: firstWindow ? firstWindow.toplevel : null, + isPinned: group.isPinned, + isRunning: group.windows.length > 0, + windowCount: group.windows.length, + allWindows: group.windows } - sortedToplevels.forEach((toplevel, index) => { - const title = toplevel.title || "(Unnamed)" - const truncatedTitle = title.length > 50 ? title.substring(0, 47) + "..." : title - const uniqueId = toplevel.title + "|" + (toplevel.appId || "") + "|" + index + if (group.isPinned) { + pinnedGroups.push(item) + } else { + unpinnedGroups.push(item) + } + }) - items.push({ - "type": "window", - "appId": toplevel.appId, - "windowId": index, - "windowTitle": truncatedTitle, - "workspaceId": -1, - "isPinned": false, - "isRunning": true, - "uniqueId": uniqueId - }) + pinnedGroups.forEach(item => items.push(item)) + + if (pinnedGroups.length > 0 && unpinnedGroups.length > 0) { + items.push({ + uniqueKey: "separator_grouped", + type: "separator", + appId: "__SEPARATOR__", + toplevel: null, + isPinned: false, + isRunning: false }) } - items.forEach(item => append(item)) + unpinnedGroups.forEach(item => items.push(item)) + root.pinnedAppCount = pinnedGroups.length + } else { + pinnedApps.forEach(appId => { + items.push({ + uniqueKey: "pinned_" + appId, + type: "pinned", + appId: appId, + toplevel: null, + isPinned: true, + isRunning: false + }) + }) + + root.pinnedAppCount = pinnedApps.length + + if (pinnedApps.length > 0 && sortedToplevels.length > 0) { + items.push({ + uniqueKey: "separator_ungrouped", + type: "separator", + appId: "__SEPARATOR__", + toplevel: null, + isPinned: false, + isRunning: false + }) + } + + sortedToplevels.forEach((toplevel, index) => { + let uniqueKey = "window_" + index + if (CompositorService.isHyprland && Hyprland.toplevels) { + const hyprlandToplevels = Array.from(Hyprland.toplevels.values) + for (let i = 0; i < hyprlandToplevels.length; i++) { + if (hyprlandToplevels[i].wayland === toplevel) { + uniqueKey = "window_" + hyprlandToplevels[i].address + break + } + } + } + + items.push({ + uniqueKey: uniqueKey, + type: "window", + appId: toplevel.appId, + toplevel: toplevel, + isPinned: false, + isRunning: true + }) + }) } + + dockItems = items } delegate: Item { id: delegateItem property alias dockButton: button + property var itemData: modelData clip: false - width: model.type === "separator" ? (root.isVertical ? root.iconSize : 8) : (root.isVertical ? root.iconSize : root.iconSize * 1.2) - height: model.type === "separator" ? (root.isVertical ? 8 : root.iconSize) : (root.isVertical ? root.iconSize * 1.2 : root.iconSize) + width: itemData.type === "separator" ? (root.isVertical ? root.iconSize : 8) : (root.isVertical ? root.iconSize : root.iconSize * 1.2) + height: itemData.type === "separator" ? (root.isVertical ? 8 : root.iconSize) : (root.isVertical ? root.iconSize * 1.2 : root.iconSize) Rectangle { - visible: model.type === "separator" + visible: itemData.type === "separator" width: root.isVertical ? root.iconSize * 0.5 : 2 height: root.isVertical ? 2 : root.iconSize * 0.5 color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) @@ -215,21 +207,24 @@ Item { DockAppButton { id: button - visible: model.type !== "separator" + visible: itemData.type !== "separator" anchors.centerIn: parent width: delegateItem.width height: delegateItem.height actualIconSize: root.iconSize - appData: model + appData: itemData contextMenu: root.contextMenu dockApps: root index: model.index parentDockScreen: root.dockScreen - showWindowTitle: model.type === "window" || model.type === "grouped" - windowTitle: model.windowTitle || "" + showWindowTitle: itemData?.type === "window" || itemData?.type === "grouped" + windowTitle: { + const title = itemData?.toplevel?.title || "(Unnamed)" + return title.length > 50 ? title.substring(0, 47) + "..." : title + } } } } @@ -239,18 +234,18 @@ Item { Connections { target: CompositorService function onToplevelsChanged() { - dockModel.updateModel() + repeater.updateModel() } } Connections { target: SessionData function onPinnedAppsChanged() { - dockModel.updateModel() + repeater.updateModel() } } onGroupByAppChanged: { - dockModel.updateModel() + repeater.updateModel() } } diff --git a/Modules/Dock/DockContextMenu.qml b/Modules/Dock/DockContextMenu.qml index 4f87af27..c9c7a725 100644 --- a/Modules/Dock/DockContextMenu.qml +++ b/Modules/Dock/DockContextMenu.qml @@ -1,6 +1,7 @@ import QtQuick import Quickshell import Quickshell.Wayland +import Quickshell.Hyprland import Quickshell.Widgets import qs.Common import qs.Services @@ -475,24 +476,10 @@ PanelWindow { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - const sortedToplevels = CompositorService.sortedToplevels - if (root.appData && root.appData.type === "window") { - for (var i = 0; i < sortedToplevels.length; i++) { - const toplevel = sortedToplevels[i] - const checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i - if (checkId === root.appData.uniqueId) { - toplevel.close() - break - } - } - } else if (root.appData && root.appData.type === "grouped") { - const allToplevels = ToplevelManager.toplevels.values - for (let i = 0; i < allToplevels.length; i++) { - const toplevel = allToplevels[i] - if (toplevel.appId === root.appData.appId) { - toplevel.close() - } - } + if (root.appData?.type === "window") { + root.appData?.toplevel?.close() + } else if (root.appData?.type === "grouped") { + root.appData?.allWindows?.forEach(window => window.toplevel?.close()) } root.close() }