1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-27 23:12:49 -05:00

dock: track hyprland addresses, fix closing, use ScriptModel

This commit is contained in:
bbedward
2025-11-10 12:26:14 -05:00
parent af95631a1d
commit cc02d09c4d
3 changed files with 135 additions and 205 deletions

View File

@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Widgets import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
@@ -91,56 +92,11 @@ Item {
} }
function getToplevelObject() { function getToplevelObject() {
if (!appData) { return appData?.toplevel || null
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
} }
function getGroupedToplevels() { function getGroupedToplevels() {
if (!appData || appData.type !== "grouped") { return appData?.allWindows?.map(w => w.toplevel).filter(t => t !== null) || []
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
} }
onIsHoveredChanged: { onIsHoveredChanged: {
if (mouseArea.pressed) return if (mouseArea.pressed) return
@@ -325,17 +281,9 @@ Item {
} }
} }
} else if (mouse.button === Qt.MiddleButton) { } else if (mouse.button === Qt.MiddleButton) {
if (appData && appData.type === "window") { if (appData?.type === "window") {
const sortedToplevels = CompositorService.sortedToplevels appData?.toplevel?.close()
for (var i = 0; i < sortedToplevels.length; i++) { } else if (appData?.type === "grouped") {
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 (contextMenu) { if (contextMenu) {
contextMenu.showForButton(root, appData, root.height, false, cachedDesktopEntry, parentDockScreen) contextMenu.showForButton(root, appData, root.height, false, cachedDesktopEntry, parentDockScreen)
} }

View File

@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -54,158 +55,149 @@ Item {
Repeater { Repeater {
id: repeater id: repeater
model: ListModel {
id: dockModel
Component.onCompleted: updateModel() property var dockItems: []
function updateModel() { model: ScriptModel {
clear() values: repeater.dockItems
objectProp: "uniqueKey"
}
const items = [] Component.onCompleted: updateModel()
const pinnedApps = [...(SessionData.pinnedApps || [])]
const sortedToplevels = CompositorService.sortedToplevels
if (root.groupByApp) { function updateModel() {
// Group windows by appId const items = []
const appGroups = new Map() const pinnedApps = [...(SessionData.pinnedApps || [])]
const sortedToplevels = CompositorService.sortedToplevels
// Add pinned apps first (even if they have no windows) if (root.groupByApp) {
pinnedApps.forEach(appId => { 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, { appGroups.set(appId, {
appId: appId, appId: appId,
isPinned: true, isPinned: false,
windows: [] 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)) appGroups.get(appId).windows.push({
root.pinnedAppCount = pinnedGroups.length toplevel: toplevel,
} else { index: index
pinnedApps.forEach(appId => {
items.push({
"type": "pinned",
"appId": appId,
"windowId": -1,
"windowTitle": "",
"workspaceId": -1,
"isPinned": true,
"isRunning": false
})
}) })
})
root.pinnedAppCount = pinnedApps.length const pinnedGroups = []
const unpinnedGroups = []
if (pinnedApps.length > 0 && sortedToplevels.length > 0) { Array.from(appGroups.entries()).forEach(([appId, group]) => {
items.push({ const firstWindow = group.windows.length > 0 ? group.windows[0] : null
"type": "separator",
"appId": "__SEPARATOR__", const item = {
"windowId": -1, uniqueKey: "grouped_" + appId,
"windowTitle": "", type: "grouped",
"workspaceId": -1, appId: appId,
"isPinned": false, toplevel: firstWindow ? firstWindow.toplevel : null,
"isRunning": false, isPinned: group.isPinned,
"isFocused": false isRunning: group.windows.length > 0,
}) windowCount: group.windows.length,
allWindows: group.windows
} }
sortedToplevels.forEach((toplevel, index) => { if (group.isPinned) {
const title = toplevel.title || "(Unnamed)" pinnedGroups.push(item)
const truncatedTitle = title.length > 50 ? title.substring(0, 47) + "..." : title } else {
const uniqueId = toplevel.title + "|" + (toplevel.appId || "") + "|" + index unpinnedGroups.push(item)
}
})
items.push({ pinnedGroups.forEach(item => items.push(item))
"type": "window",
"appId": toplevel.appId, if (pinnedGroups.length > 0 && unpinnedGroups.length > 0) {
"windowId": index, items.push({
"windowTitle": truncatedTitle, uniqueKey: "separator_grouped",
"workspaceId": -1, type: "separator",
"isPinned": false, appId: "__SEPARATOR__",
"isRunning": true, toplevel: null,
"uniqueId": uniqueId 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 { delegate: Item {
id: delegateItem id: delegateItem
property alias dockButton: button property alias dockButton: button
property var itemData: modelData
clip: false clip: false
width: model.type === "separator" ? (root.isVertical ? root.iconSize : 8) : (root.isVertical ? root.iconSize : root.iconSize * 1.2) width: itemData.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) height: itemData.type === "separator" ? (root.isVertical ? 8 : root.iconSize) : (root.isVertical ? root.iconSize * 1.2 : root.iconSize)
Rectangle { Rectangle {
visible: model.type === "separator" visible: itemData.type === "separator"
width: root.isVertical ? root.iconSize * 0.5 : 2 width: root.isVertical ? root.iconSize * 0.5 : 2
height: root.isVertical ? 2 : root.iconSize * 0.5 height: root.isVertical ? 2 : root.iconSize * 0.5
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
@@ -215,21 +207,24 @@ Item {
DockAppButton { DockAppButton {
id: button id: button
visible: model.type !== "separator" visible: itemData.type !== "separator"
anchors.centerIn: parent anchors.centerIn: parent
width: delegateItem.width width: delegateItem.width
height: delegateItem.height height: delegateItem.height
actualIconSize: root.iconSize actualIconSize: root.iconSize
appData: model appData: itemData
contextMenu: root.contextMenu contextMenu: root.contextMenu
dockApps: root dockApps: root
index: model.index index: model.index
parentDockScreen: root.dockScreen parentDockScreen: root.dockScreen
showWindowTitle: model.type === "window" || model.type === "grouped" showWindowTitle: itemData?.type === "window" || itemData?.type === "grouped"
windowTitle: model.windowTitle || "" windowTitle: {
const title = itemData?.toplevel?.title || "(Unnamed)"
return title.length > 50 ? title.substring(0, 47) + "..." : title
}
} }
} }
} }
@@ -239,18 +234,18 @@ Item {
Connections { Connections {
target: CompositorService target: CompositorService
function onToplevelsChanged() { function onToplevelsChanged() {
dockModel.updateModel() repeater.updateModel()
} }
} }
Connections { Connections {
target: SessionData target: SessionData
function onPinnedAppsChanged() { function onPinnedAppsChanged() {
dockModel.updateModel() repeater.updateModel()
} }
} }
onGroupByAppChanged: { onGroupByAppChanged: {
dockModel.updateModel() repeater.updateModel()
} }
} }

View File

@@ -1,6 +1,7 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Widgets import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
@@ -475,24 +476,10 @@ PanelWindow {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
const sortedToplevels = CompositorService.sortedToplevels if (root.appData?.type === "window") {
if (root.appData && root.appData.type === "window") { root.appData?.toplevel?.close()
for (var i = 0; i < sortedToplevels.length; i++) { } else if (root.appData?.type === "grouped") {
const toplevel = sortedToplevels[i] root.appData?.allWindows?.forEach(window => window.toplevel?.close())
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()
}
}
} }
root.close() root.close()
} }