73 lines
2.4 KiB
Python
73 lines
2.4 KiB
Python
"""Weather quad: wttr.in rendered with its original ANSI/CLI art (via AnsiRenderer).
|
|
|
|
Reuses the shared LocationService for the city; if none is known yet, wttr.in
|
|
geolocates the caller's IP itself, so the widget still works standalone. The
|
|
'ascii_art' feature toggle swaps the art for a compact one-line text summary.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import gi
|
|
|
|
gi.require_version("Gtk", "4.0")
|
|
from gi.repository import Gtk # noqa: E402
|
|
|
|
from lib.ansi import AnsiRenderer
|
|
from lib.proc import run_text
|
|
from module_base import Feature, ModuleContext, ModuleInstance, ModuleSpec
|
|
from paths import BACKEND_DIR
|
|
|
|
_SCRIPT = str(BACKEND_DIR / "weather.sh")
|
|
|
|
|
|
class _WeatherView(Gtk.Box):
|
|
def __init__(self, ctx: ModuleContext, opts: str):
|
|
super().__init__(orientation=Gtk.Orientation.VERTICAL)
|
|
self.add_css_class("weather-view")
|
|
self.ctx = ctx
|
|
self.opts = opts
|
|
self._loc = ""
|
|
|
|
self.renderer = AnsiRenderer()
|
|
self.append(self.renderer.view)
|
|
self._status = Gtk.Label(label="Loading weather…")
|
|
self._status.add_css_class("weather-status")
|
|
self.append(self._status)
|
|
|
|
ctx.services.location.subscribe(self._on_location)
|
|
self.refresh()
|
|
|
|
def _on_location(self, data: dict) -> None:
|
|
self._loc = data.get("city") or ""
|
|
self.refresh()
|
|
|
|
def refresh(self) -> None:
|
|
art = self.ctx.feature("ascii_art", True)
|
|
opts = self.opts if art else "format=%l:+%c+%t,+%w"
|
|
run_text([_SCRIPT, self._loc, opts], self._on_result)
|
|
|
|
def _on_result(self, ok: bool, out: str, err: str) -> None:
|
|
if ok and out.strip() and "<html" not in out.lower():
|
|
self.renderer.set_text(out.rstrip("\n"))
|
|
self._status.set_visible(False)
|
|
else:
|
|
self._status.set_text("Weather unavailable")
|
|
self._status.set_visible(True)
|
|
|
|
|
|
def build(ctx: ModuleContext) -> ModuleInstance:
|
|
# The ascii_art toggle rebuilds the whole card via QuadCard, so both views are
|
|
# recreated and re-fetch with the current format — no partial refresh here.
|
|
compact = _WeatherView(ctx, opts="0") # current conditions only
|
|
expanded = _WeatherView(ctx, opts="") # full 3-day forecast
|
|
return ModuleInstance(compact=compact, expanded=expanded)
|
|
|
|
|
|
SPEC = ModuleSpec(
|
|
id="weather",
|
|
title="Weather",
|
|
icon="", # nf-weather
|
|
build=build,
|
|
features=[Feature("ascii_art", "CLI art", True)],
|
|
)
|