From 25fe327294e34b87d8690f7d6818d081cb949123 Mon Sep 17 00:00:00 2001 From: Ultradesu Date: Mon, 31 Mar 2025 16:18:21 +0100 Subject: [PATCH] Bump rust and libs --- Cargo.toml | 2 +- src/config.rs | 175 ++++++++++---------------- src/main.rs | 24 ++-- src/utils.rs | 331 ++++++++++++++++++-------------------------------- 4 files changed, 189 insertions(+), 343 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b77bf58..1a244ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "tmux-helper" version = "0.3.4" description = "Utility for printing system info for tmux status line." authors = ["Ultra Desu "] -edition = "2018" +edition = "2021" [dependencies] sys-info = "*" diff --git a/src/config.rs b/src/config.rs index 920ca65..20f5cc9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use clap::{App, Arg}; +use clap::{Arg, Command}; #[derive(Debug)] pub enum Action { @@ -36,191 +36,142 @@ fn colorize(color: String) -> String { } pub fn read() -> Config { - // Parse opts and args - let cli_args = App::new(env!("CARGO_PKG_NAME")) + let cli_args = Command::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") + Arg::new("cpu") + .short('c') .long("cpu") .help("Print cpu load bar.") - .conflicts_with_all(&["mem", "mpris", "mpd", "localtime", "utctime"]) - .required(false), + .action(clap::ArgAction::SetTrue) + .conflicts_with_all(["mem", "mpris", "mpd", "localtime", "utctime"]), ) .arg( - Arg::with_name("mem") - .short("m") + Arg::new("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), + .action(clap::ArgAction::SetTrue) + .help("Print mem usage bar."), ) .arg( - Arg::with_name("mpris") - .short("p") + Arg::new("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), + .action(clap::ArgAction::SetTrue) + .help("Show player info using MPRIS2 interface."), ) .arg( - Arg::with_name("mpd") - .short("d") + Arg::new("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), + .action(clap::ArgAction::SetTrue) + .help("Show mpd player using MPD native protocol."), ) - // Options .arg( - Arg::with_name("localtime") - .short("l") + Arg::new("localtime") + .short('l') .long("localtime") .help("Local time") - // .conflicts_with_all(&["mem", "mpris", "mpd", "cpu", "utctime"]) - .takes_value(true) - .required(false), + .num_args(0..=1) + .default_missing_value("%H:%M"), ) .arg( - Arg::with_name("utctime") - .short("u") + Arg::new("utctime") + .short('u') .long("utctime") .help("UTC time") - // .conflicts_with_all(&["mem", "mpris", "mpd", "cpu", "localtime"]) - .takes_value(true) - .required(false), + .num_args(0..=1) + .default_missing_value("%H:%M"), ) .arg( - Arg::with_name("mpd_address") - .short("a") + Arg::new("mpd_address") + .short('a') .long("mpd-address") .help(": of MPD server.") - .takes_value(true) - .default_value("127.0.0.1:6600") - .required(false), + .default_value("127.0.0.1:6600"), ) .arg( - Arg::with_name("COLOR_LOW") + Arg::new("COLOR_LOW") .long("COLOR_LOW") .help("CPU and MEM bar color while low usage.") - .takes_value(true) - .default_value("119") - .required(false), + .default_value("119"), ) .arg( - Arg::with_name("COLOR_MID") + Arg::new("COLOR_MID") .long("COLOR_MID") .help("CPU and MEM bar color while mid usage.") - .takes_value(true) - .default_value("220") - .required(false), + .default_value("220"), ) .arg( - Arg::with_name("COLOR_HIGH") + Arg::new("COLOR_HIGH") .long("COLOR_HIGH") .help("CPU and MEM bar color while high usage.") - .takes_value(true) - .default_value("197") - .required(false), + .default_value("197"), ) .arg( - Arg::with_name("COLOR_TRACK_NAME") + Arg::new("COLOR_TRACK_NAME") .long("COLOR_TRACK_NAME") .help("Color of track name filed.") - .takes_value(true) - .default_value("46") - .required(false), + .default_value("46"), ) .arg( - Arg::with_name("COLOR_TRACK_ARTIST") + Arg::new("COLOR_TRACK_ARTIST") .long("COLOR_TRACK_ARTIST") .help("Color of artist name filed.") - .takes_value(true) - .default_value("46") - .required(false), + .default_value("46"), ) .arg( - Arg::with_name("COLOR_TRACK_TIME") + Arg::new("COLOR_TRACK_TIME") .long("COLOR_TRACK_TIME") .help("Color of playing time field.") - .takes_value(true) - .default_value("153") - .required(false), + .default_value("153"), ) .arg( - Arg::with_name("COLOR_END") + Arg::new("COLOR_END") .long("COLOR_END") .help("Default color using to terminate others.") - .takes_value(true) - .default_value("153") - .required(false), + .default_value("153"), ) .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 lt_format = cli_args.get_one::("localtime").map(|s| s.to_string()); + let ut_format = cli_args.get_one::("utctime").map(|s| s.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()), + mpd_server: cli_args.get_one::("mpd_address").unwrap().to_string(), + lt_format, + ut_format, + color_low: colorize(cli_args.get_one::("COLOR_LOW").unwrap().to_string()), + color_mid: colorize(cli_args.get_one::("COLOR_MID").unwrap().to_string()), + color_high: colorize(cli_args.get_one::("COLOR_HIGH").unwrap().to_string()), + color_track_name: colorize(cli_args.get_one::("COLOR_TRACK_NAME").unwrap().to_string()), + color_track_artist: colorize(cli_args.get_one::("COLOR_TRACK_ARTIST").unwrap().to_string()), + color_track_time: colorize(cli_args.get_one::("COLOR_TRACK_TIME").unwrap().to_string()), + color_end: colorize(cli_args.get_one::("COLOR_END").unwrap().to_string()), }; - if cli_args.is_present("cpu") { + if cli_args.get_flag("cpu") { cfg.action = Action::Cpu; } - if cli_args.is_present("mem") { + if cli_args.get_flag("mem") { cfg.action = Action::Mem; } - if cli_args.is_present("localtime") { + if cli_args.contains_id("localtime") { cfg.action = Action::Localtime; } - if cli_args.is_present("utctime") { + if cli_args.contains_id("utctime") { cfg.action = Action::Utctime; } - if cli_args.is_present("mpris") { + if cli_args.get_flag("mpris") { cfg.action = Action::Mpris; } - if cli_args.is_present("mpd") { + if cli_args.get_flag("mpd") { cfg.action = Action::Mpd; } + cfg } + diff --git a/src/main.rs b/src/main.rs index 403b954..ddc4eb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,27 +1,18 @@ -extern crate chrono; -extern crate dbus; -extern crate mpd; - mod config; mod utils; #[derive(Debug, Clone)] -struct TrackInfo { - title: String, - artist: String, - position: String, - duration: String, - status: String, +pub struct TrackInfo { + pub title: String, + pub artist: String, + pub position: String, + pub duration: String, + pub status: String, } fn main() { let conf = config::read(); - // 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 + match conf.action { config::Action::Cpu => utils::cpu_load_bar(15, &conf), config::Action::Mem => utils::mem_load_bar(15, &conf), @@ -31,3 +22,4 @@ fn main() { config::Action::Mpd => utils::mpd(&conf), } } + diff --git a/src/utils.rs b/src/utils.rs index 8b83d92..084b7f5 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,287 +1,190 @@ +use dbus::arg::RefArg; use crate::config; -use crate::dbus::blocking::stdintf::org_freedesktop_dbus::Properties; -use chrono::{DateTime, Local, Utc}; +use chrono::{Local, Utc}; use dbus::{arg, blocking::Connection}; use mpd::Client; use size_format::SizeFormatterBinary; use std::process; use std::time::Duration; use sys_info; +use dbus::blocking::stdintf::org_freedesktop_dbus::Properties; #[derive(Debug, Clone)] pub struct TrackInfo { - title: String, - artist: String, - position: String, - duration: String, - status: String, + pub title: String, + pub artist: String, + pub position: String, + pub duration: String, + pub 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); + let bar_sym = "▮"; + let ratio = (value as f32) / (max as f32); + bar.push_str(if ratio < low { + &config.color_low + } else if ratio < mid { + &config.color_mid } else { - bar.push_str(&config.color_high); - } + &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(if i < value { bar_sym } else { " " }); } bar.push_str(&config.color_end); - bar.push_str("|"); - print!("{}", bar) + bar.push('|'); + 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; + let memory = sys_info::mem_info().expect("Failed to get mem_info"); + let used_ratio = (memory.total - memory.avail) as f32 / memory.total as f32; + let len = (used_ratio * bar_len as f32) as i32; to_bar(len, bar_len, 0.7, 0.9, config); print!( "{}B #[default]", - SizeFormatterBinary::new((memory.avail * 1000) as u64) + SizeFormatterBinary::new((memory.avail * 1024) as u64) ); } 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); + let cpu_count = sys_info::cpu_num().expect("Failed to get cpu_num"); + let la_one = sys_info::loadavg().expect("Failed to get loadavg").one; + let len = (la_one / cpu_count as f64 * bar_len as f64).round() as i32; + to_bar(len, 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 proxy = conn.with_proxy("org.freedesktop.DBus", "/", Duration::from_secs(5)); 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) + Ok(names.into_iter().filter(|n| n.contains("org.mpris.MediaPlayer2")).collect()) } 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(), + let proxy = conn.with_proxy(player, "/org/mpris/MediaPlayer2", Duration::from_secs(5)); + let metadata: arg::PropMap = proxy.get("org.mpris.MediaPlayer2.Player", "Metadata")?; + + let title = metadata.get("xesam:title").and_then(|v| v.as_str()).unwrap_or("").to_string(); + let artist = metadata.get("xesam:artist") + .and_then(|v| v.as_iter()) + .and_then(|mut artists| artists.next().and_then(|a| a.as_str())) + .unwrap_or("").to_string(); + let duration_us = metadata.get("mpris:length").and_then(|v| v.as_i64()).unwrap_or(0); + let position_us: i64 = proxy.get("org.mpris.MediaPlayer2.Player", "Position")?; + let status_text: String = proxy.get("org.mpris.MediaPlayer2.Player", "PlaybackStatus")?; + + let status = match status_text.as_str() { + "Playing" => "▶", + "Paused" => "⏸", + _ => "⏹", + }.to_string(); + + let track_info = TrackInfo { + title, + artist, + position: format_time(position_us / 1_000_000), + duration: format_time(duration_us / 1_000_000), + status, }; - players_vec.push(track_info); - } - for player in &players_vec { - if player.status == "▶".to_string() { - return Ok(player.clone()); + + if track_info.status == "▶" { + return Ok(track_info); } } - Ok(players_vec[players_vec.len() - 1].clone()) + Err("No active player".into()) } 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() + format!("{:02}:{:02}", sec / 60, sec % 60) } 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)); - } + let fmt = format.unwrap_or_else(|| "%H:%M".to_string()); + let now = if utc { Utc::now().format(&fmt) } else { Local::now().format(&fmt) }; + println!("{}", now); } -fn shorten(line: String, max_len: usize, max_shift: usize) -> String { - let mut new_line = String::new(); - let len = if max_len + max_shift >= line.chars().count() { - line.chars().count() +fn shorten(line: &str, max_len: usize) -> String { + if line.chars().count() > max_len { + format!("{}..", line.chars().take(max_len).collect::()) } else { - max_len - }; - if line.len() > len { - let mut counter = 0; - for ch in line.chars() { - if counter == len { - new_line.push_str(".."); - break; - } - new_line.push(ch); - counter += 1; - } - } else { - new_line = line; + line.to_string() } - new_line } fn format_player(track_info: TrackInfo, config: &config::Config) { - let mut separator: String = " — ".to_string(); - let mut max_len = 30; - if track_info.artist.chars().count() == 0 { - separator = "".to_string(); - max_len = max_len * 2; - } - let artist_line = shorten(track_info.artist, max_len, 6); - let title_line = shorten(track_info.title, max_len, 6); - if track_info.position == "00:00" || track_info.duration == "" { + let separator = if track_info.artist.is_empty() { "" } else { " — " }; + let max_len = if track_info.artist.is_empty() { 60 } else { 30 }; + + let artist_line = shorten(&track_info.artist, max_len); + let title_line = shorten(&track_info.title, max_len); + + if track_info.position == "00:00" || track_info.duration.is_empty() { println!( - "#[none]#[bold]{}{}{}#[none]{}{}{}{} {}{} {}#[default]", - config.color_track_name, - title_line, - config.color_end, + "#[bold]{}{}{}{}{}{} {}{} {}#[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.status, - config.color_end, + config.color_track_artist, artist_line, config.color_end, + config.color_track_time, track_info.status ); } else { println!( - "#[none]#[bold]{}{}{}#[none]{}{}{}{} {}[{}/{}] {} {}#[default]", - config.color_track_name, - title_line, - config.color_end, + "#[bold]{}{}{}{}{}{} {}[{}/{}] {}{}{}#[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, + 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()) { + match player_info(get_player().unwrap_or_default()) { Ok(track_info) => format_player(track_info, config), - Err(_e) => println!("No music playing"), + Err(_) => 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 conn = Client::connect(&config.mpd_server).unwrap_or_else(|e| { + println!("Can't connect to MPD server. {}", e); + process::exit(1); + }); + + let song = conn.currentsong().unwrap_or(None); + let status = conn.status().unwrap(); + + let artist = song.as_ref() + .and_then(|s| s.tags.iter().find(|(k, _)| k == "Artist").map(|(_, v)| v)) + .cloned() + .unwrap_or_default(); + + + let title = song.as_ref() + .and_then(|s| s.title.clone().or_else(|| s.name.clone())) + .unwrap_or_default(); + + + let (position, duration) = status.time.unwrap_or_default(); + + let track_info = TrackInfo { + title, + artist, + position: format_time(position.as_secs() as i64), + duration: format_time(duration.as_secs() as i64), + status: match status.state { + mpd::State::Play => "▶", + mpd::State::Pause => "⏸", + mpd::State::Stop => "⏹", + }.to_string(), }; - let mut track_info = TrackInfo { - title: String::new(), - artist: String::new(), - position: String::new(), - duration: String::new(), - status: String::new(), - }; - // println!("{:?}", conn.currentsong()); - 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 there is no tags and title. - if track_info.artist == track_info.title { - if let Some(name) = song.name { - track_info.title = name - } - } - } - if let Some(time) = conn.status().unwrap().time { - track_info.position = format_time(time.0.num_seconds() as i64); - track_info.duration = format_time(time.1.num_seconds() as i64); - } - 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) + + format_player(track_info, config); } +