Add: Weather widget

- Use OWM daemon's cache file
- OWM icon codes
- Add dependency: serde_json
This commit is contained in:
Candifloss 2025-11-23 12:48:43 +05:30
parent 61f7020fd3
commit 2a1beab0e8
4 changed files with 145 additions and 1 deletions

View File

@ -11,6 +11,7 @@ chrono = "0.4.42"
dirs = "6.0.0" dirs = "6.0.0"
i-slint-backend-winit = { version = "1.14.1", features = ["x11"] } i-slint-backend-winit = { version = "1.14.1", features = ["x11"] }
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
slint = { version = "1.14.1", features = ["backend-winit"] } slint = { version = "1.14.1", features = ["backend-winit"] }
toml = "0.9.8" toml = "0.9.8"

View File

@ -4,6 +4,7 @@ mod common;
mod datewidget; mod datewidget;
mod timewidget; mod timewidget;
mod volumewidget; mod volumewidget;
mod weatherwidget;
mod wifiwidget; mod wifiwidget;
use crate::TopBar; use crate::TopBar;
@ -15,7 +16,8 @@ pub fn install_callbacks(ui: &TopBar) {
volumewidget::install(ui); volumewidget::install(ui);
brightnesswidget::install(ui); brightnesswidget::install(ui);
wifiwidget::install(ui); wifiwidget::install(ui);
weatherwidget::install(ui);
// In the future: // In the future:
// weatherwidget::install(ui); // notifwidget::install(ui);
} }

View File

@ -0,0 +1,120 @@
use super::common::run_cmd;
use crate::TopBar;
use serde::Deserialize;
use slint::{ComponentHandle, SharedString, Timer, TimerMode};
use std::{fs, path::PathBuf, time::Duration};
/// The subset of the JSON we actually need.
#[derive(Deserialize)]
struct WeatherJson {
weather: Vec<WeatherEntry>,
main: MainEntry,
}
#[derive(Deserialize)]
struct WeatherEntry {
icon: String, // e.g. "09n"
main: String, // e.g. "Mist"
}
#[derive(Deserialize)]
struct MainEntry {
temp: f32, // e.g. 21.09
}
/// Expand `~` to the user home dir
fn expand_home(path: &str) -> PathBuf {
if let Some(home) = dirs::home_dir()
&& path.starts_with("~/")
{
return home.join(path.trim_start_matches("~/"));
}
PathBuf::from(path)
}
/// Parse the JSON file; return (temp, cond, icon code)
fn read_weather_file() -> Option<(f32, String, String)> {
let path = expand_home("~/.cache/candydesktop/owm_widget.json");
let data = fs::read_to_string(path).ok()?;
let parsed: WeatherJson = serde_json::from_str(&data).ok()?;
let temp = parsed.main.temp;
let cond = parsed.weather.first()?.main.clone(); // e.g. "Mist"
let icon_code = parsed.weather.first()?.icon.clone(); // e.g. "09n"
Some((temp, cond, icon_code))
}
/// Pick glyph based on OWM icon code
fn icon_from_code(code: &str) -> &'static str {
match code {
// Thunderstorm
"11d" | "11n" => "󰙾",
// Drizzle
"09d" | "09n" => "󰖗",
// Rain
"10d" => "󰖖",
"10n" => "󰼳",
// Snow
"13d" | "13n" => "󰙿",
// Atmosphere (mist, fog, haze, dust)
"50d" | "50n" => "󰖑",
// Clouds
"02d" | "03d" | "04d" => "󰖐",
"02n" | "03n" | "04n" => "󰼱",
// Clear sky
"01d" => "󰖙",
"01n" => "󰖔",
// Fallback (unknown)
_ => "󰖩",
}
}
/// Format tooltip: "21.1 °C, Mist"
fn tooltip(temp: f32, cond: &str) -> SharedString {
SharedString::from(format!("{temp:.1} °C, {cond}"))
}
/// Timer to refresh weather every 100 seconds
fn start_weather_updater(ui: &TopBar) {
let weak = ui.as_weak();
// One-shot update on startup
if let Some(ui) = weak.upgrade()
&& let Some((t, c, icon_code)) = read_weather_file()
{
ui.set_weather_icon(icon_from_code(&icon_code).into());
ui.set_weather_tooltip(tooltip(t, &c).into());
}
// Refresh every 100 sec
let timer = Box::leak(Box::new(Timer::default()));
timer.start(TimerMode::Repeated, Duration::from_secs(100), move || {
if let Some(ui) = weak.upgrade()
&& let Some((t, c, icon_code)) = read_weather_file()
{
ui.set_weather_icon(icon_from_code(&icon_code).into());
ui.set_weather_tooltip(tooltip(t, &c));
}
});
}
/// Public entry: connect callback + start updater
pub fn install(ui: &TopBar) {
let weak = ui.as_weak();
ui.on_show_weather(move || {
if weak.upgrade().is_some() {
run_cmd("xclock"); // Fix later
}
});
start_weather_updater(ui);
}

View File

@ -54,6 +54,15 @@ export component TopBar inherits Window {
in property <color> wifi_bg_clicked: #4a5f70;*/ in property <color> wifi_bg_clicked: #4a5f70;*/
callback show_wifi(); callback show_wifi();
// Weather widget
in property <string> weather_tooltip;
in property <string> weather_icon;
in property <color> weather_icon_color: #ffffff;
in property <color> weather_bg_normal: #8e9162;
in property <color> weather_bg_hover: #6d8a4d;
in property <color> weather_bg_clicked: #4a5f70;
callback show_weather();
title: "chocobar"; title: "chocobar";
width: bar_width *1px; width: bar_width *1px;
height: bar_height *1px; height: bar_height *1px;
@ -98,6 +107,18 @@ export component TopBar inherits Window {
HorizontalLayout { HorizontalLayout {
alignment: end; // Right-align alignment: end; // Right-align
// SquareIconWidget - Weather
SquareIconWidget {
bar_height: root.bar_height;
icon_text: root.weather_icon;
tooltip_text: root.weather_tooltip;
icon_color: root.weather_icon_color;
bg_normal: root.weather_bg_normal;
bg_hover: root.weather_bg_hover;
bg_clicked: root.weather_bg_clicked;
square_btn_callback => root.show_weather();
}
// SquareIconWidget - Wifi // SquareIconWidget - Wifi
SquareIconWidget { SquareIconWidget {
bar_height: root.bar_height; bar_height: root.bar_height;