diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..524032d --- /dev/null +++ b/src/config.rs @@ -0,0 +1,226 @@ +use clap::{App, Arg}; + +#[derive(Debug)] +pub enum Action { + Mem, + Cpu, + Mpris, + Mpd, + Localtime, + Utctime, +} + +impl Default for Action { + fn default() -> Action { + Action::Cpu + } +} + +#[derive(Default, Debug)] +pub struct Config { + pub action: Action, + pub mpd_server: String, + pub lt_format: Option, + pub ut_format: Option, + pub color_low: String, + pub color_mid: String, + pub color_high: String, + pub color_track_name: String, + pub color_track_artist: String, + pub color_track_time: String, + pub color_end: String, +} + +fn colorize(color: String) -> String { + format!("#[fg=colour{}]", color) +} + +pub fn read() -> Config { + // Parse opts and args + let cli_args = App::new(env!("CARGO_PKG_NAME")) + .version(env!("CARGO_PKG_VERSION")) + .author(env!("CARGO_PKG_AUTHORS")) + .about(env!("CARGO_PKG_DESCRIPTION")) + // Flags + .arg( + Arg::with_name("cpu") + .short("c") + .long("cpu") + .help("Print cpu load bar.") + .conflicts_with_all(&["mem", "mpris", "mpd", "localtime", "utctime"]) + .required(false), + ) + .arg( + Arg::with_name("mem") + .short("m") + .long("mem") + .help("Print mem usage bar.") +// .conflicts_with("cpu") +// .conflicts_with("mpris") +// .conflicts_with("mpd") +// .conflicts_with("localtime") +// .conflicts_with("utctime") + .required(false), + ) + .arg( + Arg::with_name("mpris") + .short("p") + .long("mpris") + .help("Show player info using MPRIS2 interface.") +// .conflicts_with("cpu") +// .conflicts_with("mem") +// .conflicts_with("localtime") +// .conflicts_with("mpd") +// .conflicts_with("utctime") + .required(false), + ) + .arg( + Arg::with_name("mpd") + .short("d") + .long("mpd") + .help("Show mpd player using MPD native protocol.") +// .conflicts_with("cpu") +// .conflicts_with("mem") +// .conflicts_with("localtime") +// .conflicts_with("mpris") +// .conflicts_with("utctime") + .required(false), + ) + // Options + .arg( + Arg::with_name("localtime") + .short("l") + .long("localtime") + .help("Local time") +// .conflicts_with_all(&["mem", "mpris", "mpd", "cpu", "utctime"]) + .takes_value(true) + .required(false), + ) + .arg( + Arg::with_name("utctime") + .short("u") + .long("utctime") + .help("UTC time") +// .conflicts_with_all(&["mem", "mpris", "mpd", "cpu", "localtime"]) + .takes_value(true) + .required(false), + ) + .arg( + Arg::with_name("mpd_address") + .short("a") + .long("mpd-address") + .help(": of MPD server.") + .takes_value(true) + .default_value("127.0.0.1:6600") + .required(false), + ) + .arg( + Arg::with_name("COLOR_LOW") + .long("COLOR_LOW") + .help("CPU and MEM bar color while low usage.") + .takes_value(true) + .default_value("119") + .required(false), + ) + .arg( + Arg::with_name("COLOR_MID") + .long("COLOR_MID") + .help("CPU and MEM bar color while mid usage.") + .takes_value(true) + .default_value("220") + .required(false), + ) + .arg( + Arg::with_name("COLOR_HIGH") + .long("COLOR_HIGH") + .help("CPU and MEM bar color while high usage.") + .takes_value(true) + .default_value("197") + .required(false), + ) + .arg( + Arg::with_name("COLOR_TRACK_NAME") + .long("COLOR_TRACK_NAME") + .help("Color of track name filed.") + .takes_value(true) + .default_value("46") + .required(false), + ) + .arg( + Arg::with_name("COLOR_TRACK_ARTIST") + .long("COLOR_TRACK_ARTIST") + .help("Color of artist name filed.") + .takes_value(true) + .default_value("46") + .required(false), + ) + .arg( + Arg::with_name("COLOR_TRACK_TIME") + .long("COLOR_TRACK_TIME") + .help("Color of playing time field.") + .takes_value(true) + .default_value("153") + .required(false), + ) + .arg( + Arg::with_name("COLOR_END") + .long("COLOR_END") + .help("Default color using to terminate others.") + .takes_value(true) + .default_value("153") + .required(false), + ) + .get_matches(); + + // cpu - cpu usage bar + // mem - mem usage bar + // mpris - player info using MPRIS2 interface + // mpd - player info using MPD native interface + // utctime - utc time + // localtime - local time + // lt_format - local time format + // ut_format - utc time format + + let lt_format = Some(match cli_args.value_of("localtime") { + Some(format) => format.to_string(), + None => "%H:%M".to_string(), + }); + let ut_format = Some(match cli_args.value_of("utctime") { + Some(format) => format.to_string(), + None => "%H:%M".to_string(), + }); + + let mut cfg = Config { + action: Action::Cpu, + mpd_server: cli_args.value_of("mpd_address").unwrap().to_string(), + lt_format: lt_format, + ut_format: ut_format, + color_low: colorize(cli_args.value_of("COLOR_LOW").unwrap().to_string()), + color_mid: colorize(cli_args.value_of("COLOR_MID").unwrap().to_string()), + color_high: colorize(cli_args.value_of("COLOR_HIGH").unwrap().to_string()), + color_track_name: colorize(cli_args.value_of("COLOR_TRACK_NAME").unwrap().to_string()), + color_track_artist: colorize(cli_args.value_of("COLOR_TRACK_ARTIST").unwrap().to_string()), + color_track_time: colorize(cli_args.value_of("COLOR_TRACK_TIME").unwrap().to_string()), + color_end: colorize(cli_args.value_of("COLOR_END").unwrap().to_string()), + }; + + if cli_args.is_present("cpu") { + cfg.action = Action::Cpu; + } + if cli_args.is_present("mem") { + cfg.action = Action::Mem; + } + if cli_args.is_present("localtime") { + cfg.action = Action::Localtime; + } + if cli_args.is_present("utctime") { + cfg.action = Action::Utctime; + } + if cli_args.is_present("mpris") { + cfg.action = Action::Mpris; + } + if cli_args.is_present("mpd") { + cfg.action = Action::Mpd; + } + cfg +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..1a3b2ac --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,267 @@ +use crate::config; +use crate::dbus::blocking::stdintf::org_freedesktop_dbus::Properties; +use chrono::{DateTime, Local, Utc}; +use dbus::{arg, blocking::Connection}; +use std::time::Duration; +use sys_info; +use mpd::Client; +use std::process; + +#[derive(Debug, Clone)] +pub struct TrackInfo { + title: String, + artist: String, + position: String, + duration: String, + status: String, +} + +pub fn to_bar(value: i32, max: i32, low: f32, mid: f32, config: &config::Config) { + let mut bar = "".to_string(); + let bar_sym = "▮".to_string(); + if (value as f32) / (max as f32) < low { + bar.push_str(&config.color_low); + } else if (value as f32) / (max as f32) < mid { + bar.push_str(&config.color_mid); + } else { + bar.push_str(&config.color_high); + } + for i in 0..max { + if i < value as i32 { + bar.push_str(&bar_sym); + } else { + bar.push_str(" ") + } + } + bar.push_str(&config.color_end); + bar.push_str("|"); + print!("{}", bar) +} + +pub fn mem_load_bar(bar_len: i32, config: &config::Config) { + let memory; + match sys_info::mem_info() { + Err(w) => panic!("{:?}", w), + Ok(mem_data) => memory = mem_data, + } + let len = + ((memory.total - memory.avail) as f32 / (memory.total as f32) * bar_len as f32) as i32; + to_bar(len, bar_len, 0.7, 0.9, config); + print!("{:.0} MiB#[default]", memory.avail / 1024); +} + +pub fn cpu_load_bar(bar_len: i32, config: &config::Config) { + let cpu_count = match sys_info::cpu_num() { + Ok(c) => c, + Err(e) => panic!("{:?}", e), + }; + let la_one: f32 = match sys_info::loadavg() { + Ok(l) => l.one as f32, + Err(e) => panic!("{:?}", e), + }; + let len: f32 = la_one as f32 / cpu_count as f32 * bar_len as f32; + to_bar(len as i32, bar_len, 0.3, 0.7, config); + print!("{:.2} LA1#[default]", la_one); +} + +pub fn get_player() -> Result, Box> { + let conn = Connection::new_session()?; + let proxy = conn.with_proxy("org.freedesktop.DBus", "/", Duration::from_millis(5000)); + let (names,): (Vec,) = proxy.method_call("org.freedesktop.DBus", "ListNames", ())?; + let mut players: Vec = Vec::new(); + for name in names { + if name.contains("org.mpris.MediaPlayer2") { + players.push(name); + } + } + + Ok(players) +} + +pub fn player_info(players: Vec) -> Result> { + let mut players_vec: Vec = Vec::new(); + for player in players { + let mut track_info = TrackInfo { + artist: "".to_string(), + title: "".to_string(), + position: "".to_string(), + duration: "".to_string(), + status: "".to_string(), + }; + let conn = Connection::new_session()?; + let proxy = conn.with_proxy( + player, + "/org/mpris/MediaPlayer2", + Duration::from_millis(5000), + ); + let metadata: Box = + proxy.get("org.mpris.MediaPlayer2.Player", "Metadata")?; + let mut iter = metadata.as_iter().unwrap(); + while let Some(key) = iter.next() { + if key.as_str() == Some("xesam:title") { + if let Some(title) = iter.next().unwrap().as_str() { + track_info.title = title.to_string(); + } + } + if key.as_str() == Some("mpris:length") { + if let Some(length) = iter.next().unwrap().as_i64() { + track_info.duration = format_time(length / 1000000); + } + } + if key.as_str() == Some("xesam:artist") { + if let Some(mut artists) = iter.next().unwrap().as_iter() { + while let Some(artist) = artists.next() { + if let Some(mut line) = artist.as_iter() { + track_info.artist = line.next().unwrap().as_str().unwrap().to_string(); + } + } + } + } + } + let position: Box = + proxy.get("org.mpris.MediaPlayer2.Player", "Position")?; + track_info.position = format_time(position.as_i64().unwrap() / 1000000); + // ugly + let _status_text_box: Box = + proxy.get("org.mpris.MediaPlayer2.Player", "PlaybackStatus")?; + let _status_text = _status_text_box.as_str().unwrap(); + match _status_text.as_ref() { + "Playing" => track_info.status = "▶".to_string(), + "Paused" => track_info.status = "⏸".to_string(), + _ => track_info.status = "⏹".to_string(), + }; + players_vec.push(track_info); + } + for player in &players_vec { + if player.status == "▶".to_string() { + return Ok(player.clone()); + } + } + Ok(players_vec[players_vec.len() - 1].clone()) +} + +pub fn format_time(sec: i64) -> String { + let minutes = sec / 60; + let secondes = sec % 60; + let result = format!("{:02}:{:02}", minutes, secondes); + result.to_string() +} + +pub fn get_time(utc: bool, format: Option) { + // Format reference: https://docs.rs/chrono/0.4.10/chrono/format/strftime/index.html + let fmt = match format { + Some(format) => format, + None => "%H:%M".to_string(), + }; + + if utc { + let local_time = Local::now(); + let utc_time = DateTime::::from_utc(local_time.naive_utc(), Utc); + println!("{}", utc_time.format(&fmt)); + } else { + let local_time = Local::now(); + println!("{}", local_time.format(&fmt)); + } +} + +fn format_player(track_info: TrackInfo, config: &config::Config) { + let mut title_len = 30; + let mut artist_len = 30; + let mut artist_line: String = String::new(); + let mut title_line: String = String::new(); + let mut separator: String = " — ".to_string(); + let max_shift = 6; + if track_info.artist.chars().count() == 0 { + separator = "".to_string(); + title_len += artist_len; + } + if artist_len + max_shift >= track_info.artist.chars().count() { + artist_len = track_info.artist.chars().count() + } + if track_info.artist.len() > artist_len { + let mut counter = 0; + for ch in track_info.artist.chars() { + if counter == artist_len { + artist_line.push_str(".."); + break; + } + artist_line.push(ch); + counter += 1; + } + + } + if title_len + max_shift >= track_info.title.chars().count() { + title_len = track_info.title.chars().count() + } + if track_info.title.len() > title_len { + let mut counter = 0; + for ch in track_info.title.chars() { + if counter == title_len { + title_line.push_str(".."); + break; + } + title_line.push(ch); + counter += 1; + } + } + println!( + "#[none]#[bold]{}{}{}#[none]{}{}{}{} {}[{}/{}] {} {}#[default]", + config.color_track_name, + title_line, + config.color_end, + separator, + config.color_track_artist, + artist_line, + config.color_end, + config.color_track_time, + track_info.position, + track_info.duration, + track_info.status, + config.color_end, + ); +} + +pub fn mpris(config: &config::Config) { + match player_info(get_player().unwrap()) { + Ok(track_info) => format_player(track_info, config), + Err(_e) => println!("No music playing"), + } +} + +pub fn mpd(config: &config::Config) { + let mut conn = match Client::connect(&config.mpd_server) { + Ok(conn) => conn, + Err(e) => {println!("Can't connect to MPD server. {}", e); process::exit(0x0001)} + }; + let mut track_info = TrackInfo { + title: String::new(), + artist: String::new(), + position: String::new(), + duration: String::new(), + status: String::new(), + }; + if let Some(song) = conn.currentsong().unwrap() { + if let Some(title) = song.title { + track_info.title = title + } + if let Some(artist) = song.tags.get("Artist") { + track_info.artist = artist.to_string() + } + } + if let Some(time) = conn.status().unwrap().time { + track_info.position = time.0.num_seconds().to_string(); + track_info.duration = time.1.num_seconds().to_string() + } + let status = match conn.status() { + Ok(status) => { + match status.state { + mpd::State::Play => "▶".to_string(), + mpd::State::Pause => "⏸".to_string(), + mpd::State::Stop => "⏹".to_string(), + } + } + Err(_) => {"⏹".to_string()}, + }; + track_info.status = status; + format_player(track_info, config) +}