231 lines
7 KiB
Rust
231 lines
7 KiB
Rust
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<TileConfig>,
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, Debug, Default)]
|
|
pub struct DefaultSection {
|
|
pub spacing: Option<u32>,
|
|
}
|
|
|
|
fn deserialize_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
|
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<str>,
|
|
#[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<str>,
|
|
#[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<str>,
|
|
#[default("%H:%M:%S")]
|
|
pub short_format: Box<str>,
|
|
// 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<str>,
|
|
#[default("%H:%M:%S %Z")]
|
|
pub short_format: Box<str>,
|
|
// 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<str>,
|
|
#[default("%H:%M:%S %Z")]
|
|
pub short_format: Box<str>,
|
|
pub geoip_path: Option<Box<Path>>,
|
|
#[serde(deserialize_with = "deserialize_duration")]
|
|
#[default(_code = "Duration::from_secs(3600)")]
|
|
pub update_interval: Duration,
|
|
pub fallback_zone: Option<Box<str>>,
|
|
#[default("https://myip.wtf/text")]
|
|
pub ip_addr_api: Box<str>,
|
|
// 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<PathBuf>,
|
|
}
|
|
|
|
pub async fn read_config() -> eyre::Result<Config> {
|
|
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<TileData>,
|
|
tile_id: usize,
|
|
dbus_conn: Arc<SyncConnection>,
|
|
) -> 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),
|
|
}
|
|
})
|
|
}
|