import QtQuick import Quickshell.Services.SystemTray Item { id: root property string selectedItemId: "" property string expandedSubmenu: "" property bool closeRequested: false property string closeReason: "" property var closePropagation: ({ "requested": false, "source": "", "reason": "" }) property var lastAction: ({}) readonly property var rowKinds: ["action", "separator", "submenu", "check", "radio", "disabled"] readonly property var nativeItems: SystemTray.items.values readonly property int attentionCount: itemsArray().filter(item => { return item.status === "NeedsAttention"; }).length function activeRole() { return "ui.focus"; } function attentionRole() { return "info"; } function borderRole(attention, active) { if (attention) return attentionRole(); if (active) return activeRole(); return "ui.accent"; } function fillRole(active) { return active ? activeRole() : "transparent"; } function enumName(value, fallback) { const text = String(value === undefined || value === null ? "" : value); if (text.indexOf("NeedsAttention") >= 0) return "NeedsAttention"; if (text.indexOf("Active") >= 0) return "Active"; if (text.indexOf("Passive") >= 0) return "Passive"; if (text.indexOf("Hardware") >= 0) return "Hardware"; if (text.indexOf("SystemServices") >= 0) return "SystemServices"; if (text.indexOf("Communications") >= 0) return "Communications"; if (text.indexOf("ApplicationStatus") >= 0) return "ApplicationStatus"; return fallback; } function itemKey(item, index) { const id = String(item && item.id ? item.id : ""); const title = String(item && item.title ? item.title : (item && item.tooltipTitle ? item.tooltipTitle : "")); return id.length > 0 ? id : (title.length > 0 ? title : `tray-${index}`); } function shapeItem(item, index) { const id = itemKey(item, index); return { "id": id, "index": index, "title": String(item && item.title ? item.title : (item && item.tooltipTitle ? item.tooltipTitle : id)), "tooltipTitle": String(item && item.tooltipTitle ? item.tooltipTitle : ""), "tooltipDescription": String(item && item.tooltipDescription ? item.tooltipDescription : ""), "icon": String(item && item.icon ? item.icon : ""), "status": enumName(item ? item.status : null, "Passive"), "category": enumName(item ? item.category : null, "ApplicationStatus"), "hasMenu": !!(item && item.hasMenu), "onlyMenu": !!(item && item.onlyMenu), "nativeRoutes": ["activate", "secondaryActivate", "scroll", "display"], "backend": "SystemTrayItem" }; } function itemsArray() { const result = []; for (let i = 0; i < nativeItems.length; i++) result.push(shapeItem(nativeItems[i], i)); return result; } function nativeItemById(id) { const needle = String(id); for (let i = 0; i < nativeItems.length; i++) if (itemKey(nativeItems[i], i) === needle) { return nativeItems[i]; } return null; } function menuFor(id) { const item = nativeItemById(id); return item && item.hasMenu ? item.menu : null; } function selectedItem() { const items = itemsArray(); if (items.length === 0) return null; if (selectedItemId.length === 0) return items[0]; for (let i = 0; i < items.length; i++) if (items[i].id === selectedItemId) { return items[i]; } return items[0]; } function menuRows() { const item = selectedItem(); if (!item || !item.hasMenu) return []; return [ { "id": `${item.id}:native-menu`, "kind": "submenu", "label": `${item.title} menu`, "enabled": true, "expanded": expandedSubmenu === `${item.id}:native-menu`, "native": true, "children": [] } ]; } function rowById(id) { const rows = menuRows(); const needle = String(id); for (let i = 0; i < rows.length; i++) if (rows[i].id === needle) { return rows[i]; } return ({ "id": needle, "kind": "missing", "label": "", "enabled": false, "native": false }); } function trayPanelContract() { return { "source": "Quickshell.Services.SystemTray SystemTray.items.values plus native StatusNotifierItem routes", "ownedBy": "PopoutHost.qml inline TrayPanel", "nativeTrayMenuPanel": true, "realSystemTray": true, "trayItemGrid": true, "dbusMenuRows": true, "noSyntheticDbusMenuRows": true, "submenuCollapsePolicy": true, "menuClosePropagation": true, "attentionStatus": true, "serviceActionsOnly": true, "nativeRoutes": ["activate", "secondaryActivate", "scroll", "display"], "rowKinds": rowKinds }; } function stateObject() { const rows = menuRows(); const items = itemsArray(); return { "backend": "Tray.qml", "source": "Quickshell.Services.SystemTray", "usesNativeSystemTray": true, "usesDBusMenu": true, "helperProcesses": [], "fakeRows": false, "nativeMenuHost": "bar-popout-host", "nativeItemCount": items.length, "attentionCount": attentionCount, "selectedItemId": selectedItemId, "items": items, "trayPanelContract": trayPanelContract(), "oneExpandedSubmenu": true, "expandedSubmenu": expandedSubmenu, "siblingsCollapsed": rows.filter(row => { return row.kind === "submenu" && row.expanded; }).length <= 1, "rowKinds": rowKinds, "rows": rows, "closeRequested": closeRequested, "closeReason": closeReason, "closePropagation": closePropagation, "lastAction": lastAction }; } function stateJson() { return JSON.stringify(stateObject()); } function selectItem(id) { const item = nativeItemById(id); selectedItemId = item ? String(id) : ""; expandedSubmenu = ""; lastAction = { "kind": "select-item", "id": String(id), "accepted": !!item }; return stateJson(); } function expandSubmenu(id) { const row = rowById(id); if (row.kind !== "submenu" || !row.enabled) { lastAction = { "kind": "expand-submenu", "id": String(id), "accepted": false }; return stateJson(); } expandedSubmenu = row.id; closeRequested = false; closeReason = ""; closePropagation = { "requested": false, "source": row.id, "reason": "submenu-expanded" }; lastAction = { "kind": "expand-submenu", "id": row.id, "accepted": true, "native": true }; return stateJson(); } function displayMenu(id, parentWindow, x, y) { const item = nativeItemById(id); if (!item || !item.hasMenu || !parentWindow) return stateJson(); item.display(parentWindow, Math.round(x), Math.round(y)); lastAction = { "kind": "display-menu", "id": String(id), "accepted": true, "native": true }; return stateJson(); } function primaryActivate(id, parentWindow, x, y) { const item = nativeItemById(id); if (item && item.onlyMenu) return displayMenu(id, parentWindow, x, y); return activateRow(id); } function secondaryActivate(id) { const item = nativeItemById(id); if (item) { item.secondaryActivate(); lastAction = { "kind": "secondary-activate", "id": String(id), "accepted": true, "native": true }; } return stateJson(); } function activateRow(id) { const item = nativeItemById(id); if (item) { item.activate(); closeRequested = true; closeReason = "item-activated"; closePropagation = { "requested": true, "source": String(id), "reason": closeReason }; lastAction = { "kind": "activate-item", "id": String(id), "accepted": true, "native": true }; return stateJson(); } const row = rowById(id); if (row.kind === "submenu") return expandSubmenu(row.id); closeRequested = false; closePropagation = { "requested": false, "source": row.id, "reason": row.kind }; lastAction = { "kind": "activate-row", "id": row.id, "rowKind": row.kind, "accepted": false, "native": false }; return stateJson(); } function closeMenu(reason) { expandedSubmenu = ""; closeRequested = true; closeReason = String(reason || "explicit"); closePropagation = { "requested": true, "source": "menu", "reason": closeReason }; lastAction = { "kind": "close-menu", "reason": closeReason }; return stateJson(); } function widgetState(attention, active) { return JSON.stringify({ "attention": attention, "active": active, "attentionRole": attentionRole(), "activeRole": activeRole(), "borderRole": borderRole(attention, active), "fillRole": fillRole(active), "menu": stateObject() }); } }