From 7e594b7f8d2eedbe65c53e43beef22b58751ec0a Mon Sep 17 00:00:00 2001 From: Damocles Date: Sun, 26 Apr 2026 18:45:11 +0200 Subject: [PATCH] bar modules own active state, fix circular effectiveVisible deadlock in BarGroup --- shell/modules/BacklightModule.qml | 3 +- shell/modules/Bar.qml | 53 +++++++-------------------- shell/modules/BarGroup.qml | 13 ++++++- shell/modules/BarModule.qml | 4 ++ shell/modules/BatteryModule.qml | 3 +- shell/modules/BluetoothModule.qml | 3 +- shell/modules/ClockModule.qml | 1 + shell/modules/CpuModule.qml | 1 + shell/modules/DiskModule.qml | 1 + shell/modules/DockModule.qml | 1 + shell/modules/GpuModule.qml | 2 +- shell/modules/IdleInhibitorModule.qml | 1 + shell/modules/MemoryModule.qml | 1 + shell/modules/MprisModule.qml | 3 +- shell/modules/NetworkModule.qml | 1 + shell/modules/NotificationsModule.qml | 1 + shell/modules/PowerModule.qml | 1 + shell/modules/PowerProfileModule.qml | 1 + shell/modules/PrivacyModule.qml | 2 +- shell/modules/TemperatureModule.qml | 1 + shell/modules/TrayModule.qml | 24 ++---------- shell/modules/VolumeModule.qml | 1 + shell/modules/WeatherModule.qml | 2 +- shell/modules/WorkspacesModule.qml | 1 + 24 files changed, 52 insertions(+), 73 deletions(-) diff --git a/shell/modules/BacklightModule.qml b/shell/modules/BacklightModule.qml index 4f99b64..ec14bd0 100644 --- a/shell/modules/BacklightModule.qml +++ b/shell/modules/BacklightModule.qml @@ -7,8 +7,7 @@ import "../applets" as C M.BarModule { id: root spacing: S.Theme.moduleSpacing - opacity: S.Modules.backlight.enable && S.BacklightService.available ? 1 : 0 - visible: opacity > 0 + active: S.Modules.backlight.enable && S.BacklightService.available tooltip: "Brightness: " + percent + "%" panelNamespace: "nova-backlight" panelTitle: "Brightness" diff --git a/shell/modules/Bar.qml b/shell/modules/Bar.qml index 3ef5314..5ca2c26 100644 --- a/shell/modules/Bar.qml +++ b/shell/modules/Bar.qml @@ -120,12 +120,8 @@ PanelWindow { M.BarGroup { M.PrivacyModule {} - M.ClockModule { - visible: S.Modules.clock.enable - } - M.NotificationsModule { - visible: S.Modules.notifications.enable - } + M.ClockModule {} + M.NotificationsModule {} } } @@ -141,7 +137,6 @@ PanelWindow { leftEdge: true M.WorkspacesModule { bar: bar - visible: S.Modules.workspaces.enable } } M.BarGroup { @@ -182,60 +177,38 @@ PanelWindow { M.MprisModule { bar: bar } - M.VolumeModule { - visible: S.Modules.volume.enable - } + M.VolumeModule {} } // Connectivity M.BarGroup { - M.NetworkModule { - visible: S.Modules.network.enable - } + M.NetworkModule {} M.BluetoothModule {} } // Controls M.BarGroup { M.BacklightModule {} - M.PowerProfileModule { - visible: S.Modules.powerProfile.enable - } - M.IdleInhibitorModule { - visible: S.Modules.idleInhibitor.enable - } + M.PowerProfileModule {} + M.IdleInhibitorModule {} } // Stats M.BarGroup { - M.CpuModule { - visible: S.Modules.cpu.enable - } - M.MemoryModule { - visible: S.Modules.memory.enable - } + M.CpuModule {} + M.MemoryModule {} M.GpuModule {} - M.TemperatureModule { - visible: S.Modules.temperature.enable - } - M.WeatherModule { - visible: S.Modules.weather.enable - } - M.DiskModule { - visible: S.Modules.disk.enable - } + M.TemperatureModule {} + M.WeatherModule {} + M.DiskModule {} } // Power + Dock M.BarGroup { rightEdge: true M.BatteryModule {} - M.DockModule { - visible: S.Modules.dock.enable - } - M.PowerModule { - visible: S.Modules.power.enable - } + M.DockModule {} + M.PowerModule {} } } } diff --git a/shell/modules/BarGroup.qml b/shell/modules/BarGroup.qml index fffa807..544a8ae 100644 --- a/shell/modules/BarGroup.qml +++ b/shell/modules/BarGroup.qml @@ -27,7 +27,18 @@ Item { readonly property real _blr: S.Theme.radius readonly property real _brr: S.Theme.radius - visible: row.visibleChildren.length > 0 + // Check children's `active` instead of visibleChildren to avoid circular + // effectiveVisible dependency: if the BarGroup is invisible, children's + // effectiveVisible is always false, so visibleChildren stays empty and + // the group can never become visible again. `active` is a plain property + // on BarModule that is not affected by the parent visibility chain. + visible: { + for (let i = 0; i < row.children.length; i++) { + if (row.children[i].active) + return true; + } + return false; + } implicitWidth: row.implicitWidth + _pad * 2 implicitHeight: S.Theme.barHeight - 3 - _pad diff --git a/shell/modules/BarModule.qml b/shell/modules/BarModule.qml index 0da9b8e..d5e3a42 100644 --- a/shell/modules/BarModule.qml +++ b/shell/modules/BarModule.qml @@ -14,6 +14,10 @@ import "../services" as S // For content resize grace, call keepPanelOpen(ms). Row { id: root + property bool active: true + opacity: active ? 1 : 0 + visible: opacity > 0 + property string tooltip: "" property bool _hovered: false property color accentColor: parent?.accentColor ?? S.Theme.base05 diff --git a/shell/modules/BatteryModule.qml b/shell/modules/BatteryModule.qml index 57b66f7..7311f83 100644 --- a/shell/modules/BatteryModule.qml +++ b/shell/modules/BatteryModule.qml @@ -7,8 +7,7 @@ import "../applets" as C M.BarModule { id: root spacing: S.Theme.moduleSpacing - opacity: S.Modules.battery.enable && S.BatteryService.available ? 1 : 0 - visible: opacity > 0 + active: S.Modules.battery.enable && S.BatteryService.available tooltip: "Battery: " + Math.round(S.BatteryService.percent) + "%" + (S.BatteryService.charging ? " (charging)" : "") panelNamespace: "nova-battery" panelTitle: "Battery" diff --git a/shell/modules/BluetoothModule.qml b/shell/modules/BluetoothModule.qml index 5d0ea98..222e023 100644 --- a/shell/modules/BluetoothModule.qml +++ b/shell/modules/BluetoothModule.qml @@ -7,8 +7,7 @@ import "../applets" as C M.BarModule { id: root spacing: S.Theme.moduleSpacing - opacity: S.Modules.bluetooth.enable && S.BluetoothService.state !== "unavailable" ? 1 : 0 - visible: opacity > 0 + active: S.Modules.bluetooth.enable && S.BluetoothService.state !== "unavailable" tooltip: { if (S.BluetoothService.state === "connected") return "Bluetooth: " + S.BluetoothService.device; diff --git a/shell/modules/ClockModule.qml b/shell/modules/ClockModule.qml index 4bbd126..d8679a4 100644 --- a/shell/modules/ClockModule.qml +++ b/shell/modules/ClockModule.qml @@ -6,6 +6,7 @@ import "../applets" as C M.BarModule { id: root + active: S.Modules.clock.enable spacing: S.Theme.moduleSpacing tooltip: Qt.formatDateTime(clock.date, "dddd, dd. MMMM yyyy") panelNamespace: "nova-clock" diff --git a/shell/modules/CpuModule.qml b/shell/modules/CpuModule.qml index dcc3099..580862d 100644 --- a/shell/modules/CpuModule.qml +++ b/shell/modules/CpuModule.qml @@ -6,6 +6,7 @@ import "../applets" as C M.BarModule { id: root + active: S.Modules.cpu.enable spacing: Math.max(1, S.Theme.moduleSpacing - 2) tooltip: "CPU: " + S.SystemStats.cpuUsage + "% @ " + S.SystemStats.cpuFreqGhz.toFixed(2) + " GHz" panelNamespace: "nova-cpu" diff --git a/shell/modules/DiskModule.qml b/shell/modules/DiskModule.qml index d682e24..fb000e4 100644 --- a/shell/modules/DiskModule.qml +++ b/shell/modules/DiskModule.qml @@ -6,6 +6,7 @@ import "../applets" as C M.BarModule { id: root + active: S.Modules.disk.enable spacing: Math.max(1, S.Theme.moduleSpacing - 2) tooltip: "Disk: " + _rootPct + "% used" panelNamespace: "nova-disk" diff --git a/shell/modules/DockModule.qml b/shell/modules/DockModule.qml index f2db938..49fc28f 100644 --- a/shell/modules/DockModule.qml +++ b/shell/modules/DockModule.qml @@ -4,6 +4,7 @@ import "../services" as S M.BarModule { id: root + active: S.Modules.dock.enable tooltip: S.DockState.open ? "Close dock" : "Open dock" onTapped: S.DockState.toggle() diff --git a/shell/modules/GpuModule.qml b/shell/modules/GpuModule.qml index 69c5701..65028e4 100644 --- a/shell/modules/GpuModule.qml +++ b/shell/modules/GpuModule.qml @@ -7,7 +7,7 @@ import "../applets" as C M.BarModule { id: root spacing: Math.max(1, S.Theme.moduleSpacing - 2) - visible: S.Modules.gpu.enable && S.SystemStats.gpuAvailable + active: S.Modules.gpu.enable && S.SystemStats.gpuAvailable tooltip: "GPU: " + S.SystemStats.gpuUsage + "%" panelNamespace: "nova-gpu" panelTitle: "GPU" diff --git a/shell/modules/IdleInhibitorModule.qml b/shell/modules/IdleInhibitorModule.qml index e7f54e5..b8c9b45 100644 --- a/shell/modules/IdleInhibitorModule.qml +++ b/shell/modules/IdleInhibitorModule.qml @@ -5,6 +5,7 @@ import "../services" as S M.BarModule { id: root + active: S.Modules.idleInhibitor.enable tooltip: { const parts = ["Idle inhibition: " + (S.IdleInhibitService.active ? "active" : "inactive")]; if (S.IdleInhibitService.inhibitors) diff --git a/shell/modules/MemoryModule.qml b/shell/modules/MemoryModule.qml index 9a676d3..c3959f9 100644 --- a/shell/modules/MemoryModule.qml +++ b/shell/modules/MemoryModule.qml @@ -6,6 +6,7 @@ import "../applets" as C M.BarModule { id: root + active: S.Modules.memory.enable spacing: Math.max(1, S.Theme.moduleSpacing - 2) tooltip: "Memory: " + usedGb.toFixed(1) + " / " + totalGb.toFixed(1) + " GB" panelNamespace: "nova-memory" diff --git a/shell/modules/MprisModule.qml b/shell/modules/MprisModule.qml index 92c42a2..dcd5553 100644 --- a/shell/modules/MprisModule.qml +++ b/shell/modules/MprisModule.qml @@ -9,8 +9,7 @@ import "../applets" as C M.BarModule { id: root spacing: S.Theme.moduleSpacing - opacity: S.Modules.mpris.enable && S.MprisService.player !== null ? 1 : 0 - visible: opacity > 0 + active: S.Modules.mpris.enable && S.MprisService.player !== null tooltip: S.MprisService.player ? (S.MprisService.player.trackTitle || S.MprisService.player.identity || "Media") + (S.MprisService.playing ? " (playing)" : " (paused)") : "Media" panelNamespace: "nova-mpris" panelTitle: "Now Playing" diff --git a/shell/modules/NetworkModule.qml b/shell/modules/NetworkModule.qml index 2aa9dac..b3b9d1c 100644 --- a/shell/modules/NetworkModule.qml +++ b/shell/modules/NetworkModule.qml @@ -6,6 +6,7 @@ import "../applets" as C M.BarModule { id: root + active: S.Modules.network.enable spacing: S.Theme.moduleSpacing tooltip: { if (state === "wifi") diff --git a/shell/modules/NotificationsModule.qml b/shell/modules/NotificationsModule.qml index 8b7502f..c625c6b 100644 --- a/shell/modules/NotificationsModule.qml +++ b/shell/modules/NotificationsModule.qml @@ -7,6 +7,7 @@ import "../applets" as C M.BarModule { id: root + active: S.Modules.notifications.enable spacing: S.Theme.moduleSpacing tooltip: S.NotifService.count > 0 ? "Notifications: " + S.NotifService.count + (S.NotifService.dnd ? " (DND)" : "") : (S.NotifService.dnd ? "Do not disturb" : "No notifications") panelNamespace: "nova-notifications" diff --git a/shell/modules/PowerModule.qml b/shell/modules/PowerModule.qml index b763b37..11b0294 100644 --- a/shell/modules/PowerModule.qml +++ b/shell/modules/PowerModule.qml @@ -7,6 +7,7 @@ import "../applets" as C M.BarModule { id: root + active: S.Modules.power.enable tooltip: "Power menu" panelNamespace: "nova-power" panelTitle: "Power" diff --git a/shell/modules/PowerProfileModule.qml b/shell/modules/PowerProfileModule.qml index d2e31af..b2d359e 100644 --- a/shell/modules/PowerProfileModule.qml +++ b/shell/modules/PowerProfileModule.qml @@ -4,6 +4,7 @@ import "../services" as S M.BarModule { id: root + active: S.Modules.powerProfile.enable tooltip: "Power profile: " + (S.PowerProfileService.profile || "unknown") onTapped: { const cycle = ["performance", "balanced", "power-saver"]; diff --git a/shell/modules/PrivacyModule.qml b/shell/modules/PrivacyModule.qml index fd4adaa..a0b703c 100644 --- a/shell/modules/PrivacyModule.qml +++ b/shell/modules/PrivacyModule.qml @@ -35,7 +35,7 @@ M.BarModule { return false; } - visible: S.Modules.privacy.enable && (root._videoCapture || root._audioIn) + active: S.Modules.privacy.enable && (root._videoCapture || root._audioIn) // Screenshare indicator Text { diff --git a/shell/modules/TemperatureModule.qml b/shell/modules/TemperatureModule.qml index 338aab6..657c64b 100644 --- a/shell/modules/TemperatureModule.qml +++ b/shell/modules/TemperatureModule.qml @@ -6,6 +6,7 @@ import "../applets" as C M.BarModule { id: root + active: S.Modules.temperature.enable spacing: Math.max(1, S.Theme.moduleSpacing - 2) tooltip: "Temperature: " + _temp + "\u00B0C" panelNamespace: "nova-temperature" diff --git a/shell/modules/TrayModule.qml b/shell/modules/TrayModule.qml index 492c82c..85cddf4 100644 --- a/shell/modules/TrayModule.qml +++ b/shell/modules/TrayModule.qml @@ -12,32 +12,14 @@ M.BarModule { 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 + active: S.Modules.tray.enable && _trayRepeater.count > 0 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); - } - } + onActiveChanged: console.log("[TrayModule] active:", active, "count:", _trayRepeater.count) + onVisibleChanged: console.log("[TrayModule] visible:", visible) Repeater { id: _trayRepeater diff --git a/shell/modules/VolumeModule.qml b/shell/modules/VolumeModule.qml index 678eca4..6a16e4f 100644 --- a/shell/modules/VolumeModule.qml +++ b/shell/modules/VolumeModule.qml @@ -7,6 +7,7 @@ import "../applets" as C M.BarModule { id: root + active: S.Modules.volume.enable spacing: S.Theme.moduleSpacing tooltip: "Volume: " + Math.round(volume * 100) + "%" + (muted ? " (muted)" : "") panelNamespace: "nova-volume" diff --git a/shell/modules/WeatherModule.qml b/shell/modules/WeatherModule.qml index 2325d21..94c0187 100644 --- a/shell/modules/WeatherModule.qml +++ b/shell/modules/WeatherModule.qml @@ -7,7 +7,7 @@ import "../applets" as C M.BarModule { id: root spacing: S.Theme.moduleSpacing - visible: S.Modules.weather.enable && S.WeatherService.available + active: S.Modules.weather.enable && S.WeatherService.available tooltip: "Weather" panelNamespace: "nova-weather" panelTitle: "Weather" diff --git a/shell/modules/WorkspacesModule.qml b/shell/modules/WorkspacesModule.qml index 03aac54..978460e 100644 --- a/shell/modules/WorkspacesModule.qml +++ b/shell/modules/WorkspacesModule.qml @@ -6,6 +6,7 @@ import "../services" as S M.BarModule { id: root + active: S.Modules.workspaces.enable spacing: 4 cursorShape: Qt.ArrowCursor