use crate::output::OutputChannel; use crate::tile::TileData; use crate::tiles; use dbus::nonblock::SyncConnection; use log::error; use log::warn; use serde::{Deserialize, Deserializer}; use smart_default::SmartDefault; use std::env::var; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; use structopt::StructOpt; use tokio::fs::File; use tokio::io::AsyncReadExt; use tokio::sync::mpsc; use tokio::task::JoinHandle; #[derive(Deserialize, Clone, Debug, Default)] #[serde(default)] pub struct Config { pub default: DefaultSection, pub tile: Vec, } #[derive(Deserialize, Clone, Debug, Default)] pub struct DefaultSection { pub spacing: Option, } fn deserialize_duration<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; let number = s.trim_end_matches(char::is_alphabetic); let suffix = s.trim_start_matches(number); let number: f64 = number.parse().expect("Not a valid f64"); let duration = match suffix { "s" | "" => Duration::from_secs_f64(number), "m" => Duration::from_secs_f64(number * 60f64), "ms" => Duration::from_secs_f64(number / 1000f64), _ => unimplemented!(), }; Ok(duration) } #[derive(Deserialize, Clone, Debug)] #[serde(tag = "type", rename_all = "snake_case")] pub enum TileConfig { Battery(BatteryConfig), Memory(MemoryConfig), Load(LoadConfig), Hostname(HostnameConfig), UtcTime(UtcTimeConfig), SystemTime(SystemTimeConfig), LocalTime(LocalTimeConfig), InternetTime(InternetTimeConfig), Iwd(IwdConfig), NetworkManager(NetworkManagerConfig), } #[derive(SmartDefault, Deserialize, Clone, Debug)] #[serde(default)] pub struct BatteryConfig { #[default("BAT0")] pub battery: Box, #[serde(deserialize_with = "deserialize_duration")] #[default(_code = "Duration::from_secs(30)")] pub update: Duration, } #[derive(SmartDefault, Deserialize, Clone, Debug)] #[serde(default)] pub struct MemoryConfig { #[serde(deserialize_with = "deserialize_duration")] #[default(_code = "Duration::from_secs(30)")] pub update: Duration, } #[derive(SmartDefault, Deserialize, Clone, Debug)] #[serde(default)] pub struct LoadConfig { #[serde(deserialize_with = "deserialize_duration")] #[default(_code = "Duration::from_secs(30)")] pub update: Duration, } #[derive(SmartDefault, Deserialize, Clone, Debug)] #[serde(default)] pub struct HostnameConfig { #[serde(deserialize_with = "deserialize_duration")] #[default(_code = "Duration::from_secs(3600)")] pub update: Duration, } #[derive(SmartDefault, Deserialize, Clone, Debug)] #[serde(default)] pub struct IwdConfig { #[default("wlan0")] pub interface: Box, #[serde(deserialize_with = "deserialize_duration")] #[default(_code = "Duration::from_secs(5)")] pub update: Duration, } #[derive(SmartDefault, Deserialize, Clone, Debug)] #[serde(default)] pub struct UtcTimeConfig { #[default("%Y-%m-%d %H:%M:%S")] pub format: Box, #[default("%H:%M:%S")] pub short_format: Box, // No update field, update on the minute or second } #[derive(SmartDefault, Deserialize, Clone, Debug)] #[serde(default)] pub struct SystemTimeConfig { #[default("%Y-%m-%d %H:%M:%S %Z")] pub format: Box, #[default("%H:%M:%S %Z")] pub short_format: Box, // No update field, update on the minute or second } #[derive(SmartDefault, Deserialize, Clone, Debug)] #[serde(default)] pub struct LocalTimeConfig { #[default("%Y-%m-%d %H:%M:%S %Z")] pub format: Box, #[default("%H:%M:%S %Z")] pub short_format: Box, pub geoip_path: Option>, #[serde(deserialize_with = "deserialize_duration")] #[default(_code = "Duration::from_secs(3600)")] pub update_interval: Duration, pub fallback_zone: Option>, #[default("https://myip.wtf/text")] pub ip_addr_api: Box, // No update field, update on the minute or second } #[derive(SmartDefault, Deserialize, Clone, Debug)] #[serde(default)] pub struct InternetTimeConfig { #[default(2)] pub precision: u8, // No update field, update on the, uuh, wtf is metric time } #[derive(SmartDefault, Deserialize, Clone, Debug)] #[serde(default)] pub struct NetworkManagerConfig { #[default(true)] pub use_symbols: bool, // No update field, we listen for events } #[derive(Debug, StructOpt)] #[structopt( name = "rustybar", about = "Something to exec for your swaybar or i3bar" )] struct Args { /// Configuration file, default is $XDG_CONFIG_HOME/rustybar/config.toml #[structopt(short, long, parse(from_os_str))] pub config: Option, } pub async fn read_config() -> eyre::Result { let args = Args::from_args(); let config_path = match args.config { Some(config) => config, None => { if let Ok(rustybar_config_env) = var("RUSTYBAR_CONFIG") { rustybar_config_env.into() } else if let Ok(xdg_config_home) = var("XDG_CONFIG_HOME") { [&xdg_config_home, "rustybar", "config.toml"] .iter() .collect() } else if let Ok(home) = var("HOME") { [&home, ".config", "rustybar", "config.toml"] .iter() .collect() } else { eyre::bail!("Could not find RUSTYBAR_CONFIG, XDG_CONFIG_HOME, or HOME environment variables"); } } }; let mut config_contents = vec![]; File::open(config_path) .await? .read_to_end(&mut config_contents) .await?; Ok(toml::from_str(std::str::from_utf8(&config_contents)?)?) } pub fn launch_tile( tile: TileConfig, sender: mpsc::Sender, tile_id: usize, dbus_conn: Arc, ) -> JoinHandle<()> { tokio::spawn(async move { let output_chan = OutputChannel::with_random_uuid(sender, tile_id); let result = match tile { TileConfig::Battery(c) => tiles::battery(c, output_chan).await, TileConfig::Hostname(c) => tiles::hostname(c, output_chan, dbus_conn).await, TileConfig::Load(c) => tiles::load(c, output_chan).await, TileConfig::Memory(c) => tiles::memory(c, output_chan).await, TileConfig::UtcTime(c) => tiles::utc_time(c, output_chan).await, TileConfig::Iwd(c) => tiles::iwd(c, output_chan, dbus_conn).await, TileConfig::SystemTime(c) => tiles::system_time(c, output_chan).await, TileConfig::LocalTime(c) => tiles::local_time(c, output_chan).await, TileConfig::InternetTime(c) => tiles::internet_time(c, output_chan).await, TileConfig::NetworkManager(c) => { tiles::network_manager(c, output_chan, dbus_conn).await } }; match result { Ok(_) => warn!("Tile {} exited without error", tile_id), Err(e) => error!("Tile {} exited with error: {:?}", tile_id, e), } }) }