From 92853657323daa63c55612135dd7487e3c506fa2 Mon Sep 17 00:00:00 2001 From: Damocles Date: Wed, 22 Apr 2026 22:06:58 +0200 Subject: [PATCH] add WeatherService, WeatherApplet with hover panel and lock screen widget --- nix/hm-module.nix | 5 +++ shell/applets/WeatherApplet.qml | 41 +++++++++++++++++++++ shell/applets/qmldir | 1 + shell/lock/LockWidgets.qml | 23 +++++++++++- shell/modules/WeatherModule.qml | 60 ++++++++++++++++++------------- shell/services/Modules.qml | 3 +- shell/services/WeatherService.qml | 42 ++++++++++++++++++++++ shell/services/qmldir | 1 + 8 files changed, 150 insertions(+), 26 deletions(-) create mode 100644 shell/applets/WeatherApplet.qml create mode 100644 shell/services/WeatherService.qml diff --git a/nix/hm-module.nix b/nix/hm-module.nix index c64c8ac..8480617 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -121,6 +121,11 @@ in default = true; description = "Show volume slider on the lock screen."; }; + weather = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Show weather summary on the lock screen."; + }; }; notifications = moduleOpt "notifications" { timeout = lib.mkOption { diff --git a/shell/applets/WeatherApplet.qml b/shell/applets/WeatherApplet.qml new file mode 100644 index 0000000..0421bf5 --- /dev/null +++ b/shell/applets/WeatherApplet.qml @@ -0,0 +1,41 @@ +import QtQuick +import "../services" as S + +Column { + id: root + + required property color accentColor + + // Weather icon + summary + Item { + width: parent.width + height: 28 + + Text { + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.verticalCenter: parent.verticalCenter + text: S.WeatherService.icon + color: root.accentColor + font.pixelSize: S.Theme.fontSize + 2 + font.family: S.Theme.fontFamily + } + } + + // Forecast details from wttrbar tooltip + Text { + width: parent.width - 24 + anchors.horizontalCenter: parent.horizontalCenter + text: S.WeatherService.tooltip + color: S.Theme.base05 + font.pixelSize: S.Theme.fontSize - 2 + font.family: S.Theme.fontFamily + wrapMode: Text.WordWrap + lineHeight: 1.3 + } + + Item { + width: 1 + height: 4 + } +} diff --git a/shell/applets/qmldir b/shell/applets/qmldir index 1cfc65a..13113c1 100644 --- a/shell/applets/qmldir +++ b/shell/applets/qmldir @@ -11,4 +11,5 @@ MprisApplet 1.0 MprisApplet.qml NetworkApplet 1.0 NetworkApplet.qml TemperatureApplet 1.0 TemperatureApplet.qml VolumeApplet 1.0 VolumeApplet.qml +WeatherApplet 1.0 WeatherApplet.qml # keep-sorted end diff --git a/shell/lock/LockWidgets.qml b/shell/lock/LockWidgets.qml index 90f105b..40e157b 100644 --- a/shell/lock/LockWidgets.qml +++ b/shell/lock/LockWidgets.qml @@ -27,13 +27,34 @@ Item { } implicitHeight: _widgetContent.implicitHeight - visible: _mprisCard.visible || _volumeCard.visible || _backlightCard.visible || _notifPills.visible + visible: _weatherCard.visible || _mprisCard.visible || _volumeCard.visible || _backlightCard.visible || _notifPills.visible Column { id: _widgetContent width: parent.width spacing: 12 + // Weather widget + Rectangle { + id: _weatherCard + width: parent.width + height: _weatherContent.implicitHeight + 16 + radius: S.Theme.radius + 2 + color: Qt.rgba(S.Theme.base01.r, S.Theme.base01.g, S.Theme.base01.b, 0.7) + border.color: Qt.rgba(S.Theme.base03.r, S.Theme.base03.g, S.Theme.base03.b, 0.3) + border.width: 1 + visible: (S.Modules.lock.weather ?? true) && S.WeatherService.available + + C.WeatherApplet { + id: _weatherContent + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.topMargin: 8 + accentColor: S.Theme.base0C + } + } + // Notification pills LockNotifPills { id: _notifPills diff --git a/shell/modules/WeatherModule.qml b/shell/modules/WeatherModule.qml index a1bff49..b9f7f02 100644 --- a/shell/modules/WeatherModule.qml +++ b/shell/modules/WeatherModule.qml @@ -1,41 +1,53 @@ import QtQuick -import Quickshell.Io +import Quickshell import "." as M import "../services" as S +import "../applets" as C M.BarSection { id: root spacing: S.Theme.moduleSpacing - tooltip: root.weatherTooltip + tooltip: "" + visible: S.Modules.weather.enable && S.WeatherService.available - property string weatherTooltip: "" + property bool _pinned: false + readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered + readonly property bool _showPanel: _anyHover || _pinned - Process { - id: proc - running: true - command: ["wttrbar"].concat(S.Modules.weather.args) - stdout: StdioCollector { - onStreamFinished: { - try { - const data = JSON.parse(text); - label.icon = data.text ?? ""; - root.weatherTooltip = data.tooltip ?? ""; - } catch (e) { - label.icon = ""; - root.weatherTooltip = ""; - } - } - } + on_AnyHoverChanged: { + if (_anyHover) + _unpinTimer.stop(); + else if (_pinned) + _unpinTimer.start(); } + Timer { - interval: S.Modules.weather.interval || 3600000 - running: true - repeat: true - onTriggered: proc.running = true + id: _unpinTimer + interval: 500 + onTriggered: root._pinned = false } M.BarIcon { - id: label + icon: S.WeatherService.icon anchors.verticalCenter: parent.verticalCenter + TapHandler { + onTapped: root._pinned = !root._pinned + } + } + + M.HoverPanel { + id: hoverPanel + showPanel: root._showPanel + screen: QsWindow.window?.screen ?? null + anchorItem: root + accentColor: root.accentColor + panelNamespace: "nova-weather" + panelTitle: "Weather" + contentWidth: 280 + + C.WeatherApplet { + width: hoverPanel.contentWidth + accentColor: root.accentColor + } } } diff --git a/shell/services/Modules.qml b/shell/services/Modules.qml index d7d112d..f9ea948 100644 --- a/shell/services/Modules.qml +++ b/shell/services/Modules.qml @@ -98,7 +98,8 @@ QtObject { screenshot: true, notifications: true, mpris: true, - volume: true + volume: true, + weather: true }) property var statsDaemon: ({ interval: -1 diff --git a/shell/services/WeatherService.qml b/shell/services/WeatherService.qml new file mode 100644 index 0000000..ed89fac --- /dev/null +++ b/shell/services/WeatherService.qml @@ -0,0 +1,42 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import "." as S + +QtObject { + id: root + + property string icon: "" + property string tooltip: "" + readonly property bool available: icon !== "" + + property Process _proc: Process { + running: S.Modules.weather.enable + command: ["wttrbar"].concat(S.Modules.weather.args) + stdout: StdioCollector { + onStreamFinished: { + try { + const data = JSON.parse(text); + root.icon = data.text ?? ""; + root.tooltip = data.tooltip ?? ""; + } catch (e) { + root.icon = ""; + root.tooltip = ""; + } + } + } + } + + property Timer _poll: Timer { + interval: S.Modules.weather.interval || 3600000 + running: S.Modules.weather.enable + repeat: true + onTriggered: root._proc.running = true + } + + function refresh() { + _proc.running = true; + } +} diff --git a/shell/services/qmldir b/shell/services/qmldir index 339d03e..901fd4e 100644 --- a/shell/services/qmldir +++ b/shell/services/qmldir @@ -15,4 +15,5 @@ singleton PowerProfileService 1.0 PowerProfileService.qml singleton ScreenshotService 1.0 ScreenshotService.qml singleton SystemStats 1.0 SystemStats.qml singleton Theme 1.0 Theme.qml +singleton WeatherService 1.0 WeatherService.qml # keep-sorted end