2020-06-05 01:04:06 +00:00
|
|
|
use crate::tile::Block;
|
2020-06-04 03:05:45 +00:00
|
|
|
use crate::tiles;
|
2020-06-14 05:37:32 +00:00
|
|
|
use futures::{stream::BoxStream, Stream};
|
|
|
|
use serde::{Deserialize, Deserializer};
|
2020-06-04 03:05:45 +00:00
|
|
|
use smart_default::SmartDefault;
|
|
|
|
use std::env::var;
|
2020-06-14 05:37:32 +00:00
|
|
|
use std::error::Error;
|
2020-06-04 03:05:45 +00:00
|
|
|
use std::io;
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use structopt::StructOpt;
|
|
|
|
use tokio::fs::File;
|
|
|
|
use tokio::prelude::*;
|
2020-06-14 05:37:32 +00:00
|
|
|
use tokio::time::{self, Duration};
|
2020-06-04 03:05:45 +00:00
|
|
|
|
|
|
|
#[derive(Deserialize, Clone, Debug, Default)]
|
|
|
|
#[serde(default)]
|
|
|
|
pub struct Config {
|
|
|
|
pub default: DefaultSection,
|
|
|
|
pub tile: Box<[TileConfig]>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize, Clone, Debug, Default)]
|
|
|
|
pub struct DefaultSection {
|
|
|
|
pub spacing: Option<u32>,
|
|
|
|
}
|
|
|
|
|
2020-06-14 05:37:32 +00:00
|
|
|
fn deserialize_duration<'de, D>(deserializer: D) -> Result<Option<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(Some(duration))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize, Clone, Debug)]
|
|
|
|
pub struct TileConfig {
|
|
|
|
#[serde(flatten)]
|
|
|
|
config_type: TileConfigType,
|
|
|
|
#[serde(deserialize_with = "deserialize_duration", default)]
|
|
|
|
update: Option<Duration>,
|
|
|
|
}
|
|
|
|
|
2020-06-04 03:05:45 +00:00
|
|
|
#[derive(Deserialize, Clone, Debug)]
|
|
|
|
#[serde(tag = "type", rename_all = "snake_case")]
|
2020-06-14 05:37:32 +00:00
|
|
|
pub enum TileConfigType {
|
2020-06-16 00:48:49 +00:00
|
|
|
Battery,
|
2020-06-04 03:05:45 +00:00
|
|
|
Memory,
|
|
|
|
Load,
|
|
|
|
Hostname,
|
|
|
|
Time(TimeConfig),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(SmartDefault, Deserialize, Clone, Debug)]
|
|
|
|
#[serde(default)]
|
|
|
|
pub struct TimeConfig {
|
|
|
|
#[default("%Y-%m-%d %H:%M:%S")]
|
|
|
|
pub format: Box<str>,
|
|
|
|
#[default("%H:%M:%S")]
|
|
|
|
pub short_format: Box<str>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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() -> Result<Config, Box<dyn std::error::Error>> {
|
|
|
|
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 {
|
|
|
|
return Err(Box::new(io::Error::new(
|
|
|
|
io::ErrorKind::NotFound,
|
|
|
|
"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_slice(&config_contents)?)
|
|
|
|
}
|
|
|
|
|
2020-06-05 01:04:06 +00:00
|
|
|
pub fn process_tile(
|
|
|
|
tile: &TileConfig,
|
|
|
|
) -> BoxStream<'static, Result<Block, Box<dyn std::error::Error + Send + Sync>>> {
|
2020-06-14 05:37:32 +00:00
|
|
|
let five_secs = Duration::from_secs(5);
|
|
|
|
match &tile.config_type {
|
2020-06-16 00:48:49 +00:00
|
|
|
TileConfigType::Battery => wrap(tiles::battery_stream(), tile.update.or(Some(five_secs))),
|
2020-06-14 05:37:32 +00:00
|
|
|
TileConfigType::Hostname => wrap(tiles::hostname_stream(), tile.update),
|
|
|
|
TileConfigType::Load => wrap(tiles::load_stream(), tile.update.or(Some(five_secs))),
|
|
|
|
TileConfigType::Memory => wrap(tiles::memory_stream(), tile.update.or(Some(five_secs))),
|
|
|
|
TileConfigType::Time(c) => wrap(tiles::time_stream(c.clone()), tile.update),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn wrap<'a, S>(
|
|
|
|
stream: S,
|
|
|
|
duration: Option<Duration>,
|
|
|
|
) -> BoxStream<'a, Result<Block, Box<dyn Error + Send + Sync>>>
|
|
|
|
where
|
|
|
|
S: Stream<Item = Result<Block, Box<dyn Error + Send + Sync>>> + Send + 'a,
|
|
|
|
{
|
|
|
|
match duration {
|
|
|
|
Some(duration) => Box::pin(time::throttle(duration, stream)),
|
|
|
|
None => Box::pin(stream),
|
2020-06-04 03:05:45 +00:00
|
|
|
}
|
|
|
|
}
|