Files
Gabriel Brown 44124d72fa Take one machine's fingerprints off everyone's desktop
The audit's second tier: values that were measurements of the author's
desktop, shipped to every machine as if they were defaults.

Settings greeted every human as Gabriel; it now greets whoever
accountsservice says is signed in, and nobody when it says nothing. The
weather shipped his home coordinates and confidently reported his forecast
anywhere on earth; it now ships unset, fetches nothing until a location is
chosen, and the location row says so. The GTK bookmarks carried seven
/home/gib paths and his file server into every file dialog; they are now
generated per machine from a template and gitignored -- Nautilus edits the
instance freely, the way settings.ini already worked one file over. Web
search routed through his personal bang redirector; the engine is now the
webSearchUrl preference with a DuckDuckGo default, read by both the script
command and the suggestions extension, which the launcher-search contract
already pins to one another. The GPU vitals path defaulted to his card1 and
lost the readout on any machine enumerated differently; a machine with
exactly one GPU now adopts it. And the Containers and Snapshots pages hide
once a scan proves their backing stack absent, instead of rendering
permanently empty on machines that never had podman or snapper.

Lesser residue swept in the same pass: the DP-2 hyprpaper block one machine
needed, the author's username-typo expansions (moved to his personal seed in
user/, where personal content belongs), a capture fallback into /home/gib,
and a parity table asserting one machine's hardware as fact.

Claude-Session: https://claude.ai/code/session_01Epx9ZC1gwm81K3jm9x9CKh
2026-08-23 11:55:43 -04:00

201 lines
6.3 KiB
QML

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 + "&current=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;
}
}
// Nothing was chosen yet: fetch nothing, claim nothing. The card offers
// the location picker instead.
readonly property bool configured: Settings.weatherLocation !== ""
function refresh(): void {
if (!root.configured) {
root.available = false;
return;
}
// 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 "";
}
}
}