Build the Panama Hyprland desktop
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
pragma Singleton
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Current conditions from Open-Meteo (no API key, no account).
|
||||
//
|
||||
// Fetched with curl rather than XMLHttpRequest: curl is guaranteed present and
|
||||
// the response is a single small JSON blob, so there is nothing to stream.
|
||||
//
|
||||
// Failure is silent by design — a weather widget that can't reach the network
|
||||
// must not produce error popups or retry storms. A failed fetch just leaves
|
||||
// `available` false until the next scheduled refresh.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
import qs.config
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// False until a fetch has succeeded, and again after one fails.
|
||||
property bool available: false
|
||||
|
||||
property real temperature: 0
|
||||
property int weatherCode: -1
|
||||
|
||||
// Nerd Font glyph and a short human label for `weatherCode`.
|
||||
readonly property string icon: root._codeIcon(root.weatherCode)
|
||||
readonly property string description: root._codeDescription(root.weatherCode)
|
||||
|
||||
readonly property string unitSuffix: Settings.temperatureUnit === "fahrenheit" ? "°F" : "°C"
|
||||
|
||||
readonly property string url: "https://api.open-meteo.com/v1/forecast" + "?latitude=" + Settings.latitude + "&longitude=" + Settings.longitude + "¤t=temperature_2m,weather_code" + "&temperature_unit=" + Settings.temperatureUnit
|
||||
|
||||
// The timer is the *only* thing that starts a fetch, so a hard failure can
|
||||
// never retry faster than the refresh interval.
|
||||
Timer {
|
||||
interval: Settings.weatherRefreshMinutes * 60 * 1000
|
||||
running: true
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: root.refresh()
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fetch
|
||||
// -s silences the progress meter, --max-time keeps a black-holed
|
||||
// connection from leaving the process alive until the next refresh.
|
||||
command: ["curl", "-s", "--max-time", "10", root.url]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: root._parse(this.text)
|
||||
}
|
||||
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode !== 0)
|
||||
root.available = false;
|
||||
}
|
||||
}
|
||||
|
||||
function refresh(): void {
|
||||
// Skip if the previous fetch is somehow still in flight.
|
||||
if (!fetch.running)
|
||||
fetch.running = true;
|
||||
}
|
||||
|
||||
function _parse(text: string): void {
|
||||
if (!text) {
|
||||
root.available = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const current = JSON.parse(text).current;
|
||||
if (!current || current.temperature_2m === undefined) {
|
||||
root.available = false;
|
||||
return;
|
||||
}
|
||||
|
||||
root.temperature = current.temperature_2m;
|
||||
root.weatherCode = current.weather_code;
|
||||
root.available = true;
|
||||
} catch (e) {
|
||||
root.available = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── WMO 4677 weather codes ──────────────────────────────────────────────
|
||||
// Open-Meteo's `weather_code` is the WMO set. Grouped here into the same
|
||||
// buckets GNOME Weather uses, because the distinctions finer than this are
|
||||
// not legible at bar size.
|
||||
// Glyphs come from the nf-weather range. They are written as escapes rather
|
||||
// than literal characters so tooling that mishandles private-use codepoints
|
||||
// cannot silently eat them.
|
||||
function _codeIcon(code: int): string {
|
||||
switch (code) {
|
||||
case 0:
|
||||
return "\u{E30D}"; // weather-day_sunny
|
||||
case 1:
|
||||
return "\u{E30C}"; // weather-day_sunny_overcast
|
||||
case 2:
|
||||
return "\u{E302}"; // weather-day_cloudy
|
||||
case 3:
|
||||
return "\u{E312}"; // weather-cloudy
|
||||
case 45:
|
||||
case 48:
|
||||
return "\u{E313}"; // weather-fog
|
||||
case 51:
|
||||
case 53:
|
||||
case 55:
|
||||
return "\u{E31B}"; // weather-sprinkle
|
||||
case 56:
|
||||
case 57:
|
||||
case 66:
|
||||
case 67:
|
||||
return "\u{E316}"; // weather-rain_mix (freezing)
|
||||
case 61:
|
||||
case 63:
|
||||
case 65:
|
||||
return "\u{E318}"; // weather-rain
|
||||
case 71:
|
||||
case 73:
|
||||
case 75:
|
||||
case 77:
|
||||
case 85:
|
||||
case 86:
|
||||
return "\u{E31A}"; // weather-snow
|
||||
case 80:
|
||||
case 81:
|
||||
case 82:
|
||||
return "\u{E319}"; // weather-showers
|
||||
case 95:
|
||||
case 96:
|
||||
case 99:
|
||||
return "\u{E31D}"; // weather-thunderstorm
|
||||
default:
|
||||
return "\u{E374}"; // weather-na — unknown, or nothing fetched yet
|
||||
}
|
||||
}
|
||||
|
||||
function _codeDescription(code: int): string {
|
||||
switch (code) {
|
||||
case 0:
|
||||
return "Clear";
|
||||
case 1:
|
||||
return "Mainly clear";
|
||||
case 2:
|
||||
return "Partly cloudy";
|
||||
case 3:
|
||||
return "Overcast";
|
||||
case 45:
|
||||
return "Fog";
|
||||
case 48:
|
||||
return "Rime fog";
|
||||
case 51:
|
||||
case 53:
|
||||
case 55:
|
||||
return "Drizzle";
|
||||
case 56:
|
||||
case 57:
|
||||
return "Freezing drizzle";
|
||||
case 61:
|
||||
case 63:
|
||||
case 65:
|
||||
return "Rain";
|
||||
case 66:
|
||||
case 67:
|
||||
return "Freezing rain";
|
||||
case 71:
|
||||
case 73:
|
||||
case 75:
|
||||
return "Snow";
|
||||
case 77:
|
||||
return "Snow grains";
|
||||
case 80:
|
||||
case 81:
|
||||
case 82:
|
||||
return "Showers";
|
||||
case 85:
|
||||
case 86:
|
||||
return "Snow showers";
|
||||
case 95:
|
||||
return "Thunderstorm";
|
||||
case 96:
|
||||
case 99:
|
||||
return "Thunderstorm, hail";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user