import QtQuick import QtQuick.Effects import QtQuick.Layouts import Quickshell import Quickshell.Services.SystemTray import "." as M import "../services" as S M.BarModule { id: root spacing: S.Theme.moduleSpacing + 2 cursorShape: Qt.ArrowCursor // Workaround: Qt 6 compiled bindings break on `visible` when the expression // references Repeater.count on a Row-derived component. The binding silently // dies (imperative overwrite by the engine) and never re-evaluates. Routing // through a custom property forces the interpreted binding path, which works. property bool _shouldBeVisible: S.Modules.tray.enable && _trayRepeater.count > 0 visible: _shouldBeVisible required property var bar property var _activeMenu: null // --- debug logging (remove once tray is confirmed working) --- on_ShouldBeVisibleChanged: console.log("[TrayModule] _shouldBeVisible:", _shouldBeVisible, "actual visible:", visible) Component.onCompleted: console.log("[TrayModule] created, enable:", S.Modules.tray.enable, "repeater count:", _trayRepeater.count) onVisibleChanged: console.log("[TrayModule] visible:", visible, "enable:", S.Modules.tray.enable, "count:", _trayRepeater.count) Connections { target: _trayRepeater function onCountChanged() { console.log("[TrayModule] repeater count:", _trayRepeater.count, "visible:", root.visible, "shouldBe:", S.Modules.tray.enable && _trayRepeater.count > 0); } } Connections { target: SystemTray.items function onValuesChanged() { console.log("[TrayModule] model valuesChanged, values.length:", SystemTray.items.values.length, "repeater count:", _trayRepeater.count); } } Repeater { id: _trayRepeater model: SystemTray.items delegate: Item { id: iconItem required property SystemTrayItem modelData readonly property bool _needsAttention: modelData.status === 2 property bool _hovered: false property real _pulseOpacity: 1 implicitWidth: S.Theme.fontSize + 4 implicitHeight: S.Theme.fontSize + 4 M.PulseAnimation on _pulseOpacity { running: iconItem._needsAttention minOpacity: 0.3 } Item { anchors.fill: parent opacity: iconItem._pulseOpacity layer.enabled: iconItem._needsAttention || iconItem._hovered layer.effect: MultiEffect { shadowEnabled: true shadowColor: iconItem._needsAttention ? S.Theme.base08 : S.Theme.base05 shadowBlur: iconItem._needsAttention ? 0.8 : 0.5 shadowVerticalOffset: 0 shadowHorizontalOffset: 0 } M.ThemedIcon { anchors.fill: parent source: iconItem.modelData.icon tint: iconItem._needsAttention ? S.Theme.base08 : root.accentColor } } HoverHandler { onHoveredChanged: { iconItem._hovered = hovered; const tip = [iconItem.modelData.tooltipTitle, iconItem.modelData.tooltipDescription].filter(s => s).join("\n") || iconItem.modelData.title; if (hovered && tip) { M.TooltipState.text = tip; M.TooltipState.itemX = iconItem.mapToGlobal(iconItem.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0); M.TooltipState.screen = QsWindow.window?.screen ?? null; M.TooltipState.accentColor = root.accentColor; M.TooltipState.visible = true; } else if (!hovered) { M.TooltipState.visible = false; } } } MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: mouse => { if (mouse.button === Qt.LeftButton) { iconItem.modelData.activate(); } else if (mouse.button === Qt.RightButton) { if (iconItem.modelData.menu) { if (root._activeMenu && root._activeMenu !== menuLoader) root._activeMenu.active = false; menuLoader.active = true; M.TooltipState.visible = false; root._activeMenu = menuLoader; } else { iconItem.modelData.secondaryActivate(); } } } } // Per-icon context menu window, created on demand LazyLoader { id: menuLoader active: false M.TrayMenu { accentColor: root.accentColor handle: iconItem.modelData.menu screen: root.bar.screen anchorX: iconItem.mapToGlobal(iconItem.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0) onDismissed: { menuLoader.active = false; root._activeMenu = null; } } } } } }