tracing and changes
This commit is contained in:
@@ -47,7 +47,7 @@ pub struct HourlyUnits {
|
||||
pub relative_humidity_2m: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Hourly {
|
||||
#[serde(deserialize_with = "naive_dt::deserialize")]
|
||||
pub time: Vec<chrono::NaiveDateTime>,
|
||||
|
||||
@@ -11,6 +11,8 @@ common = { path = "../common" }
|
||||
iced = { workspace = true }
|
||||
iced_layershell = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
clap = {workspace = true}
|
||||
toml = {workspace =true}
|
||||
|
||||
@@ -39,10 +39,16 @@ impl Bar {
|
||||
.collect();
|
||||
// One inbox per distinct service any module wants.
|
||||
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()
|
||||
.map(|key| (key, Inbox::new(key)))
|
||||
.collect();
|
||||
tracing::debug!(
|
||||
modules = modules.len(),
|
||||
services = ?inputs.keys().copied().collect::<Vec<_>>(),
|
||||
"spawning wanted services"
|
||||
);
|
||||
Self {
|
||||
active_popup: None,
|
||||
modules,
|
||||
|
||||
+22
-6
@@ -27,27 +27,43 @@ pub(crate) use msg::{Message, Msg};
|
||||
/// Height of the bar in logical pixels.
|
||||
const BAR_HEIGHT: u32 = 36;
|
||||
|
||||
fn main() -> Result<(), iced_layershell::Error> {
|
||||
let args = args::Args::parse();
|
||||
/// Default log filter per build profile. Debug builds log barbar's own crates
|
||||
/// 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;
|
||||
if let Ok(mut config_file) = config_handle {
|
||||
let mut string = String::new();
|
||||
config_file.read_to_string(&mut string).unwrap();
|
||||
config = toml::from_str(&string).unwrap();
|
||||
tracing::info!(config = %args.config.display(), "config loaded");
|
||||
} else {
|
||||
println!("cant open config file");
|
||||
tracing::warn!(config = %args.config.display(), "cannot open config file");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let start_mode = match config.monitor {
|
||||
Some(x) => StartMode::TargetScreen(x),
|
||||
let start_mode = match &config.monitor {
|
||||
Some(x) => StartMode::TargetScreen(x.clone()),
|
||||
None => StartMode::Active,
|
||||
};
|
||||
|
||||
let modules = config.modules.clone().unwrap_or_default();
|
||||
let order = config.order;
|
||||
tracing::info!(monitor = ?config.monitor, "starting barbar daemon");
|
||||
|
||||
daemon(
|
||||
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);
|
||||
bar.popup_id = Some(id);
|
||||
tracing::debug!(module = %module_id, ?id, size = ?(w, h), "popup opened");
|
||||
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.
|
||||
pub fn close_popup(bar: &mut Bar) -> Task<Message> {
|
||||
if let Some(id) = bar.popup_id {
|
||||
tracing::debug!(?id, "requesting popup close");
|
||||
return Task::done(Message::Effect(ModuleEffect::ClosePopup(id)));
|
||||
}
|
||||
Task::none()
|
||||
|
||||
@@ -26,6 +26,7 @@ fn handle_event(bar: &mut Bar, event: Msg) -> Task<Message> {
|
||||
if bar.popup_id == Some(id) {
|
||||
bar.popup_id = None;
|
||||
bar.active_popup = None;
|
||||
tracing::debug!(?id, "popup window closed");
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
@@ -37,12 +38,19 @@ fn route(bar: &mut Bar, wire: Wire) -> Task<Message> {
|
||||
match wire.to {
|
||||
Target::Module(id) => match bar.modules.get_mut(id) {
|
||||
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) => {
|
||||
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()
|
||||
}
|
||||
@@ -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
|
||||
// downcasts it. New services never touch this file.
|
||||
let topic = Service(key);
|
||||
bar.modules
|
||||
let tasks: Vec<_> = bar
|
||||
.modules
|
||||
.iter_mut()
|
||||
.filter(|(id, _)| {
|
||||
bar.routes
|
||||
@@ -59,7 +68,9 @@ fn route(bar: &mut Bar, wire: Wire) -> Task<Message> {
|
||||
.is_some_and(|keys| keys.contains(&topic))
|
||||
})
|
||||
.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) => {
|
||||
// Toggle: close if already open for this module, else open.
|
||||
if bar.active_popup.as_deref() == Some(module_id.as_str()) {
|
||||
tracing::debug!(module = %module_id, "closing popup");
|
||||
popup::close_popup(bar)
|
||||
} else {
|
||||
tracing::debug!(module = %module_id, "opening popup");
|
||||
popup::capture_bounds(module_id, element_id)
|
||||
}
|
||||
}
|
||||
|
||||
+34
-6
@@ -1,5 +1,5 @@
|
||||
use common::Wire;
|
||||
use iced::widget::{container, row, text};
|
||||
use iced::widget::{container, row, space, text};
|
||||
use iced::{window, Alignment, Element, Length, Theme};
|
||||
|
||||
use crate::app::Bar;
|
||||
@@ -33,21 +33,49 @@ fn bar_view(bar: &Bar) -> Element<'_, Message> {
|
||||
.view(None)
|
||||
.map(|w| Message::Event(Msg::Wire(w)))
|
||||
})
|
||||
.collect::<Vec<Element<Message>>>());
|
||||
.collect::<Vec<Element<Message>>>())
|
||||
.spacing(8);
|
||||
|
||||
let middle = row(bar
|
||||
.order
|
||||
.1
|
||||
.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
|
||||
.order
|
||||
.2
|
||||
.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)
|
||||
.align_x(Alignment::Center)
|
||||
.width(Length::Fill)
|
||||
.align_y(Alignment::Center)
|
||||
.into()
|
||||
}
|
||||
|
||||
|
||||
@@ -11,5 +11,7 @@ path = "src/lib.rs"
|
||||
common = { path = "../common" }
|
||||
iced = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
serde = {workspace = true}
|
||||
|
||||
@@ -29,7 +29,7 @@ pub struct Clock {
|
||||
#[derive(Deserialize, Default)]
|
||||
struct ClockConfig {
|
||||
#[serde(default)]
|
||||
format: bool,
|
||||
show_seconds: bool,
|
||||
}
|
||||
|
||||
impl Clock {
|
||||
@@ -37,7 +37,7 @@ impl Clock {
|
||||
match config_clock(config) {
|
||||
Some(x) => Self {
|
||||
value: "--:--:--".to_string(),
|
||||
show_seconds: x.format,
|
||||
show_seconds: x.show_seconds,
|
||||
},
|
||||
None => Self::default(),
|
||||
}
|
||||
@@ -102,6 +102,7 @@ impl BarModule for Clock {
|
||||
match msg.downcast::<ClockMsg>() {
|
||||
Some(ClockMsg::ToggleSeconds) => {
|
||||
self.show_seconds = !self.show_seconds;
|
||||
tracing::debug!(show_seconds = self.show_seconds, "clock toggle");
|
||||
let want = if self.show_seconds {
|
||||
ClockKind::Seconds
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
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::{
|
||||
widget::{container, mouse_area, text},
|
||||
Element, Task,
|
||||
@@ -8,11 +12,37 @@ use iced::{
|
||||
|
||||
pub struct WeatherModule {
|
||||
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 {
|
||||
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> {
|
||||
// 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>() {
|
||||
Some(WeatherKind::Poke) => {
|
||||
tracing::debug!("weather poked");
|
||||
self.text = "poked".into();
|
||||
Task::none()
|
||||
}
|
||||
None => Task::none(),
|
||||
None => (),
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Route keys this module subscribes to.
|
||||
fn services(&self) -> Vec<Service> {
|
||||
vec![WeatherMsg::State.key()]
|
||||
vec![WeatherMsg::State.key(), ClockKind::Seconds.key()]
|
||||
}
|
||||
|
||||
/// Optional: read `module.weather` from the config table.
|
||||
|
||||
@@ -11,6 +11,7 @@ path = "src/lib.rs"
|
||||
common = { path = "../common" }
|
||||
iced = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -39,12 +39,15 @@ impl ClockTicker {
|
||||
iced::stream::channel(0, move |mut sender: mpsc::Sender<Wire>| async move {
|
||||
let mut clock = ClockTicker::new();
|
||||
let mut interval = tokio::time::interval(std::time::Duration::from_secs(1));
|
||||
tracing::info!(key, "clock service started");
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = interval.tick() => {
|
||||
if let Some(payload) = clock.tick() {
|
||||
tracing::debug!(key, kind = ?payload.kind, "publishing clock");
|
||||
let wire = Wire::topic(Endpoint::service(key), key, payload);
|
||||
if sender.send(wire).await.is_err() {
|
||||
tracing::warn!(key, "clock send failed; stopping service");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,14 @@ pub trait IntoSubscription {
|
||||
|
||||
impl IntoSubscription for Service {
|
||||
fn into_subscription(self, inbox: Inbox) -> Subscription<Wire> {
|
||||
match self.0 {
|
||||
key if key.starts_with(datetime::NAMESPACE) => datetime::ClockTicker::run(inbox),
|
||||
key if key.starts_with(weather::NAMESPACE) => weather::WeatherService::run(inbox),
|
||||
_ => Subscription::none(),
|
||||
let key = self.0;
|
||||
match key {
|
||||
k if k.starts_with(datetime::NAMESPACE) => datetime::ClockTicker::run(inbox),
|
||||
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 iced::{futures::channel::mpsc, Subscription};
|
||||
use common::{Endpoint, Inbox, WeatherMsg, WeatherPayload, WeatherResponse, Wire};
|
||||
use iced::{
|
||||
futures::{channel::mpsc, SinkExt},
|
||||
Subscription,
|
||||
};
|
||||
use tokio::time::interval;
|
||||
|
||||
pub const NAMESPACE: &str = "weather.";
|
||||
@@ -12,28 +15,37 @@ const FORECAST_URL: &str = "https://api.open-meteo.com/v1/forecast\
|
||||
|
||||
pub struct WeatherService {
|
||||
client: reqwest::Client,
|
||||
latest: Option<WeatherResponse>,
|
||||
}
|
||||
|
||||
impl WeatherService {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
client: reqwest::Client::default(),
|
||||
latest: None,
|
||||
}
|
||||
}
|
||||
pub fn run(inbox: Inbox) -> Subscription<Wire> {
|
||||
Subscription::run_with(inbox, |inbox| {
|
||||
let _key = inbox.key();
|
||||
let key = inbox.key();
|
||||
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 interval = interval(Duration::from_mins(30));
|
||||
tracing::info!(key = key, "weather service started");
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = 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> {
|
||||
self.client
|
||||
.get(FORECAST_URL)
|
||||
.send()
|
||||
.await
|
||||
.ok()?
|
||||
.json::<WeatherResponse>()
|
||||
.await
|
||||
.ok()
|
||||
tracing::debug!(url = FORECAST_URL, "fetching weather");
|
||||
match self.client.get(FORECAST_URL).send().await {
|
||||
Ok(resp) => match resp.json::<WeatherResponse>().await {
|
||||
Ok(parsed) => {
|
||||
tracing::debug!("weather fetched");
|
||||
Some(parsed)
|
||||
}
|
||||
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