add WeatherService, WeatherApplet with hover panel and lock screen widget

This commit is contained in:
Damocles 2026-04-22 22:06:58 +02:00
parent 6f385130ff
commit 9285365732
8 changed files with 150 additions and 26 deletions

View file

@ -121,6 +121,11 @@ in
default = true; default = true;
description = "Show volume slider on the lock screen."; 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" { notifications = moduleOpt "notifications" {
timeout = lib.mkOption { timeout = lib.mkOption {

View file

@ -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
}
}

View file

@ -11,4 +11,5 @@ MprisApplet 1.0 MprisApplet.qml
NetworkApplet 1.0 NetworkApplet.qml NetworkApplet 1.0 NetworkApplet.qml
TemperatureApplet 1.0 TemperatureApplet.qml TemperatureApplet 1.0 TemperatureApplet.qml
VolumeApplet 1.0 VolumeApplet.qml VolumeApplet 1.0 VolumeApplet.qml
WeatherApplet 1.0 WeatherApplet.qml
# keep-sorted end # keep-sorted end

View file

@ -27,13 +27,34 @@ Item {
} }
implicitHeight: _widgetContent.implicitHeight implicitHeight: _widgetContent.implicitHeight
visible: _mprisCard.visible || _volumeCard.visible || _backlightCard.visible || _notifPills.visible visible: _weatherCard.visible || _mprisCard.visible || _volumeCard.visible || _backlightCard.visible || _notifPills.visible
Column { Column {
id: _widgetContent id: _widgetContent
width: parent.width width: parent.width
spacing: 12 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 // Notification pills
LockNotifPills { LockNotifPills {
id: _notifPills id: _notifPills

View file

@ -1,41 +1,53 @@
import QtQuick import QtQuick
import Quickshell.Io import Quickshell
import "." as M import "." as M
import "../services" as S import "../services" as S
import "../applets" as C
M.BarSection { M.BarSection {
id: root id: root
spacing: S.Theme.moduleSpacing 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 { on_AnyHoverChanged: {
id: proc if (_anyHover)
running: true _unpinTimer.stop();
command: ["wttrbar"].concat(S.Modules.weather.args) else if (_pinned)
stdout: StdioCollector { _unpinTimer.start();
onStreamFinished: {
try {
const data = JSON.parse(text);
label.icon = data.text ?? "";
root.weatherTooltip = data.tooltip ?? "";
} catch (e) {
label.icon = "";
root.weatherTooltip = "";
}
}
}
} }
Timer { Timer {
interval: S.Modules.weather.interval || 3600000 id: _unpinTimer
running: true interval: 500
repeat: true onTriggered: root._pinned = false
onTriggered: proc.running = true
} }
M.BarIcon { M.BarIcon {
id: label icon: S.WeatherService.icon
anchors.verticalCenter: parent.verticalCenter 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
}
} }
} }

View file

@ -98,7 +98,8 @@ QtObject {
screenshot: true, screenshot: true,
notifications: true, notifications: true,
mpris: true, mpris: true,
volume: true volume: true,
weather: true
}) })
property var statsDaemon: ({ property var statsDaemon: ({
interval: -1 interval: -1

View file

@ -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;
}
}

View file

@ -15,4 +15,5 @@ singleton PowerProfileService 1.0 PowerProfileService.qml
singleton ScreenshotService 1.0 ScreenshotService.qml singleton ScreenshotService 1.0 ScreenshotService.qml
singleton SystemStats 1.0 SystemStats.qml singleton SystemStats 1.0 SystemStats.qml
singleton Theme 1.0 Theme.qml singleton Theme 1.0 Theme.qml
singleton WeatherService 1.0 WeatherService.qml
# keep-sorted end # keep-sorted end