From deb3fc0a12e3189597b582104cf5295ff3e89149 Mon Sep 17 00:00:00 2001 From: Damocles Date: Wed, 22 Apr 2026 23:43:59 +0200 Subject: [PATCH 1/4] readme: add lock.weather and lock.threatEffect options --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2e29645..3d21ada 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,8 @@ programs.nova-shell.modules = { lock.notifications = false; # hide notification icons on the lock screen lock.mpris = false; # hide media controls on the lock screen lock.volume = false; # hide volume slider on the lock screen + lock.weather = false; # hide weather summary on the lock screen + lock.threatEffect = false; # disable red vignette on wrong password # modules with extra config backlight.step = 2; # brightness adjustment % From aa0f7bb5ef3fe2af87546b1f26e5516e1364cba2 Mon Sep 17 00:00:00 2001 From: Damocles Date: Thu, 23 Apr 2026 00:48:07 +0200 Subject: [PATCH 2/4] extract PinnableSection base component from 12 bar modules --- shell/modules/BatteryModule.qml | 22 ++------------------ shell/modules/BluetoothModule.qml | 21 ++----------------- shell/modules/ClockModule.qml | 21 ++----------------- shell/modules/CpuModule.qml | 21 ++----------------- shell/modules/DiskModule.qml | 21 ++----------------- shell/modules/GpuModule.qml | 21 ++----------------- shell/modules/MemoryModule.qml | 21 ++----------------- shell/modules/MprisModule.qml | 21 ++----------------- shell/modules/NetworkModule.qml | 21 ++----------------- shell/modules/NotificationsModule.qml | 21 ++----------------- shell/modules/PinnableSection.qml | 30 +++++++++++++++++++++++++++ shell/modules/TemperatureModule.qml | 21 ++----------------- shell/modules/WeatherModule.qml | 21 ++----------------- shell/modules/qmldir | 1 + 14 files changed, 55 insertions(+), 229 deletions(-) create mode 100644 shell/modules/PinnableSection.qml diff --git a/shell/modules/BatteryModule.qml b/shell/modules/BatteryModule.qml index 7368682..9050f3e 100644 --- a/shell/modules/BatteryModule.qml +++ b/shell/modules/BatteryModule.qml @@ -4,12 +4,12 @@ import "." as M import "../services" as S import "../applets" as C -M.BarSection { +M.PinnableSection { id: root spacing: S.Theme.moduleSpacing opacity: S.Modules.battery.enable && S.BatteryService.available ? 1 : 0 visible: opacity > 0 - tooltip: "" + _panelHovered: hoverPanel.panelHovered property real _blinkOpacity: 1 @@ -34,24 +34,6 @@ M.BarSection { root._blinkOpacity = 1 } - // Panel state - property bool _pinned: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _pinned - - on_AnyHoverChanged: { - if (_anyHover) - _unpinTimer.stop(); - else if (_pinned) - _unpinTimer.start(); - } - - Timer { - id: _unpinTimer - interval: 500 - onTriggered: root._pinned = false - } - // Bar widgets M.BarIcon { icon: { diff --git a/shell/modules/BluetoothModule.qml b/shell/modules/BluetoothModule.qml index 6cf34e0..91b0bc5 100644 --- a/shell/modules/BluetoothModule.qml +++ b/shell/modules/BluetoothModule.qml @@ -4,29 +4,12 @@ import "." as M import "../services" as S import "../applets" as C -M.BarSection { +M.PinnableSection { id: root spacing: S.Theme.moduleSpacing opacity: S.Modules.bluetooth.enable && S.BluetoothService.state !== "unavailable" ? 1 : 0 visible: opacity > 0 - tooltip: "" - - property bool _pinned: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _pinned - - on_AnyHoverChanged: { - if (_anyHover) - _unpinTimer.stop(); - else if (_pinned) - _unpinTimer.start(); - } - - Timer { - id: _unpinTimer - interval: 500 - onTriggered: root._pinned = false - } + _panelHovered: hoverPanel.panelHovered M.BarIcon { icon: "\uF294" diff --git a/shell/modules/ClockModule.qml b/shell/modules/ClockModule.qml index fa06723..69e3663 100644 --- a/shell/modules/ClockModule.qml +++ b/shell/modules/ClockModule.qml @@ -4,33 +4,16 @@ import "." as M import "../services" as S import "../applets" as C -M.BarSection { +M.PinnableSection { id: root spacing: S.Theme.moduleSpacing - tooltip: "" + _panelHovered: hoverPanel.panelHovered SystemClock { id: clock precision: SystemClock.Seconds } - property bool _pinned: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _pinned - - on_AnyHoverChanged: { - if (_anyHover) - _unpinTimer.stop(); - else if (_pinned) - _unpinTimer.start(); - } - - Timer { - id: _unpinTimer - interval: 500 - onTriggered: root._pinned = false - } - M.BarLabel { font.pixelSize: S.Theme.fontSize + 1 label: Qt.formatDateTime(clock.date, "ddd, dd. MMM HH:mm") diff --git a/shell/modules/CpuModule.qml b/shell/modules/CpuModule.qml index 825be3f..16e5db6 100644 --- a/shell/modules/CpuModule.qml +++ b/shell/modules/CpuModule.qml @@ -4,19 +4,15 @@ import "." as M import "../services" as S import "../applets" as C -M.BarSection { +M.PinnableSection { id: root spacing: Math.max(1, S.Theme.moduleSpacing - 2) - tooltip: "" + _panelHovered: hoverPanel.panelHovered readonly property var _cores: S.SystemStats.cpuCores readonly property var _coreMaxFreq: S.SystemStats.cpuCoreMaxFreq readonly property var _coreTypes: S.SystemStats.cpuCoreTypes - property bool _pinned: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _pinned - property bool _coreConsumerActive: false on_ShowPanelChanged: { @@ -35,19 +31,6 @@ M.BarSection { onProcessesChanged: hoverPanel.keepOpen(300) } - on_AnyHoverChanged: { - if (_anyHover) - _unpinTimer.stop(); - else if (_pinned) - _unpinTimer.start(); - } - - Timer { - id: _unpinTimer - interval: 500 - onTriggered: root._pinned = false - } - M.BarIcon { icon: "\uF2DB" anchors.verticalCenter: parent.verticalCenter diff --git a/shell/modules/DiskModule.qml b/shell/modules/DiskModule.qml index 425ce38..616ed65 100644 --- a/shell/modules/DiskModule.qml +++ b/shell/modules/DiskModule.qml @@ -4,10 +4,10 @@ import "." as M import "../services" as S import "../applets" as C -M.BarSection { +M.PinnableSection { id: root spacing: Math.max(1, S.Theme.moduleSpacing - 2) - tooltip: "" + _panelHovered: hoverPanel.panelHovered property var _mounts: S.SystemStats.diskMounts property int _rootPct: S.SystemStats.diskRootPct @@ -19,23 +19,6 @@ M.BarSection { return false; } - property bool _pinned: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _pinned - - on_AnyHoverChanged: { - if (_anyHover) - _unpinTimer.stop(); - else if (_pinned) - _unpinTimer.start(); - } - - Timer { - id: _unpinTimer - interval: 500 - onTriggered: root._pinned = false - } - M.BarIcon { icon: "\uF0C9" color: root._anyWarn ? S.Theme.base09 : root.accentColor diff --git a/shell/modules/GpuModule.qml b/shell/modules/GpuModule.qml index 106463b..794ad55 100644 --- a/shell/modules/GpuModule.qml +++ b/shell/modules/GpuModule.qml @@ -4,28 +4,11 @@ import "." as M import "../services" as S import "../applets" as C -M.BarSection { +M.PinnableSection { id: root spacing: Math.max(1, S.Theme.moduleSpacing - 2) - tooltip: "" visible: S.Modules.gpu.enable && S.SystemStats.gpuAvailable - - property bool _pinned: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _pinned - - on_AnyHoverChanged: { - if (_anyHover) - _unpinTimer.stop(); - else if (_pinned) - _unpinTimer.start(); - } - - Timer { - id: _unpinTimer - interval: 500 - onTriggered: root._pinned = false - } + _panelHovered: hoverPanel.panelHovered M.BarIcon { icon: "\uEB4C" diff --git a/shell/modules/MemoryModule.qml b/shell/modules/MemoryModule.qml index 64f5eff..d1d54bc 100644 --- a/shell/modules/MemoryModule.qml +++ b/shell/modules/MemoryModule.qml @@ -4,10 +4,10 @@ import "." as M import "../services" as S import "../applets" as C -M.BarSection { +M.PinnableSection { id: root spacing: Math.max(1, S.Theme.moduleSpacing - 2) - tooltip: "" + _panelHovered: hoverPanel.panelHovered property int percent: S.SystemStats.memPercent property real usedGb: S.SystemStats.memUsedGb @@ -16,29 +16,12 @@ M.BarSection { property real cachedGb: S.SystemStats.memCachedGb property real buffersGb: S.SystemStats.memBuffersGb - property bool _pinned: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _pinned - property M.ProcessList _procs: M.ProcessList { sortBy: "mem" active: root._showPanel onProcessesChanged: hoverPanel.keepOpen(300) } - on_AnyHoverChanged: { - if (_anyHover) - _unpinTimer.stop(); - else if (_pinned) - _unpinTimer.start(); - } - - Timer { - id: _unpinTimer - interval: 500 - onTriggered: root._pinned = false - } - M.BarIcon { icon: "\uEFC5" anchors.verticalCenter: parent.verticalCenter diff --git a/shell/modules/MprisModule.qml b/shell/modules/MprisModule.qml index 0edbf0e..cd32262 100644 --- a/shell/modules/MprisModule.qml +++ b/shell/modules/MprisModule.qml @@ -6,12 +6,12 @@ import "." as M import "../services" as S import "../applets" as C -M.BarSection { +M.PinnableSection { id: root spacing: S.Theme.moduleSpacing opacity: S.Modules.mpris.enable && player !== null ? 1 : 0 visible: opacity > 0 - tooltip: "" + _panelHovered: hoverPanel.panelHovered readonly property var _players: S.MprisService.players readonly property MprisPlayer player: S.MprisService.player @@ -71,23 +71,6 @@ M.BarSection { required property var bar - property bool _pinned: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _pinned - - on_AnyHoverChanged: { - if (_anyHover) - _unpinTimer.stop(); - else if (_pinned) - _unpinTimer.start(); - } - - Timer { - id: _unpinTimer - interval: 500 - onTriggered: root._pinned = false - } - M.BarIcon { icon: root.playing ? "\uF04B" : (root.player?.playbackState === MprisPlaybackState.Paused ? "\uDB80\uDFE4" : "\uDB81\uDCDB") anchors.verticalCenter: parent.verticalCenter diff --git a/shell/modules/NetworkModule.qml b/shell/modules/NetworkModule.qml index 5624489..df827c2 100644 --- a/shell/modules/NetworkModule.qml +++ b/shell/modules/NetworkModule.qml @@ -4,30 +4,13 @@ import "." as M import "../services" as S import "../applets" as C -M.BarSection { +M.PinnableSection { id: root spacing: S.Theme.moduleSpacing - tooltip: "" + _panelHovered: hoverPanel.panelHovered readonly property string state: S.NetworkService.state - property bool _pinned: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _pinned - - on_AnyHoverChanged: { - if (_anyHover) - _unpinTimer.stop(); - else if (_pinned) - _unpinTimer.start(); - } - - Timer { - id: _unpinTimer - interval: 500 - onTriggered: root._pinned = false - } - M.BarIcon { icon: { if (root.state === "wifi") diff --git a/shell/modules/NotificationsModule.qml b/shell/modules/NotificationsModule.qml index 7df4b2b..1af6743 100644 --- a/shell/modules/NotificationsModule.qml +++ b/shell/modules/NotificationsModule.qml @@ -5,30 +5,13 @@ import "." as M import "../services" as S import "../applets" as C -M.BarSection { +M.PinnableSection { id: root spacing: S.Theme.moduleSpacing - tooltip: "" + _panelHovered: hoverPanel.panelHovered readonly property bool hasUrgent: S.NotifService.list.some(n => n.urgency === NotificationUrgency.Critical && n.state !== "dismissed") - property bool _pinned: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _pinned - - on_AnyHoverChanged: { - if (_anyHover) - _unpinTimer.stop(); - else if (_pinned) - _unpinTimer.start(); - } - - Timer { - id: _unpinTimer - interval: 500 - onTriggered: root._pinned = false - } - M.BarIcon { icon: { if (S.NotifService.dnd) diff --git a/shell/modules/PinnableSection.qml b/shell/modules/PinnableSection.qml new file mode 100644 index 0000000..5a4c1e3 --- /dev/null +++ b/shell/modules/PinnableSection.qml @@ -0,0 +1,30 @@ +import QtQuick +import Quickshell +import "." as M +import "../services" as S + +// Base component for bar modules with a pinnable hover panel. +// Provides the _pinned/_anyHover/_showPanel/_unpinTimer boilerplate. +// Modules bind _panelHovered to their HoverPanel's panelHovered property. +M.BarSection { + id: root + tooltip: "" + + property bool _pinned: false + property bool _panelHovered: false + readonly property bool _anyHover: root._hovered || _panelHovered + readonly property bool _showPanel: _anyHover || _pinned + + on_AnyHoverChanged: { + if (_anyHover) + _unpinTimer.stop(); + else if (_pinned) + _unpinTimer.start(); + } + + Timer { + id: _unpinTimer + interval: 500 + onTriggered: root._pinned = false + } +} diff --git a/shell/modules/TemperatureModule.qml b/shell/modules/TemperatureModule.qml index 89bd8e5..b352759 100644 --- a/shell/modules/TemperatureModule.qml +++ b/shell/modules/TemperatureModule.qml @@ -4,10 +4,10 @@ import "." as M import "../services" as S import "../applets" as C -M.BarSection { +M.PinnableSection { id: root spacing: Math.max(1, S.Theme.moduleSpacing - 2) - tooltip: "" + _panelHovered: hoverPanel.panelHovered readonly property int _warm: S.Modules.temperature.warm || 80 readonly property int _hot: S.Modules.temperature.hot || 90 @@ -30,23 +30,6 @@ M.BarSection { } } - property bool _pinned: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _pinned - - on_AnyHoverChanged: { - if (_anyHover) - _unpinTimer.stop(); - else if (_pinned) - _unpinTimer.start(); - } - - Timer { - id: _unpinTimer - interval: 500 - onTriggered: root._pinned = false - } - M.BarIcon { icon: "\uF2C9" color: root._stateColor diff --git a/shell/modules/WeatherModule.qml b/shell/modules/WeatherModule.qml index b9f7f02..b22b24c 100644 --- a/shell/modules/WeatherModule.qml +++ b/shell/modules/WeatherModule.qml @@ -4,28 +4,11 @@ import "." as M import "../services" as S import "../applets" as C -M.BarSection { +M.PinnableSection { id: root spacing: S.Theme.moduleSpacing - tooltip: "" visible: S.Modules.weather.enable && S.WeatherService.available - - property bool _pinned: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _pinned - - on_AnyHoverChanged: { - if (_anyHover) - _unpinTimer.stop(); - else if (_pinned) - _unpinTimer.start(); - } - - Timer { - id: _unpinTimer - interval: 500 - onTriggered: root._pinned = false - } + _panelHovered: hoverPanel.panelHovered M.BarIcon { icon: S.WeatherService.icon diff --git a/shell/modules/qmldir b/shell/modules/qmldir index 3a8eee3..8d09375 100644 --- a/shell/modules/qmldir +++ b/shell/modules/qmldir @@ -23,6 +23,7 @@ NotifCard 1.0 NotifCard.qml NotifPopup 1.0 NotifPopup.qml NotificationsModule 1.0 NotificationsModule.qml OverviewBackdrop 1.0 OverviewBackdrop.qml +PinnableSection 1.0 PinnableSection.qml PopupBackground 1.0 PopupBackground.qml PowerMenu 1.0 PowerMenu.qml PowerModule 1.0 PowerModule.qml From 5c5d3bbfac1914584d585ee756cf1c2d19b7f0aa Mon Sep 17 00:00:00 2001 From: Damocles Date: Thu, 23 Apr 2026 00:49:48 +0200 Subject: [PATCH 3/4] fix PinnableSection: drop self-referential module import for qmllint --- shell/modules/PinnableSection.qml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/shell/modules/PinnableSection.qml b/shell/modules/PinnableSection.qml index 5a4c1e3..12aff05 100644 --- a/shell/modules/PinnableSection.qml +++ b/shell/modules/PinnableSection.qml @@ -1,12 +1,9 @@ import QtQuick -import Quickshell -import "." as M -import "../services" as S // Base component for bar modules with a pinnable hover panel. // Provides the _pinned/_anyHover/_showPanel/_unpinTimer boilerplate. // Modules bind _panelHovered to their HoverPanel's panelHovered property. -M.BarSection { +BarSection { id: root tooltip: "" From 1db16b435c910754214696e46279dca7fb35ea04 Mon Sep 17 00:00:00 2001 From: Damocles Date: Thu, 23 Apr 2026 00:52:42 +0200 Subject: [PATCH 4/4] update qmllint baseline for new/renamed files --- test/qmllint-baseline.txt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/qmllint-baseline.txt b/test/qmllint-baseline.txt index d417ffc..7014c15 100644 --- a/test/qmllint-baseline.txt +++ b/test/qmllint-baseline.txt @@ -1,3 +1,5 @@ +shell/applets/BluetoothApplet.qml: Unqualified access [unqualified] +shell/applets/ClockApplet.qml: Unqualified access [unqualified] shell/applets/CpuApplet.qml: Member "_barColor" not found on type "QQuickItem" [missing-property] shell/applets/CpuApplet.qml: Member "_f" not found on type "QQuickItem" [missing-property] shell/applets/CpuApplet.qml: Member "_throttled" not found on type "QQuickItem" [missing-property] @@ -8,6 +10,10 @@ shell/applets/MemoryApplet.qml: Unqualified access [unqualified] shell/applets/MprisApplet.qml: Member "frac" not found on type "QQuickItem" [missing-property] shell/applets/MprisApplet.qml: Member "spacing" not found on type "Repeater" [missing-property] shell/applets/MprisApplet.qml: Unqualified access [unqualified] +shell/applets/NetworkApplet.qml: Unqualified access [unqualified] +shell/applets/NotifApplet.qml: Member "_notif" not found on type "QQuickItem" [missing-property] +shell/applets/NotifApplet.qml: Member "_type" not found on type "QQuickItem" [missing-property] +shell/applets/NotifApplet.qml: Unqualified access [unqualified] shell/applets/TemperatureApplet.qml: Unqualified access [unqualified] shell/applets/VolumeApplet.qml: Unqualified access [unqualified] shell/lock/Lock.qml: Unqualified access [unqualified] @@ -25,9 +31,9 @@ shell/modules/BarLabel.qml: Member "screen" not found on type "QObject" [missing shell/modules/BarSection.qml: Member "accentColor" not found on type "QQuickItem" [missing-property] shell/modules/BarSection.qml: Member "screen" not found on type "QObject" [missing-property] shell/modules/BatteryModule.qml: Member "screen" not found on type "QObject" [missing-property] -shell/modules/BluetoothMenu.qml: Unqualified access [unqualified] shell/modules/BluetoothModule.qml: Member "screen" not found on type "QObject" [missing-property] shell/modules/BluetoothModule.qml: Unqualified access [unqualified] +shell/modules/ClockModule.qml: Member "screen" not found on type "QObject" [missing-property] shell/modules/CpuModule.qml: Member "screen" not found on type "QObject" [missing-property] shell/modules/DiskModule.qml: Member "screen" not found on type "QObject" [missing-property] shell/modules/Flyout.qml: Could not find property "left". [missing-property] @@ -42,12 +48,9 @@ shell/modules/HoverPanel.qml: Type margins is used but it is not resolved [unres shell/modules/HoverPanel.qml: unknown grouped property scope margins. [unqualified] shell/modules/MemoryModule.qml: Member "screen" not found on type "QObject" [missing-property] shell/modules/MprisModule.qml: Member "screen" not found on type "QObject" [missing-property] -shell/modules/NetworkMenu.qml: Unqualified access [unqualified] shell/modules/NetworkModule.qml: Member "screen" not found on type "QObject" [missing-property] +shell/modules/NetworkModule.qml: Unqualified access [unqualified] shell/modules/NotifCard.qml: Unqualified access [unqualified] -shell/modules/NotifCenter.qml: Member "_notif" not found on type "QQuickItem" [missing-property] -shell/modules/NotifCenter.qml: Member "_type" not found on type "QQuickItem" [missing-property] -shell/modules/NotifCenter.qml: Unqualified access [unqualified] shell/modules/NotifPopup.qml: Could not find property "right". [missing-property] shell/modules/NotifPopup.qml: Could not find property "top". [missing-property] shell/modules/NotifPopup.qml: Type PanelWindow is not creatable. [uncreatable-type] @@ -71,9 +74,11 @@ shell/modules/TrayModule.qml: Member "screen" not found on type "QObject" [missi shell/modules/TrayModule.qml: Type "qs::dbus::dbusmenu::DBusMenuHandle" of property "menu" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] shell/modules/TrayModule.qml: Unqualified access [unqualified] shell/modules/VolumeModule.qml: Member "screen" not found on type "QObject" [missing-property] +shell/modules/WeatherModule.qml: Member "screen" not found on type "QObject" [missing-property] shell/modules/WindowTitleModule.qml: Unqualified access [unqualified] shell/modules/WorkspacesModule.qml: Member "screen" not found on type "QObject" [missing-property] shell/modules/WorkspacesModule.qml: Unqualified access [unqualified] +shell/services/BatteryService.qml: Unqualified access [unqualified] shell/services/BluetoothService.qml: Unqualified access [unqualified] shell/services/LockService.qml: Type QProcess::ExitStatus of parameter exitStatus in signal called exited was not found, but is required to compile onExited. Did you add all imports and dependencies? [signal-handler-parameters] shell/services/LockService.qml: Unqualified access [unqualified]