Add: Weather widget
- Use OWM daemon's cache file - OWM icon codes - Add dependency: serde_json
This commit is contained in:
parent
61f7020fd3
commit
2a1beab0e8
@ -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"
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
120
src/widgets/weatherwidget.rs
Normal file
120
src/widgets/weatherwidget.rs
Normal 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);
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user