diff --git a/Cargo.toml b/Cargo.toml index 908a1c0..c74fe6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "rsblocks" -version = "0.1.1" +version = "0.1.2" authors = ["mustafa salih "] edition = "2018" readme = "README.md" license = "MIT" +keywords = ["linux", "dwm","statusbar"] repository = "https://github.com/MustafaSalih1993/rsblocks" -description = "This is a minimal status bar for dwm window manager for linux" +description = "a multi threaded status bar for dwm window manager for linux" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/README.md b/README.md index f88dc7c..4077e8c 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,21 @@ # rsblocks -A minimal fast dwm status bar written in **Rust** 🦀 +A minimal multi threaded fast status bar for dwm window manager written in **Rust** 🦀


## Features +* Multi Threads * Time/Date * Used Memory * Used Disk space -* Sound volume _reads from `amixer` for now_ -* Easy to configure -* Minimal +* Sound volume _reads from `alsa-utils` for now_ +* Easy to configure with `rsblocks.yml` file -## Note -This tool is still in development stage. + +## Notes +* This tool is still in development stage. +* currently supports only linux. ## Cargo Installation You can install the binary crate directly @@ -38,7 +40,9 @@ mv ./target/release/rsblocks /usr/local/bin you good to go now and can run `rsblocks` from your terminal or put that in your `.xinitrc` ## Configuration -**rsblocks** will try to read the file `~/.config/rsblocks/rsblocks.yml`, if it does not exist, it will load the defaults. +#### Notes: +* **rsblocks** will try to read the file `$HOME/.config/rsblocks/rsblocks.yml`, if it does not exist, it will load the defaults. +* **rsblocks** will read the configuration file **only** on startup, which means you have to kill it and start it again if you updated your `rsblocks.yml` file. create the directory ```sh diff --git a/rsblocks.yml b/rsblocks.yml index 7a6f910..be6939f 100644 --- a/rsblocks.yml +++ b/rsblocks.yml @@ -1,22 +1,28 @@ # This is the full configuration template available for rsblocks tool # the names are clearly defines itself. +# NOTE: the (delay) is **seconds** with the float point to update the item in the bar. + general: seperator: '┃' time: icon: '' format: '%d %b, %I:%M:%S %p' + delay: 1.0 memory: enable: true icon: '▦' + delay: 2.0 disk: enable: true icon: '' + delay: 120.0 -# reads from amixer +# reads from alsa-utils volume: enable: false icon: '' + delay: 0.18 diff --git a/screenshots/1.png b/screenshots/1.png index d9b333c..67bff90 100644 Binary files a/screenshots/1.png and b/screenshots/1.png differ diff --git a/src/lib.rs b/src/lib.rs index 3af6f72..786d4cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,10 +4,20 @@ use std::fs::File; use std::io::Error; use std::io::Read; use std::process::Command; +use std::sync::mpsc; use std::thread; use std::time::Duration; use yaml_rust::{yaml, YamlLoader}; +#[derive(Debug)] +pub enum ThreadsData { + Sound(String), + Disk(String), + Memory(String), + Time(String), +} + +#[derive(Clone)] pub struct Config { pub seperator: String, pub time: Time, @@ -16,45 +26,78 @@ pub struct Config { pub volume: Volume, } +#[derive(Clone)] pub struct Time { pub format: String, pub icon: String, + pub delay: f64, } +#[derive(Clone)] pub struct Memory { pub icon: String, pub enabled: bool, + pub delay: f64, } +#[derive(Clone)] pub struct Disk { pub icon: String, pub enabled: bool, + pub delay: f64, } - +#[derive(Clone)] pub struct Volume { pub icon: String, pub enabled: bool, + pub delay: f64, } pub fn load_config() -> Result { let yml_source = env::var("HOME").unwrap() + "/.config/rsblocks/rsblocks.yml"; let mut data = String::new(); - let mut file = match File::open(yml_source) { + let mut file = match File::open(&yml_source) { Ok(file) => file, Err(_) => { - println!("~/.config/rsblocks/rsblocks.yml file not found, loading defaults!"); + eprintln!("{} file not found, loading defaults!", &yml_source); return Ok(load_defaults()); } }; file.read_to_string(&mut data)?; let yml_content = &YamlLoader::load_from_str(&data).unwrap()[0]; - let config = gen_default_config(yml_content); + let config = parse_config(yml_content); Ok(config) } -fn gen_default_config(doc: &yaml::Yaml) -> Config { - // setting icons +fn load_defaults() -> Config { + Config { + seperator: String::from("|"), + time: Time { + format: String::from("%T"), + icon: String::from(""), + delay: 1.0, + }, + memory: Memory { + icon: String::from(""), + enabled: true, + delay: 2.0, + }, + disk: Disk { + icon: String::from(""), + enabled: false, + delay: 60.0, + }, + volume: Volume { + icon: String::from(""), + enabled: false, + delay: 0.17, + }, + } +} + +fn parse_config(doc: &yaml::Yaml) -> Config { + // parsing icons and set default if not exist in the config file let seperator = get_or_set_string(doc, "general", "seperator", "|"); let time_icon = get_or_set_string(doc, "time", "icon", ""); let time_format = get_or_set_string(doc, "time", "format", "%T"); @@ -62,32 +105,52 @@ fn gen_default_config(doc: &yaml::Yaml) -> Config { let disk_icon = get_or_set_string(doc, "disk", "icon", ""); let volume_icon = get_or_set_string(doc, "volume", "icon", ""); - // everything false by default + // parsing enabled state (everything false by default) let disk_enabled = get_or_set_bool(doc, "disk", "enable"); let memory_enabled = get_or_set_bool(doc, "memory", "enable"); let volume_enabled = get_or_set_bool(doc, "volume", "enable"); + // parsing update_delay state (should be all seconds in f64 type) + let time_delay = get_or_set_f32(doc, "time", "delay", 1.0); + let disk_delay = get_or_set_f32(doc, "disk", "delay", 60.0); + let memory_delay = get_or_set_f32(doc, "memory", "delay", 2.0); + let volume_delay = get_or_set_f32(doc, "volume", "delay", 0.17); + Config { seperator, time: Time { format: time_format, icon: time_icon, + delay: time_delay, }, memory: Memory { icon: mem_icon, enabled: memory_enabled, + delay: memory_delay, }, disk: Disk { icon: disk_icon, enabled: disk_enabled, + delay: disk_delay, }, volume: Volume { icon: volume_icon, enabled: volume_enabled, + delay: volume_delay, }, } } +// getting a f32 value from rsblocks.yml file or set default (last argument) +fn get_or_set_f32(doc: &yaml::Yaml, parent: &str, child: &str, default: f64) -> f64 { + let val: f64 = if doc[parent][child].is_badvalue() { + default + } else { + doc[parent][child].as_f64().unwrap() + }; + val +} + // getting a boolean value from rsblocks.yml file or set it false if it does not exist fn get_or_set_bool(doc: &yaml::Yaml, parent: &str, child: &str) -> bool { let val: bool; @@ -99,7 +162,7 @@ fn get_or_set_bool(doc: &yaml::Yaml, parent: &str, child: &str) -> bool { val } -// getting a String value from the rsblocks.yml file or set the default in the last parameter +// getting a String value from the rsblocks.yml file or set the default(last argument) fn get_or_set_string(doc: &yaml::Yaml, parent: &str, child: &str, default_val: &str) -> String { let val: String; if doc[parent][child].is_badvalue() { @@ -111,68 +174,109 @@ fn get_or_set_string(doc: &yaml::Yaml, parent: &str, child: &str, default_val: & val } -fn load_defaults() -> Config { - Config { - seperator: String::from("|"), - time: Time { - format: String::from("%T"), - icon: String::from(""), - }, - memory: Memory { - icon: String::from(""), - enabled: false, - }, - disk: Disk { - icon: String::from(""), - enabled: false, - }, - volume: Volume { - icon: String::from(""), - enabled: false, - }, - } -} -/* LOADING CONFIGS ENDS HERE */ +/*######################## LOADING CONFIGS ENDS HERE ############################ + +*/ /* Running the program: +TODO: this is sucks, repeated code in threads below, fix me you fucking asshole + */ -TODO: this is sucks, i want to update each one in a diffrent delay, maybe i'll try to use threads -*/ pub fn run(config: Config) { - loop { - let mut bar = String::from(""); + let (tx, rx) = mpsc::channel(); - // the order of the IF's below matters to the final format + // volume thread + if config.volume.enabled { + let volume_tx = tx.clone(); + let configcp = config.clone(); + let mut vol_data = ThreadsData::Sound(get_volume(&configcp)); + thread::spawn(move || loop { + let _ = volume_tx.send(vol_data); + vol_data = ThreadsData::Sound(get_volume(&configcp)); + thread::sleep(Duration::from_secs_f64(configcp.volume.delay)) + }); + } - if config.volume.enabled { - // volume return String - bar.push_str(&get_sound(&config)); + // Disk thread + if config.disk.enabled { + let disk_tx = tx.clone(); + let configcp = config.clone(); + let mut disk_data = ThreadsData::Disk(get_disk(&configcp)); + thread::spawn(move || loop { + disk_tx.send(disk_data).unwrap(); + disk_data = ThreadsData::Disk(get_disk(&configcp)); + thread::sleep(Duration::from_secs_f64(configcp.disk.delay)) + }); + } + + // Memory thread + if config.memory.enabled { + let memory_tx = tx.clone(); + let configcp = config.clone(); + let memory_data = get_memory(&configcp).unwrap(); + let mut memory_data = ThreadsData::Memory(memory_data); + thread::spawn(move || loop { + memory_tx.send(memory_data).unwrap(); + memory_data = ThreadsData::Memory(get_memory(&configcp).unwrap()); + thread::sleep(Duration::from_secs_f64(configcp.memory.delay)) + }); + } + + // Time thread + { + let time_tx = tx; + let configcp = config.clone(); + let mut time_data = ThreadsData::Time(get_time(&configcp)); + thread::spawn(move || loop { + time_tx.send(time_data).unwrap(); + time_data = ThreadsData::Time(get_time(&configcp)); + thread::sleep(Duration::from_secs_f64(configcp.time.delay)) + }); + } + + //Main + { + // NOTE: order matters to the final format + + let mut bar: Vec = vec!["".to_string(); 4]; + //iterating the values recieved from the threads + for data in rx { + match data { + ThreadsData::Sound(x) => bar[0] = x, + ThreadsData::Disk(x) => bar[1] = x, + ThreadsData::Memory(x) => bar[2] = x, + ThreadsData::Time(x) => bar[3] = x, + } + + // match ends here + update(&bar); } - - if config.disk.enabled { - // disk_free return String - bar.push_str(&disk_free(&config)); - } - - if config.memory.enabled { - // mem return Result - bar.push_str(&mem(&config).unwrap()); - } - - bar.push_str(&fmt_date(&config)); - - Command::new("xsetroot") - .arg("-name") - .arg(bar) - .output() - .unwrap(); - - thread::sleep(Duration::from_millis(75)); } } -// format bar date/time -pub fn fmt_date(config: &Config) -> String { +pub fn update(bar: &Vec) { + // TODO: FIX ME, this solution sucks + let mut x = String::new(); + for i in bar.iter() { + x.push_str(i.as_str()); + } + Command::new("xsetroot") + .arg("-name") + .arg(x) + .output() + .unwrap(); +} + +/*############################ RUNNING THE PROGRAM ENDS HERE ###########################################*/ + +/* + + + + +############################# HELPER FUNCTIONS BELOW ################################### +*/ +pub fn get_time(config: &Config) -> String { let now = Local::now(); format!( @@ -183,7 +287,7 @@ pub fn fmt_date(config: &Config) -> String { ) } -pub fn disk_free(config: &Config) -> String { +pub fn get_disk(config: &Config) -> String { let cmd = Command::new("sh") .arg("-c") .args(&["df -h"]) @@ -205,9 +309,9 @@ pub fn disk_free(config: &Config) -> String { } // TODO: what a horrible solution to get the sound, i dont like it -// find another way you dumb fuck +// find another way -pub fn get_sound(config: &Config) -> String { +pub fn get_volume(config: &Config) -> String { let cmd_content = Command::new("amixer") .args(&["-D", "pulse", "get", "Master"]) .output() @@ -227,7 +331,7 @@ pub fn get_sound(config: &Config) -> String { format!(" {} {} {}", config.volume.icon, vol, config.seperator) } -pub fn mem(config: &Config) -> Result { +pub fn get_memory(config: &Config) -> Result { let mut buf = String::new(); File::open("/proc/meminfo")?.read_to_string(&mut buf)?; @@ -288,7 +392,7 @@ pub fn mem(config: &Config) -> Result { } Ok(result) } - +// helper function for the get_memory function fn assign_val(line: &str, assignable: &mut u32) { let parsed: u32 = line.split(':').collect::>()[1] .trim()