diff --git a/quickshell/Common/SessionData.qml b/quickshell/Common/SessionData.qml index 0667f381b..5fd95e0c4 100644 --- a/quickshell/Common/SessionData.qml +++ b/quickshell/Common/SessionData.qml @@ -14,7 +14,7 @@ Singleton { id: root readonly property var log: Log.scoped("SessionData") - readonly property int sessionConfigVersion: 4 + readonly property int sessionConfigVersion: 5 readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true" diff --git a/quickshell/Common/settings/SessionStore.js b/quickshell/Common/settings/SessionStore.js index 7d8c70f24..98b6a8b3b 100644 --- a/quickshell/Common/settings/SessionStore.js +++ b/quickshell/Common/settings/SessionStore.js @@ -83,5 +83,29 @@ function migrateToVersion(obj, targetVersion, settingsData) { session.configVersion = 4; } + if (currentVersion < 5) { + console.info("SessionData: Migrating session to version 5"); + console.info("SessionData: Tray keys now use a stable id (or id::instance) instead of id::tooltipTitle"); + + function stripTrayKeys(list) { + if (!list || list.constructor !== Array) return list; + var out = []; + for (var i = 0; i < list.length; i++) { + var raw = list[i]; + if (typeof raw !== "string") continue; + var key = raw.includes("::") ? raw.split("::")[0] : raw; + var hasDup = false; + for (var j = 0; j < out.length; j++) { + if (out[j] === key) { hasDup = true; break; } + } + if (key && !hasDup) out.push(key); + } + return out; + } + + if (session.hiddenTrayIds !== undefined) session.hiddenTrayIds = stripTrayKeys(session.hiddenTrayIds); + if (session.trayItemOrder !== undefined) session.trayItemOrder = stripTrayKeys(session.trayItemOrder); + session.configVersion = 5; + } return session; } diff --git a/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml b/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml index 6b1f8636a..97581352d 100644 --- a/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml +++ b/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml @@ -40,13 +40,33 @@ BasePill { return !hiddenTrayIds.includes(itemId.toLowerCase()); }); } + property var _trayKeyByItem: new Map() function getTrayItemKey(item) { const id = item?.id || ""; - const tooltipTitle = item?.tooltipTitle || ""; - if (!tooltipTitle || tooltipTitle === id) { - return id; + if (!id) + return ""; + if (root._trayKeyByItem.has(item)) + return root._trayKeyByItem.get(item); + const taken = new Set(); + for (const live of root.allTrayItems) { + if (live !== item && (live?.id || "") === id && root._trayKeyByItem.has(live)) + taken.add(root._trayKeyByItem.get(live)); } - return `${id}::${tooltipTitle}`; + let key = id; + for (let n = 1; taken.has(key); n++) + key = `${id}::${n}`; + root._trayKeyByItem.set(item, key); + return key; + } + + function resolveOrderIndex(key, orderMap) { + if (orderMap.has(key)) + return orderMap.get(key); + return -1; + } + + function isTrayIdHidden(key) { + return SessionData.isHiddenTrayId(key); } function trayIconSourceFor(trayItem) { @@ -150,6 +170,13 @@ BasePill { } } + Connections { + target: SystemTray + function onItemUnregistered(item) { + root._trayKeyByItem.delete(item); + } + } + function sortByPreferredOrder(items, trigger) { void trigger; const savedOrder = SessionData.trayItemOrder || []; @@ -159,15 +186,17 @@ BasePill { return [...items].sort((a, b) => { const keyA = getTrayItemKey(a); const keyB = getTrayItemKey(b); - const orderA = orderMap.has(keyA) ? orderMap.get(keyA) : 10000 + items.indexOf(a); - const orderB = orderMap.has(keyB) ? orderMap.get(keyB) : 10000 + items.indexOf(b); - return orderA - orderB; + const orderA = resolveOrderIndex(keyA, orderMap); + const orderB = resolveOrderIndex(keyB, orderMap); + const posA = orderA >= 0 ? orderA : 10000 + items.indexOf(a); + const posB = orderB >= 0 ? orderB : 10000 + items.indexOf(b); + return posA - posB; }); } readonly property var allSortedTrayItems: sortByPreferredOrder(allTrayItems, _trayOrderTrigger) readonly property var allSortedTrayItemKeys: allSortedTrayItems.map(item => getTrayItemKey(item)) - readonly property var visibleSortedTrayItems: allSortedTrayItems.filter(item => !SessionData.isHiddenTrayId(root.getTrayItemKey(item))) + readonly property var visibleSortedTrayItems: allSortedTrayItems.filter(item => !root.isTrayIdHidden(root.getTrayItemKey(item))) readonly property int automaticVisibleItemLimit: { if (!root.useAutomaticOverflow) return root.visibleSortedTrayItems.length; @@ -190,7 +219,7 @@ BasePill { item: item })) readonly property var autoOverflowBarItems: visibleSortedTrayItems.slice(automaticVisibleItemLimit) - readonly property var manualHiddenBarItems: allSortedTrayItems.filter(item => SessionData.isHiddenTrayId(root.getTrayItemKey(item))) + readonly property var manualHiddenBarItems: allSortedTrayItems.filter(item => root.isTrayIdHidden(root.getTrayItemKey(item))) readonly property var hiddenBarItemKeys: manualHiddenBarItems.concat(autoOverflowBarItems).map(item => root.getTrayItemKey(item)) readonly property var hiddenBarItems: allSortedTrayItems.filter(item => hiddenBarItemKeys.indexOf(root.getTrayItemKey(item)) !== -1) readonly property string trayIconTintMode: { @@ -298,7 +327,7 @@ BasePill { } function isManualHiddenTrayItem(item) { - return SessionData.isHiddenTrayId(getTrayItemKey(item)); + return root.isTrayIdHidden(root.getTrayItemKey(item)); } function isAutoOverflowTrayItem(item) {