mirror of
https://github.com/samjage/weather-and-stats.git
synced 2026-06-06 00:20:42 +00:00
Initial commit: Weather && Stats KDE Plasma 6 panel widget
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
import QtQuick
|
||||||
|
import org.kde.plasma.plasmoid
|
||||||
|
import org.kde.plasma.configuration
|
||||||
|
|
||||||
|
ConfigModel {
|
||||||
|
ConfigCategory {
|
||||||
|
name: "General"
|
||||||
|
icon: "weather-clear"
|
||||||
|
source: "configGeneral.qml"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
|
||||||
|
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
|
||||||
|
<kcfgfile name=""/>
|
||||||
|
<group name="General">
|
||||||
|
<entry name="latitude" type="Double">
|
||||||
|
<default>39.84</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="longitude" type="Double">
|
||||||
|
<default>-82.81</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="locationName" type="String">
|
||||||
|
<default>Canal Winchester, OH</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="useFahrenheit" type="Bool">
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="showCondition" type="Bool">
|
||||||
|
<default>false</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="weatherRefresh" type="Int">
|
||||||
|
<default>5</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="cpuTempFahrenheit" type="Bool">
|
||||||
|
<default>false</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="cpuTempThreshold" type="Int">
|
||||||
|
<default>80</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="statsRefresh" type="Int">
|
||||||
|
<default>3</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="showCpuTemp" type="Bool">
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="showCpuUsage" type="Bool">
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="showMemory" type="Bool">
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="showNetwork" type="Bool">
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
</group>
|
||||||
|
</kcfg>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# CPU temp via hwmon (same source as btop)
|
||||||
|
# Looks for coretemp (Intel), k10temp/zenpower (AMD), cpu-thermal (ARM)
|
||||||
|
CTEMP=-1
|
||||||
|
for hwmon in /sys/class/hwmon/hwmon*/; do
|
||||||
|
name=$(cat "${hwmon}name" 2>/dev/null)
|
||||||
|
if [[ "$name" == "coretemp" || "$name" == "k10temp" || "$name" == "zenpower" || "$name" == "cpu-thermal" ]]; then
|
||||||
|
# Prefer Package/Tctl label, otherwise take temp1
|
||||||
|
for label_file in "${hwmon}"temp*_label; do
|
||||||
|
label=$(cat "$label_file" 2>/dev/null)
|
||||||
|
if [[ "$label" == "Package"* || "$label" == "Tctl" || "$label" == "Tccd"* ]]; then
|
||||||
|
val=$(cat "${label_file/_label/_input}" 2>/dev/null)
|
||||||
|
if [ -n "$val" ] && [ "$val" -gt 0 ]; then CTEMP=$(( val / 1000 )); break 2; fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
val=$(cat "${hwmon}temp1_input" 2>/dev/null)
|
||||||
|
if [ -n "$val" ] && [ "$val" -gt 0 ]; then CTEMP=$(( val / 1000 )); break; fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
# Fallback: highest thermal_zone reading
|
||||||
|
if [ "$CTEMP" -lt 0 ]; then
|
||||||
|
best=$(cat /sys/class/thermal/thermal_zone*/temp 2>/dev/null | sort -rn | head -1)
|
||||||
|
[ -n "$best" ] && CTEMP=$(( best / 1000 ))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Memory usage %
|
||||||
|
MEM=$(free | awk '/Mem:/{print int($3/$2*100)}')
|
||||||
|
|
||||||
|
# Auto-detect most active network interface (excluding loopback)
|
||||||
|
IFACE=$(awk 'NR>2{gsub(/:$/,"",$1); if($1!="lo") print $1, ($2+0)+($10+0)}' /proc/net/dev \
|
||||||
|
| sort -k2 -rn | awk 'NR==1{print $1}')
|
||||||
|
[ -z "$IFACE" ] && IFACE="lo"
|
||||||
|
|
||||||
|
# Helper functions for sampling
|
||||||
|
get_cpu() { awk 'NR==1{t=0; for(i=2;i<=NF;i++) t+=$i; print t, $6}' /proc/stat; }
|
||||||
|
get_net() { awk -v d="$IFACE:" '$1==d{print $2, $10}' /proc/net/dev; }
|
||||||
|
|
||||||
|
# First samples
|
||||||
|
read T1 I1 < <(get_cpu)
|
||||||
|
read R1 X1 < <(get_net)
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Second samples
|
||||||
|
read T2 I2 < <(get_cpu)
|
||||||
|
read R2 X2 < <(get_net)
|
||||||
|
|
||||||
|
# CPU usage %
|
||||||
|
DT=$(( T2 - T1 ))
|
||||||
|
CPU=$(( DT > 0 ? (100 * (DT - (I2 - I1))) / DT : 0 ))
|
||||||
|
|
||||||
|
# Network KB/s
|
||||||
|
DOWN=$(( (R2 - R1) / 1024 ))
|
||||||
|
UP=$(( (X2 - X1) / 1024 ))
|
||||||
|
|
||||||
|
echo "$CTEMP $CPU $MEM $DOWN $UP"
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
implicitHeight: col.implicitHeight
|
||||||
|
|
||||||
|
property double cfg_latitude: 0
|
||||||
|
property double cfg_longitude: 0
|
||||||
|
property string cfg_locationName: ""
|
||||||
|
property bool cfg_useFahrenheit: true
|
||||||
|
property bool cfg_showCondition: false
|
||||||
|
property int cfg_weatherRefresh: 5
|
||||||
|
property bool cfg_cpuTempFahrenheit: false
|
||||||
|
property int cfg_cpuTempThreshold: 80
|
||||||
|
property int cfg_statsRefresh: 3
|
||||||
|
property bool cfg_showCpuTemp: true
|
||||||
|
property bool cfg_showCpuUsage: true
|
||||||
|
property bool cfg_showMemory: true
|
||||||
|
property bool cfg_showNetwork: true
|
||||||
|
|
||||||
|
component SectionHeader: ColumnLayout {
|
||||||
|
property string title: ""
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 8
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
text: title
|
||||||
|
font.bold: true
|
||||||
|
font.pointSize: 9
|
||||||
|
font.letterSpacing: 2
|
||||||
|
opacity: 0.5
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 1
|
||||||
|
opacity: 0.15
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: col
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 20
|
||||||
|
anchors.topMargin: 16
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
// ── WEATHER ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
SectionHeader { title: "WEATHER" }
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
text: cfg_locationName ? "📍 " + cfg_locationName : "No location set"
|
||||||
|
opacity: 0.7
|
||||||
|
font.italic: !cfg_locationName
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
QQC2.TextField {
|
||||||
|
id: searchField
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: "Search for a city..."
|
||||||
|
onAccepted: doSearch()
|
||||||
|
}
|
||||||
|
QQC2.Button {
|
||||||
|
text: "Search"
|
||||||
|
onClicked: doSearch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: resultsList
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Math.min(resultsModel.count * 40, 160)
|
||||||
|
visible: resultsModel.count > 0
|
||||||
|
clip: true
|
||||||
|
model: ListModel { id: resultsModel }
|
||||||
|
delegate: QQC2.ItemDelegate {
|
||||||
|
width: resultsList.width
|
||||||
|
text: model.name
|
||||||
|
onClicked: {
|
||||||
|
cfg_latitude = model.lat
|
||||||
|
cfg_longitude = model.lon
|
||||||
|
cfg_locationName = model.name
|
||||||
|
resultsModel.clear()
|
||||||
|
searchField.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
QQC2.Label { text: "Temperature:" }
|
||||||
|
QQC2.RadioButton {
|
||||||
|
text: "°F"
|
||||||
|
checked: cfg_useFahrenheit
|
||||||
|
onToggled: if (checked) cfg_useFahrenheit = true
|
||||||
|
}
|
||||||
|
QQC2.RadioButton {
|
||||||
|
text: "°C"
|
||||||
|
checked: !cfg_useFahrenheit
|
||||||
|
onToggled: if (checked) cfg_useFahrenheit = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
QQC2.Label { text: "Refresh every" }
|
||||||
|
QQC2.SpinBox {
|
||||||
|
value: cfg_weatherRefresh
|
||||||
|
from: 1; to: 60
|
||||||
|
onValueChanged: cfg_weatherRefresh = value
|
||||||
|
}
|
||||||
|
QQC2.Label { text: "min" }
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.CheckBox {
|
||||||
|
text: "Show condition text (e.g. \"Partly Cloudy\")"
|
||||||
|
checked: cfg_showCondition
|
||||||
|
onToggled: cfg_showCondition = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── STATS ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
SectionHeader { title: "STATS" }
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
QQC2.Label { text: "CPU Temp:" }
|
||||||
|
QQC2.RadioButton {
|
||||||
|
text: "°F"
|
||||||
|
checked: cfg_cpuTempFahrenheit
|
||||||
|
onToggled: if (checked) cfg_cpuTempFahrenheit = true
|
||||||
|
}
|
||||||
|
QQC2.RadioButton {
|
||||||
|
text: "°C"
|
||||||
|
checked: !cfg_cpuTempFahrenheit
|
||||||
|
onToggled: if (checked) cfg_cpuTempFahrenheit = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
QQC2.Label { text: "Refresh every" }
|
||||||
|
QQC2.SpinBox {
|
||||||
|
value: cfg_statsRefresh
|
||||||
|
from: 2; to: 60
|
||||||
|
onValueChanged: cfg_statsRefresh = value
|
||||||
|
}
|
||||||
|
QQC2.Label { text: "sec" }
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
QQC2.Label { text: "Alert when CPU temp exceeds:" }
|
||||||
|
QQC2.SpinBox {
|
||||||
|
from: cfg_cpuTempFahrenheit ? 122 : 50
|
||||||
|
to: cfg_cpuTempFahrenheit ? 230 : 110
|
||||||
|
value: cfg_cpuTempFahrenheit ? Math.round(cfg_cpuTempThreshold * 9/5 + 32) : cfg_cpuTempThreshold
|
||||||
|
onValueChanged: cfg_cpuTempThreshold = cfg_cpuTempFahrenheit ? Math.round((value - 32) * 5/9) : value
|
||||||
|
}
|
||||||
|
QQC2.Label { text: cfg_cpuTempFahrenheit ? "°F" : "°C" }
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
text: "Visible stats:"
|
||||||
|
opacity: 0.7
|
||||||
|
Layout.topMargin: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
columns: 2
|
||||||
|
Layout.fillWidth: true
|
||||||
|
columnSpacing: 32
|
||||||
|
rowSpacing: 2
|
||||||
|
|
||||||
|
QQC2.CheckBox {
|
||||||
|
text: "CPU Temperature"
|
||||||
|
checked: cfg_showCpuTemp
|
||||||
|
onToggled: cfg_showCpuTemp = checked
|
||||||
|
}
|
||||||
|
QQC2.CheckBox {
|
||||||
|
text: "CPU Usage"
|
||||||
|
checked: cfg_showCpuUsage
|
||||||
|
onToggled: cfg_showCpuUsage = checked
|
||||||
|
}
|
||||||
|
QQC2.CheckBox {
|
||||||
|
text: "Memory Usage"
|
||||||
|
checked: cfg_showMemory
|
||||||
|
onToggled: cfg_showMemory = checked
|
||||||
|
}
|
||||||
|
QQC2.CheckBox {
|
||||||
|
text: "Network Speed"
|
||||||
|
checked: cfg_showNetwork
|
||||||
|
onToggled: cfg_showNetwork = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillHeight: true; Layout.minimumHeight: 16 }
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSearch() {
|
||||||
|
var query = searchField.text.trim()
|
||||||
|
if (query.length < 2) return
|
||||||
|
var url = "https://geocoding-api.open-meteo.com/v1/search"
|
||||||
|
+ "?name=" + encodeURIComponent(query)
|
||||||
|
+ "&count=8&language=en&format=json"
|
||||||
|
var req = new XMLHttpRequest()
|
||||||
|
req.open("GET", url)
|
||||||
|
req.onreadystatechange = function() {
|
||||||
|
if (req.readyState === XMLHttpRequest.DONE && req.status === 200) {
|
||||||
|
var data = JSON.parse(req.responseText)
|
||||||
|
resultsModel.clear()
|
||||||
|
if (data.results) {
|
||||||
|
data.results.forEach(function(r) {
|
||||||
|
var label = r.name
|
||||||
|
if (r.admin1) label += ", " + r.admin1
|
||||||
|
if (r.country) label += ", " + r.country
|
||||||
|
resultsModel.append({ name: label, lat: r.latitude, lon: r.longitude })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import org.kde.plasma.plasmoid
|
||||||
|
import org.kde.plasma.plasma5support as P5Support
|
||||||
|
import org.kde.plasma.components as PlasmaComponents
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
|
PlasmoidItem {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// ── Config bindings ───────────────────────────────────────────────────────
|
||||||
|
property double lat: Plasmoid.configuration.latitude
|
||||||
|
property double lon: Plasmoid.configuration.longitude
|
||||||
|
property bool fahrenheit: Plasmoid.configuration.useFahrenheit
|
||||||
|
property bool showCondition: Plasmoid.configuration.showCondition || false
|
||||||
|
property int weatherRefresh: Plasmoid.configuration.weatherRefresh || 5
|
||||||
|
property bool cpuFahrenheit: Plasmoid.configuration.cpuTempFahrenheit || false
|
||||||
|
property int cpuTempThreshold: Plasmoid.configuration.cpuTempThreshold || 80
|
||||||
|
property int statsRefresh: Plasmoid.configuration.statsRefresh || 10
|
||||||
|
property bool showCpuTemp: Plasmoid.configuration.showCpuTemp !== false
|
||||||
|
property bool showCpuUsage: Plasmoid.configuration.showCpuUsage !== false
|
||||||
|
property bool showMemory: Plasmoid.configuration.showMemory !== false
|
||||||
|
property bool showNetwork: Plasmoid.configuration.showNetwork !== false
|
||||||
|
|
||||||
|
// ── Live state ────────────────────────────────────────────────────────────
|
||||||
|
property string weatherIcon: ""
|
||||||
|
property string temperature: "--"
|
||||||
|
property string weatherCondition: ""
|
||||||
|
property int cpuTempRaw: -1
|
||||||
|
property int cpuUsage: -1
|
||||||
|
property int memUsage: -1
|
||||||
|
property int netDown: -1
|
||||||
|
property int netUp: -1
|
||||||
|
|
||||||
|
property string cpuTempDisplay: {
|
||||||
|
if (cpuTempRaw < 0) return "--"
|
||||||
|
if (cpuFahrenheit) return Math.round(cpuTempRaw * 9/5 + 32) + "°F"
|
||||||
|
return cpuTempRaw + "°C"
|
||||||
|
}
|
||||||
|
|
||||||
|
function padPct(val) {
|
||||||
|
var s = val + "%"
|
||||||
|
if (val < 10) return "\u00a0\u00a0" + s
|
||||||
|
if (val < 100) return "\u00a0" + s
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatNetSpeed(kbs) {
|
||||||
|
if (kbs < 0) return "--"
|
||||||
|
if (kbs < 1000) return kbs + " KB/s"
|
||||||
|
return (kbs / 1024).toFixed(1) + " MB/s"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Panel display ─────────────────────────────────────────────────────────
|
||||||
|
preferredRepresentation: fullRepresentation
|
||||||
|
|
||||||
|
fullRepresentation: PlasmaComponents.Label {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.preferredWidth: implicitWidth + 16
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
textFormat: Text.RichText
|
||||||
|
|
||||||
|
text: {
|
||||||
|
// CPU temp is red when above threshold
|
||||||
|
var hot = root.cpuTempRaw > 0 && root.cpuTempRaw >= root.cpuTempThreshold
|
||||||
|
|
||||||
|
// Weather section
|
||||||
|
var s = root.weatherIcon + "\u00a0\u00a0" + root.temperature + "°" + (root.fahrenheit ? "F" : "C")
|
||||||
|
if (root.showCondition && root.weatherCondition !== "")
|
||||||
|
s += " " + root.weatherCondition
|
||||||
|
|
||||||
|
// CPU + Memory stats
|
||||||
|
var cpuMem = []
|
||||||
|
if (root.showCpuTemp) {
|
||||||
|
var tempStr = "\uf2c8\u00a0\u00a0" + root.cpuTempDisplay
|
||||||
|
cpuMem.push(hot ? "<font color='#ff5555'>" + tempStr + "</font>" : tempStr)
|
||||||
|
}
|
||||||
|
if (root.showCpuUsage)
|
||||||
|
cpuMem.push("\uf2db\u00a0\u00a0" + (root.cpuUsage >= 0 ? padPct(root.cpuUsage) : "\u00a0--"))
|
||||||
|
if (root.showMemory)
|
||||||
|
cpuMem.push("\ue266\u00a0\u00a0" + (root.memUsage >= 0 ? padPct(root.memUsage) : "\u00a0--"))
|
||||||
|
|
||||||
|
var sp = "\u00a0\u00a0\u00a0\u00a0"
|
||||||
|
var div = "\u00a0\u00a0\u2502\u00a0\u00a0"
|
||||||
|
|
||||||
|
if (cpuMem.length > 0) s += div + cpuMem.join(sp)
|
||||||
|
|
||||||
|
// Network stats
|
||||||
|
if (root.showNetwork)
|
||||||
|
s += div + "\uf0ac" + sp + "\uf063\u00a0\u00a0" + formatNetSpeed(root.netDown) + sp + "\uf062\u00a0\u00a0" + formatNetSpeed(root.netUp)
|
||||||
|
|
||||||
|
return s + " "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Weather ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function iconForCode(code) {
|
||||||
|
if (code === 0) return "\ue30d"
|
||||||
|
if (code <= 3) return "\ue312"
|
||||||
|
if (code === 45 || code === 48) return "\ue313"
|
||||||
|
if ([51,53,55,61,63,65].indexOf(code) >= 0) return "\ue318"
|
||||||
|
if ([71,73,75].indexOf(code) >= 0) return "\ue31a"
|
||||||
|
return "\ue32e"
|
||||||
|
}
|
||||||
|
|
||||||
|
function conditionForCode(code) {
|
||||||
|
var map = {
|
||||||
|
0: "Clear", 1: "Mostly Clear", 2: "Partly Cloudy", 3: "Overcast",
|
||||||
|
45: "Foggy", 48: "Icy Fog",
|
||||||
|
51: "Light Drizzle", 53: "Drizzle", 55: "Heavy Drizzle",
|
||||||
|
61: "Light Rain", 63: "Rain", 65: "Heavy Rain",
|
||||||
|
71: "Light Snow", 73: "Snow", 75: "Heavy Snow", 77: "Snow Grains",
|
||||||
|
80: "Light Showers", 81: "Showers", 82: "Heavy Showers",
|
||||||
|
85: "Snow Showers", 86: "Heavy Snow Showers",
|
||||||
|
95: "Thunderstorm", 96: "Thunderstorm w/ Hail", 99: "Heavy Thunderstorm"
|
||||||
|
}
|
||||||
|
return map[code] || ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchWeather() {
|
||||||
|
var unit = root.fahrenheit ? "fahrenheit" : "celsius"
|
||||||
|
var url = "https://api.open-meteo.com/v1/forecast"
|
||||||
|
+ "?latitude=" + root.lat
|
||||||
|
+ "&longitude=" + root.lon
|
||||||
|
+ "¤t_weather=true"
|
||||||
|
+ "&temperature_unit=" + unit
|
||||||
|
+ "&timezone=auto"
|
||||||
|
var req = new XMLHttpRequest()
|
||||||
|
req.open("GET", url)
|
||||||
|
req.onreadystatechange = function() {
|
||||||
|
if (req.readyState === XMLHttpRequest.DONE && req.status === 200) {
|
||||||
|
var cw = JSON.parse(req.responseText).current_weather
|
||||||
|
root.temperature = Math.round(cw.temperature).toString()
|
||||||
|
root.weatherIcon = root.iconForCode(cw.weathercode)
|
||||||
|
root.weatherCondition = root.conditionForCode(cw.weathercode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.send()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── System Stats ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
P5Support.DataSource {
|
||||||
|
id: sysStatsSource
|
||||||
|
engine: "executable"
|
||||||
|
connectedSources: []
|
||||||
|
onNewData: function(source, data) {
|
||||||
|
var out = data["stdout"].trim().split(/\s+/)
|
||||||
|
if (out.length >= 5) {
|
||||||
|
root.cpuTempRaw = parseInt(out[0]) || -1
|
||||||
|
root.cpuUsage = parseInt(out[1]) || -1
|
||||||
|
root.memUsage = parseInt(out[2]) || -1
|
||||||
|
root.netDown = parseInt(out[3])
|
||||||
|
root.netUp = parseInt(out[4])
|
||||||
|
}
|
||||||
|
sysStatsSource.disconnectSource(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchSysStats() {
|
||||||
|
var path = Qt.resolvedUrl("../stats.sh").toString().replace("file://", "")
|
||||||
|
sysStatsSource.connectSource("bash " + path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Timers ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: weatherTimer
|
||||||
|
interval: Math.max(1, root.weatherRefresh) * 60000
|
||||||
|
repeat: true
|
||||||
|
running: true
|
||||||
|
onTriggered: root.fetchWeather()
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: statsTimer
|
||||||
|
interval: Math.max(2, root.statsRefresh) * 1000
|
||||||
|
repeat: true
|
||||||
|
running: true
|
||||||
|
onTriggered: root.fetchSysStats()
|
||||||
|
}
|
||||||
|
|
||||||
|
onWeatherRefreshChanged: weatherTimer.restart()
|
||||||
|
onStatsRefreshChanged: statsTimer.restart()
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
fetchWeather()
|
||||||
|
fetchSysStats()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Plasmoid.configuration
|
||||||
|
function onLatitudeChanged() { root.fetchWeather() }
|
||||||
|
function onLongitudeChanged() { root.fetchWeather() }
|
||||||
|
function onUseFahrenheitChanged() { root.fetchWeather() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"KPlugin": {
|
||||||
|
"Name": "Weather && Stats",
|
||||||
|
"Description": "Live weather and system stats for the KDE panel",
|
||||||
|
"Icon": "weather-clear",
|
||||||
|
"Id": "com.github.samjage.weatherstats",
|
||||||
|
"Version": "1.0",
|
||||||
|
"License": "GPL-2.0",
|
||||||
|
"Category": "System Information",
|
||||||
|
"Authors": [
|
||||||
|
{
|
||||||
|
"Name": "samjage",
|
||||||
|
"Email": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Website": "https://github.com/samjage/weather-and-stats"
|
||||||
|
},
|
||||||
|
"X-Plasma-API-Minimum-Version": "6.0",
|
||||||
|
"KPackageStructure": "Plasma/Applet"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user