tracing and changes
This commit is contained in:
Generated
+109
@@ -31,6 +31,15 @@ dependencies = [
|
|||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android-activity"
|
name = "android-activity"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
@@ -514,6 +523,8 @@ dependencies = [
|
|||||||
"services",
|
"services",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1913,6 +1924,12 @@ dependencies = [
|
|||||||
"wayland-protocols-wlr",
|
"wayland-protocols-wlr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.189"
|
version = "0.2.189"
|
||||||
@@ -2046,6 +2063,15 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matchers"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||||
|
dependencies = [
|
||||||
|
"regex-automata",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.3"
|
version = "2.8.3"
|
||||||
@@ -2097,11 +2123,13 @@ dependencies = [
|
|||||||
name = "modules"
|
name = "modules"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"common",
|
"common",
|
||||||
"iced",
|
"iced",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
"toml",
|
"toml",
|
||||||
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2187,6 +2215,15 @@ dependencies = [
|
|||||||
"jni-sys 0.3.1",
|
"jni-sys 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-ansi-term"
|
||||||
|
version = "0.50.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -2882,6 +2919,23 @@ dependencies = [
|
|||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "renderdoc-sys"
|
name = "renderdoc-sys"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -3217,6 +3271,16 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sharded-slab"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3595,6 +3659,15 @@ dependencies = [
|
|||||||
"syn 3.0.5",
|
"syn 3.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiny-skia"
|
name = "tiny-skia"
|
||||||
version = "0.11.4"
|
version = "0.11.4"
|
||||||
@@ -3821,6 +3894,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"valuable",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-log"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-subscriber"
|
||||||
|
version = "0.3.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
|
||||||
|
dependencies = [
|
||||||
|
"matchers",
|
||||||
|
"nu-ansi-term",
|
||||||
|
"once_cell",
|
||||||
|
"regex-automata",
|
||||||
|
"sharded-slab",
|
||||||
|
"smallvec",
|
||||||
|
"thread_local",
|
||||||
|
"tracing",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3904,6 +4007,12 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ edition = "2021"
|
|||||||
iced = { version = "0.14", default-features = false, features = ["wgpu", "wayland", "crisp", "web-colors", "thread-pool", "advanced", "tokio"] }
|
iced = { version = "0.14", default-features = false, features = ["wgpu", "wayland", "crisp", "web-colors", "thread-pool", "advanced", "tokio"] }
|
||||||
iced_layershell = { version = "0.19", default-features = false }
|
iced_layershell = { version = "0.19", default-features = false }
|
||||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time"] }
|
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time"] }
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
toml = "1.1"
|
toml = "1.1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ pub struct HourlyUnits {
|
|||||||
pub relative_humidity_2m: String,
|
pub relative_humidity_2m: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct Hourly {
|
pub struct Hourly {
|
||||||
#[serde(deserialize_with = "naive_dt::deserialize")]
|
#[serde(deserialize_with = "naive_dt::deserialize")]
|
||||||
pub time: Vec<chrono::NaiveDateTime>,
|
pub time: Vec<chrono::NaiveDateTime>,
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ common = { path = "../common" }
|
|||||||
iced = { workspace = true }
|
iced = { workspace = true }
|
||||||
iced_layershell = { workspace = true }
|
iced_layershell = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
tracing-subscriber = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
clap = {workspace = true}
|
clap = {workspace = true}
|
||||||
toml = {workspace =true}
|
toml = {workspace =true}
|
||||||
|
|||||||
@@ -39,10 +39,16 @@ impl Bar {
|
|||||||
.collect();
|
.collect();
|
||||||
// One inbox per distinct service any module wants.
|
// One inbox per distinct service any module wants.
|
||||||
let wanted: BTreeSet<&'static str> = routes.values().flatten().map(|s| s.0).collect();
|
let wanted: BTreeSet<&'static str> = routes.values().flatten().map(|s| s.0).collect();
|
||||||
let inputs = wanted
|
tracing::debug!(?routes, "module routes: wanted msg types per module");
|
||||||
|
let inputs: BTreeMap<&'static str, Inbox> = wanted
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|key| (key, Inbox::new(key)))
|
.map(|key| (key, Inbox::new(key)))
|
||||||
.collect();
|
.collect();
|
||||||
|
tracing::debug!(
|
||||||
|
modules = modules.len(),
|
||||||
|
services = ?inputs.keys().copied().collect::<Vec<_>>(),
|
||||||
|
"spawning wanted services"
|
||||||
|
);
|
||||||
Self {
|
Self {
|
||||||
active_popup: None,
|
active_popup: None,
|
||||||
modules,
|
modules,
|
||||||
|
|||||||
+22
-6
@@ -27,27 +27,43 @@ pub(crate) use msg::{Message, Msg};
|
|||||||
/// Height of the bar in logical pixels.
|
/// Height of the bar in logical pixels.
|
||||||
const BAR_HEIGHT: u32 = 36;
|
const BAR_HEIGHT: u32 = 36;
|
||||||
|
|
||||||
fn main() -> Result<(), iced_layershell::Error> {
|
/// Default log filter per build profile. Debug builds log barbar's own crates
|
||||||
let args = args::Args::parse();
|
/// at `debug` while dependency noise (iced_layershell/sctk/cosmic_text/wgpu…)
|
||||||
|
/// stays at `warn`; release builds log `warn` and above. `RUST_LOG` overrides.
|
||||||
|
const DEFAULT_FILTER: &str = if cfg!(debug_assertions) {
|
||||||
|
"warn,core=debug,common=debug,modules=debug,services=debug"
|
||||||
|
} else {
|
||||||
|
"warn"
|
||||||
|
};
|
||||||
|
|
||||||
let config_handle = File::open(args.config);
|
fn main() -> Result<(), iced_layershell::Error> {
|
||||||
|
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||||
|
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(DEFAULT_FILTER));
|
||||||
|
tracing_subscriber::fmt().with_env_filter(filter).init();
|
||||||
|
|
||||||
|
let args = args::Args::parse();
|
||||||
|
tracing::debug!(config = %args.config.display(), "parsed args");
|
||||||
|
|
||||||
|
let config_handle = File::open(&args.config);
|
||||||
let config: BarbarConfig;
|
let config: BarbarConfig;
|
||||||
if let Ok(mut config_file) = config_handle {
|
if let Ok(mut config_file) = config_handle {
|
||||||
let mut string = String::new();
|
let mut string = String::new();
|
||||||
config_file.read_to_string(&mut string).unwrap();
|
config_file.read_to_string(&mut string).unwrap();
|
||||||
config = toml::from_str(&string).unwrap();
|
config = toml::from_str(&string).unwrap();
|
||||||
|
tracing::info!(config = %args.config.display(), "config loaded");
|
||||||
} else {
|
} else {
|
||||||
println!("cant open config file");
|
tracing::warn!(config = %args.config.display(), "cannot open config file");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let start_mode = match config.monitor {
|
let start_mode = match &config.monitor {
|
||||||
Some(x) => StartMode::TargetScreen(x),
|
Some(x) => StartMode::TargetScreen(x.clone()),
|
||||||
None => StartMode::Active,
|
None => StartMode::Active,
|
||||||
};
|
};
|
||||||
|
|
||||||
let modules = config.modules.clone().unwrap_or_default();
|
let modules = config.modules.clone().unwrap_or_default();
|
||||||
let order = config.order;
|
let order = config.order;
|
||||||
|
tracing::info!(monitor = ?config.monitor, "starting barbar daemon");
|
||||||
|
|
||||||
daemon(
|
daemon(
|
||||||
move || {
|
move || {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ pub fn open_popup(bar: &mut Bar, module_id: String, bounds: Rectangle) -> Task<M
|
|||||||
|
|
||||||
let (id, task) = popup_open(settings);
|
let (id, task) = popup_open(settings);
|
||||||
bar.popup_id = Some(id);
|
bar.popup_id = Some(id);
|
||||||
|
tracing::debug!(module = %module_id, ?id, size = ?(w, h), "popup opened");
|
||||||
task
|
task
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +56,7 @@ pub fn open_popup(bar: &mut Bar, module_id: String, bounds: Rectangle) -> Task<M
|
|||||||
/// `Msg::WindowClosed`, so popup content keeps rendering until then.
|
/// `Msg::WindowClosed`, so popup content keeps rendering until then.
|
||||||
pub fn close_popup(bar: &mut Bar) -> Task<Message> {
|
pub fn close_popup(bar: &mut Bar) -> Task<Message> {
|
||||||
if let Some(id) = bar.popup_id {
|
if let Some(id) = bar.popup_id {
|
||||||
|
tracing::debug!(?id, "requesting popup close");
|
||||||
return Task::done(Message::Effect(ModuleEffect::ClosePopup(id)));
|
return Task::done(Message::Effect(ModuleEffect::ClosePopup(id)));
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ fn handle_event(bar: &mut Bar, event: Msg) -> Task<Message> {
|
|||||||
if bar.popup_id == Some(id) {
|
if bar.popup_id == Some(id) {
|
||||||
bar.popup_id = None;
|
bar.popup_id = None;
|
||||||
bar.active_popup = None;
|
bar.active_popup = None;
|
||||||
|
tracing::debug!(?id, "popup window closed");
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
@@ -37,12 +38,19 @@ fn route(bar: &mut Bar, wire: Wire) -> Task<Message> {
|
|||||||
match wire.to {
|
match wire.to {
|
||||||
Target::Module(id) => match bar.modules.get_mut(id) {
|
Target::Module(id) => match bar.modules.get_mut(id) {
|
||||||
Some(module) => module.update(wire).map(Message::Effect),
|
Some(module) => module.update(wire).map(Message::Effect),
|
||||||
None => Task::none(),
|
None => {
|
||||||
|
tracing::warn!(module = id, "wire routed to unknown module");
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Target::Service(key) => {
|
Target::Service(key) => {
|
||||||
if let Some(inbox) = bar.inputs.get(key) {
|
if let Some(inbox) = bar.inputs.get(key) {
|
||||||
inbox.send(wire);
|
if !inbox.send(wire) {
|
||||||
|
tracing::warn!(service = key, "service inbox closed");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracing::warn!(service = key, "wire routed to unknown service");
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
@@ -51,7 +59,8 @@ fn route(bar: &mut Bar, wire: Wire) -> Task<Message> {
|
|||||||
// Fan out by route key; the payload is opaque here, each module
|
// Fan out by route key; the payload is opaque here, each module
|
||||||
// downcasts it. New services never touch this file.
|
// downcasts it. New services never touch this file.
|
||||||
let topic = Service(key);
|
let topic = Service(key);
|
||||||
bar.modules
|
let tasks: Vec<_> = bar
|
||||||
|
.modules
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.filter(|(id, _)| {
|
.filter(|(id, _)| {
|
||||||
bar.routes
|
bar.routes
|
||||||
@@ -59,7 +68,9 @@ fn route(bar: &mut Bar, wire: Wire) -> Task<Message> {
|
|||||||
.is_some_and(|keys| keys.contains(&topic))
|
.is_some_and(|keys| keys.contains(&topic))
|
||||||
})
|
})
|
||||||
.map(|(_, module)| module.update(wire.clone()).map(Message::Effect))
|
.map(|(_, module)| module.update(wire.clone()).map(Message::Effect))
|
||||||
.fold(Task::none(), |acc, t| acc.chain(t))
|
.collect();
|
||||||
|
tracing::debug!(topic = key, subscribers = tasks.len(), "fan-out");
|
||||||
|
tasks.into_iter().fold(Task::none(), |acc, t| acc.chain(t))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,8 +81,10 @@ fn handle_effect(bar: &mut Bar, effect: ModuleEffect) -> Task<Message> {
|
|||||||
ModuleEffect::RequestPopup(module_id, element_id) => {
|
ModuleEffect::RequestPopup(module_id, element_id) => {
|
||||||
// Toggle: close if already open for this module, else open.
|
// Toggle: close if already open for this module, else open.
|
||||||
if bar.active_popup.as_deref() == Some(module_id.as_str()) {
|
if bar.active_popup.as_deref() == Some(module_id.as_str()) {
|
||||||
|
tracing::debug!(module = %module_id, "closing popup");
|
||||||
popup::close_popup(bar)
|
popup::close_popup(bar)
|
||||||
} else {
|
} else {
|
||||||
|
tracing::debug!(module = %module_id, "opening popup");
|
||||||
popup::capture_bounds(module_id, element_id)
|
popup::capture_bounds(module_id, element_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-6
@@ -1,5 +1,5 @@
|
|||||||
use common::Wire;
|
use common::Wire;
|
||||||
use iced::widget::{container, row, text};
|
use iced::widget::{container, row, space, text};
|
||||||
use iced::{window, Alignment, Element, Length, Theme};
|
use iced::{window, Alignment, Element, Length, Theme};
|
||||||
|
|
||||||
use crate::app::Bar;
|
use crate::app::Bar;
|
||||||
@@ -33,21 +33,49 @@ fn bar_view(bar: &Bar) -> Element<'_, Message> {
|
|||||||
.view(None)
|
.view(None)
|
||||||
.map(|w| Message::Event(Msg::Wire(w)))
|
.map(|w| Message::Event(Msg::Wire(w)))
|
||||||
})
|
})
|
||||||
.collect::<Vec<Element<Message>>>());
|
.collect::<Vec<Element<Message>>>())
|
||||||
|
.spacing(8);
|
||||||
|
|
||||||
let middle = row(bar
|
let middle = row(bar
|
||||||
.order
|
.order
|
||||||
.1
|
.1
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| bar.modules.get(x.to_string()).unwrap().view(None)));
|
.map(|x| {
|
||||||
|
bar.modules
|
||||||
|
.get(x.to_string())
|
||||||
|
.unwrap()
|
||||||
|
.view(None)
|
||||||
|
.map(|w| Message::Event(Msg::Wire(w)))
|
||||||
|
})
|
||||||
|
.collect::<Vec<Element<Message>>>())
|
||||||
|
.spacing(8);
|
||||||
let right = row(bar
|
let right = row(bar
|
||||||
.order
|
.order
|
||||||
.2
|
.2
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| bar.modules.get(x.to_string()).unwrap().view(None)));
|
.map(|x| {
|
||||||
|
bar.modules
|
||||||
|
.get(x.to_string())
|
||||||
|
.unwrap()
|
||||||
|
.view(None)
|
||||||
|
.map(|w| Message::Event(Msg::Wire(w)))
|
||||||
|
})
|
||||||
|
.collect::<Vec<Element<Message>>>())
|
||||||
|
.spacing(8);
|
||||||
|
|
||||||
container(left.width(Length::Fill).align_y(Alignment::Center))
|
let row = row![
|
||||||
|
left,
|
||||||
|
space::horizontal(),
|
||||||
|
middle,
|
||||||
|
space::horizontal(),
|
||||||
|
right,
|
||||||
|
]
|
||||||
|
.width(Length::Fill);
|
||||||
|
|
||||||
|
container(row)
|
||||||
.padding(8)
|
.padding(8)
|
||||||
.align_x(Alignment::Center)
|
.width(Length::Fill)
|
||||||
|
.align_y(Alignment::Center)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,5 +11,7 @@ path = "src/lib.rs"
|
|||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
iced = { workspace = true }
|
iced = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
chrono = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
serde = {workspace = true}
|
serde = {workspace = true}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ pub struct Clock {
|
|||||||
#[derive(Deserialize, Default)]
|
#[derive(Deserialize, Default)]
|
||||||
struct ClockConfig {
|
struct ClockConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
format: bool,
|
show_seconds: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clock {
|
impl Clock {
|
||||||
@@ -37,7 +37,7 @@ impl Clock {
|
|||||||
match config_clock(config) {
|
match config_clock(config) {
|
||||||
Some(x) => Self {
|
Some(x) => Self {
|
||||||
value: "--:--:--".to_string(),
|
value: "--:--:--".to_string(),
|
||||||
show_seconds: x.format,
|
show_seconds: x.show_seconds,
|
||||||
},
|
},
|
||||||
None => Self::default(),
|
None => Self::default(),
|
||||||
}
|
}
|
||||||
@@ -102,6 +102,7 @@ impl BarModule for Clock {
|
|||||||
match msg.downcast::<ClockMsg>() {
|
match msg.downcast::<ClockMsg>() {
|
||||||
Some(ClockMsg::ToggleSeconds) => {
|
Some(ClockMsg::ToggleSeconds) => {
|
||||||
self.show_seconds = !self.show_seconds;
|
self.show_seconds = !self.show_seconds;
|
||||||
|
tracing::debug!(show_seconds = self.show_seconds, "clock toggle");
|
||||||
let want = if self.show_seconds {
|
let want = if self.show_seconds {
|
||||||
ClockKind::Seconds
|
ClockKind::Seconds
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
use common::{BarModule, Endpoint, ModuleEffect, Service, WeatherKind, WeatherMsg, Wire};
|
use chrono::Timelike;
|
||||||
|
use common::{
|
||||||
|
BarModule, ClockKind, ClockPayload, Endpoint, Hourly, ModuleEffect, Service, WeatherKind,
|
||||||
|
WeatherMsg, WeatherPayload, WeatherResponse, Wire,
|
||||||
|
};
|
||||||
use iced::{
|
use iced::{
|
||||||
widget::{container, mouse_area, text},
|
widget::{container, mouse_area, text},
|
||||||
Element, Task,
|
Element, Task,
|
||||||
@@ -8,11 +12,37 @@ use iced::{
|
|||||||
|
|
||||||
pub struct WeatherModule {
|
pub struct WeatherModule {
|
||||||
text: String,
|
text: String,
|
||||||
|
/// Cached hourly forecast from the last weather update.
|
||||||
|
hourly: Option<Hourly>,
|
||||||
|
/// Current hour, mirrored from the datetime service's clock ticks.
|
||||||
|
hour: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WeatherModule {
|
||||||
|
/// Shows the forecast temperature for the tracked current hour.
|
||||||
|
fn refresh(&mut self) {
|
||||||
|
let (Some(hourly), Some(hour)) = (&self.hourly, self.hour) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(i) = hourly.time.iter().position(|t| t.hour() == hour) {
|
||||||
|
if let Some(&temp) = hourly.temperature_2m.get(i) {
|
||||||
|
let value = format!("{}°", temp as i32);
|
||||||
|
if self.text != value {
|
||||||
|
self.text = value;
|
||||||
|
tracing::info!(hour, temp, "weather: current hour");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WeatherModule {
|
impl Default for WeatherModule {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { text: "hi".into() }
|
Self {
|
||||||
|
text: "waiting ".into(),
|
||||||
|
hourly: None,
|
||||||
|
hour: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,18 +62,36 @@ impl BarModule for WeatherModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, msg: Wire) -> Task<ModuleEffect> {
|
fn update(&mut self, msg: Wire) -> Task<ModuleEffect> {
|
||||||
|
// Weather state: cache the hourly forecast, then show the current hour.
|
||||||
|
if let Some(env) = msg.downcast::<WeatherPayload>() {
|
||||||
|
match env.kind {
|
||||||
|
WeatherMsg::State => match env.payload.downcast_ref::<WeatherResponse>() {
|
||||||
|
Some(resp) => {
|
||||||
|
self.hourly = Some(resp.hourly.clone());
|
||||||
|
self.refresh();
|
||||||
|
}
|
||||||
|
None => tracing::warn!("weather payload was not a WeatherResponse"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Clock tick from the datetime service: track the current hour.
|
||||||
|
if let Some(payload) = msg.downcast::<ClockPayload>() {
|
||||||
|
self.hour = payload.value.split(':').next().and_then(|h| h.parse().ok());
|
||||||
|
self.refresh();
|
||||||
|
}
|
||||||
match msg.downcast::<WeatherKind>() {
|
match msg.downcast::<WeatherKind>() {
|
||||||
Some(WeatherKind::Poke) => {
|
Some(WeatherKind::Poke) => {
|
||||||
|
tracing::debug!("weather poked");
|
||||||
self.text = "poked".into();
|
self.text = "poked".into();
|
||||||
Task::none()
|
|
||||||
}
|
}
|
||||||
None => Task::none(),
|
None => (),
|
||||||
}
|
}
|
||||||
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Route keys this module subscribes to.
|
/// Route keys this module subscribes to.
|
||||||
fn services(&self) -> Vec<Service> {
|
fn services(&self) -> Vec<Service> {
|
||||||
vec![WeatherMsg::State.key()]
|
vec![WeatherMsg::State.key(), ClockKind::Seconds.key()]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optional: read `module.weather` from the config table.
|
/// Optional: read `module.weather` from the config table.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ path = "src/lib.rs"
|
|||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
iced = { workspace = true }
|
iced = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
|||||||
@@ -39,12 +39,15 @@ impl ClockTicker {
|
|||||||
iced::stream::channel(0, move |mut sender: mpsc::Sender<Wire>| async move {
|
iced::stream::channel(0, move |mut sender: mpsc::Sender<Wire>| async move {
|
||||||
let mut clock = ClockTicker::new();
|
let mut clock = ClockTicker::new();
|
||||||
let mut interval = tokio::time::interval(std::time::Duration::from_secs(1));
|
let mut interval = tokio::time::interval(std::time::Duration::from_secs(1));
|
||||||
|
tracing::info!(key, "clock service started");
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = interval.tick() => {
|
_ = interval.tick() => {
|
||||||
if let Some(payload) = clock.tick() {
|
if let Some(payload) = clock.tick() {
|
||||||
|
tracing::debug!(key, kind = ?payload.kind, "publishing clock");
|
||||||
let wire = Wire::topic(Endpoint::service(key), key, payload);
|
let wire = Wire::topic(Endpoint::service(key), key, payload);
|
||||||
if sender.send(wire).await.is_err() {
|
if sender.send(wire).await.is_err() {
|
||||||
|
tracing::warn!(key, "clock send failed; stopping service");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,14 @@ pub trait IntoSubscription {
|
|||||||
|
|
||||||
impl IntoSubscription for Service {
|
impl IntoSubscription for Service {
|
||||||
fn into_subscription(self, inbox: Inbox) -> Subscription<Wire> {
|
fn into_subscription(self, inbox: Inbox) -> Subscription<Wire> {
|
||||||
match self.0 {
|
let key = self.0;
|
||||||
key if key.starts_with(datetime::NAMESPACE) => datetime::ClockTicker::run(inbox),
|
match key {
|
||||||
key if key.starts_with(weather::NAMESPACE) => weather::WeatherService::run(inbox),
|
k if k.starts_with(datetime::NAMESPACE) => datetime::ClockTicker::run(inbox),
|
||||||
_ => Subscription::none(),
|
k if k.starts_with(weather::NAMESPACE) => weather::WeatherService::run(inbox),
|
||||||
|
_ => {
|
||||||
|
tracing::warn!(service = key, "no service impl for route key");
|
||||||
|
Subscription::none()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
use std::time::Duration;
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use common::{Inbox, WeatherResponse, Wire};
|
use common::{Endpoint, Inbox, WeatherMsg, WeatherPayload, WeatherResponse, Wire};
|
||||||
use iced::{futures::channel::mpsc, Subscription};
|
use iced::{
|
||||||
|
futures::{channel::mpsc, SinkExt},
|
||||||
|
Subscription,
|
||||||
|
};
|
||||||
use tokio::time::interval;
|
use tokio::time::interval;
|
||||||
|
|
||||||
pub const NAMESPACE: &str = "weather.";
|
pub const NAMESPACE: &str = "weather.";
|
||||||
@@ -12,28 +15,37 @@ const FORECAST_URL: &str = "https://api.open-meteo.com/v1/forecast\
|
|||||||
|
|
||||||
pub struct WeatherService {
|
pub struct WeatherService {
|
||||||
client: reqwest::Client,
|
client: reqwest::Client,
|
||||||
latest: Option<WeatherResponse>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WeatherService {
|
impl WeatherService {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
client: reqwest::Client::default(),
|
client: reqwest::Client::default(),
|
||||||
latest: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn run(inbox: Inbox) -> Subscription<Wire> {
|
pub fn run(inbox: Inbox) -> Subscription<Wire> {
|
||||||
Subscription::run_with(inbox, |inbox| {
|
Subscription::run_with(inbox, |inbox| {
|
||||||
let _key = inbox.key();
|
let key = inbox.key();
|
||||||
let mut _rx = inbox.take().expect("cant take");
|
let mut _rx = inbox.take().expect("cant take");
|
||||||
|
|
||||||
iced::stream::channel(0, move |mut _sender: mpsc::Sender<Wire>| async move {
|
iced::stream::channel(0, move |mut sender: mpsc::Sender<Wire>| async move {
|
||||||
let mut weather = WeatherService::new();
|
let mut weather = WeatherService::new();
|
||||||
let mut interval = interval(Duration::from_mins(30));
|
let mut interval = interval(Duration::from_mins(30));
|
||||||
|
tracing::info!(key = key, "weather service started");
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = interval.tick() => {
|
_ = interval.tick() => {
|
||||||
weather.latest = weather.get_weather().await;
|
if let Some(resp) = weather.get_weather().await {
|
||||||
|
let env = WeatherPayload {
|
||||||
|
kind: WeatherMsg::State,
|
||||||
|
payload: Arc::new(resp),
|
||||||
|
};
|
||||||
|
let wire = Wire::topic(Endpoint::service(key), key, env);
|
||||||
|
if sender.send(wire).await.is_err() {
|
||||||
|
tracing::warn!(key, "weather send failed; stopping service");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,13 +53,22 @@ impl WeatherService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
async fn get_weather(&self) -> Option<WeatherResponse> {
|
async fn get_weather(&self) -> Option<WeatherResponse> {
|
||||||
self.client
|
tracing::debug!(url = FORECAST_URL, "fetching weather");
|
||||||
.get(FORECAST_URL)
|
match self.client.get(FORECAST_URL).send().await {
|
||||||
.send()
|
Ok(resp) => match resp.json::<WeatherResponse>().await {
|
||||||
.await
|
Ok(parsed) => {
|
||||||
.ok()?
|
tracing::debug!("weather fetched");
|
||||||
.json::<WeatherResponse>()
|
Some(parsed)
|
||||||
.await
|
}
|
||||||
.ok()
|
Err(e) => {
|
||||||
|
tracing::warn!(error = %e, "weather response decode failed");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(error = %e, "weather request failed");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user