From e7e258dcd38ae9ee9eea4c6f343eeff2fcaf6ec7 Mon Sep 17 00:00:00 2001 From: mustafa salih Date: Wed, 13 Jan 2021 01:08:46 +0300 Subject: [PATCH 1/4] rsblocks now support spotify using dbus --- Cargo.lock | 22 ++++- Cargo.toml | 3 +- rsblocks.yml | 19 ++-- src/lib.rs | 270 ++++++++++++++++++++++++++++++++++++--------------- 4 files changed, 227 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff50eb5..e9de6fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -194,6 +194,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7313c0d620d0cb4dbd9d019e461a4beb501071ff46ec0ab933efb4daa76d73e3" +[[package]] +name = "dbus" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b1334c0161ddfccd239ac81b188d62015b049c986c5cd0b7f9447cf2c54f4a3" +dependencies = [ + "libc", + "libdbus-sys", +] + [[package]] name = "event-listener" version = "2.5.1" @@ -260,6 +270,15 @@ version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" +[[package]] +name = "libdbus-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc12a3bc971424edbbf7edaf6e5740483444db63aa8e23d3751ff12a30f306f0" +dependencies = [ + "pkg-config", +] + [[package]] name = "linked-hash-map" version = "0.5.3" @@ -397,11 +416,12 @@ checksum = "4e1b7878800220a76a08f32c057829511440f65528b63b940f2f2bc145d7ac68" [[package]] name = "rsblocks" -version = "0.1.8" +version = "0.1.9" dependencies = [ "alsa", "breadx", "chrono", + "dbus", "minreq", "mpd", "nix 0.19.1", diff --git a/Cargo.toml b/Cargo.toml index d80267d..9e38b4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rsblocks" -version = "0.1.8" +version = "0.1.9" authors = ["mustafa salih "] edition = "2018" readme = "README.md" @@ -17,6 +17,7 @@ minreq = "2.2.1" alsa = "0.4.3" nix = "0.19.1" mpd = "0.0.12" +dbus = "0.9.1" [dependencies.breadx] version = "0.1.11" diff --git a/rsblocks.yml b/rsblocks.yml index 059016a..65a5569 100644 --- a/rsblocks.yml +++ b/rsblocks.yml @@ -5,7 +5,8 @@ general: seperator: '┃' - + +# Time always running, no enable option for this time: icon: '' format: '%d %b, %I:%M:%S %p' @@ -13,21 +14,21 @@ time: memory: - enable: true icon: '▦' + enable: true delay: 2.0 disk: - enable: true icon: '' + enable: true delay: 120.0 battery: - source: 'BAT0' icon: '' enable: false + source: 'BAT0' delay: 120.0 @@ -45,15 +46,19 @@ mpd: delay: 5.0 -# reads from alsa, alsa-utils package should -# be installed on the system to make it work. volume: - enable: true icon: '' + enable: true delay: 0.18 card: 'PULSE' +spotify: + icon: '' + enable: false + delay: 0.5 + + # weather format options is available on # https://github.com/chubin/wttr.in # NOTES: diff --git a/src/lib.rs b/src/lib.rs index d417164..c03c954 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,32 @@ +//! `rsblocks` A multi threaded status bar for dwm window manager. + +/* + + Licence: MIT + + + Author: Mustafa Salih + [MustafaSalih](https://github.com/MustafaSalih1993) + + + Contributers: + + - AdaShoelace - for her/his contributions. + [AdaShoelace](https://github.com/AdaShoelace) + + + Thanks to: + - wttr.in - for using their weather API. + [wttr.in](https://github.com/chubin/wttr.in) + + +*/ + use alsa::mixer::{Mixer, SelemChannelId, SelemId}; use breadx::{display::*, window::Window}; use chrono::prelude::*; +use dbus::blocking::stdintf::org_freedesktop_dbus::Properties; +use dbus::{arg, blocking::Connection}; use mpd::{Client, Song}; use std::fs::File; use std::io::Error; @@ -21,6 +47,7 @@ pub enum ThreadsData { Battery(String), CpuTemp(String), Uptime(String), + Spotify(String), } #[derive(Clone)] @@ -35,6 +62,7 @@ pub struct Config { pub cpu_temperature: CpuTemp, pub uptime: Uptime, pub mpd: Mpd, + pub spotify: Spotify, } #[derive(Clone)] @@ -104,6 +132,33 @@ pub struct Mpd { pub enabled: bool, pub delay: f64, } + +#[derive(Clone)] +pub struct Spotify { + pub icon: String, + pub enabled: bool, + pub delay: f64, +} + +pub struct Blocks { + disp: Display, + root: Window, +} + +impl Blocks { + pub fn new() -> Self { + let disp = Display::create(None, None).expect("Failed to create x11 connection"); + let root = disp.default_screen().root; + Self { disp, root } + } +} + +impl Default for Blocks { + fn default() -> Self { + Self::new() + } +} + /* TODOS TODO 1: Error handling required if rsblocks.yml file is empty. @@ -117,11 +172,12 @@ pub struct Mpd { TODO 5: Fix repeated code for threads in `run` function. - TODO 6: Getting song metadata from mpd by a format provided by the user. + TODO 6: Getting song metadata by a format provided by the user. */ -/*this function is responsible to check if the rsblocks.yml file +/* +this function is responsible to check if the rsblocks.yml file exists to call parse_config to read it OTHERWISE it will call load_defaults to load a hardcoded default configuration @@ -200,62 +256,80 @@ fn load_defaults() -> Config { enabled: false, delay: 15.0, }, + spotify: Spotify { + icon: String::from(""), + enabled: false, + delay: 15.0, + }, } } /* it will read and parse the rsblocks.yml file content and return a valid configuration IF some content is missing in the rsblocks.yml file, it will set -a default values to that +a default values to that. + +NOTE: (get_or_set) functions job below getting the values from the configuration doc IF + a value is not exist in the config it will SET a value givin in the last argument. */ 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", "|"); + + // time values let time_icon = get_or_set_string(doc, "time", "icon", ""); - let mem_icon = get_or_set_string(doc, "memory", "icon", ""); - let disk_icon = get_or_set_string(doc, "disk", "icon", ""); - let volume_icon = get_or_set_string(doc, "volume", "icon", ""); - let weather_icon = get_or_set_string(doc, "weather", "icon", ""); - let battery_icon = get_or_set_string(doc, "battery", "icon", ""); - let cpu_temperature_icon = get_or_set_string(doc, "cpu_temperature", "icon", ""); - let uptime_icon = get_or_set_string(doc, "uptime", "icon", ""); - let mpd_icon = get_or_set_string(doc, "mpd", "icon", ""); - - //parsing formats and city weather let time_format = get_or_set_string(doc, "time", "format", "%T"); - let weather_format = get_or_set_string(doc, "weather", "format", "%l:+%t"); - let weather_city = get_or_set_string(doc, "weather", "city", ""); - - // 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"); - let weather_enabled = get_or_set_bool(doc, "weather", "enable"); - let battery_enabled = get_or_set_bool(doc, "battery", "enable"); - let cpu_temperature_enabled = get_or_set_bool(doc, "cpu_temperature", "enable"); - let uptime_enabled = get_or_set_bool(doc, "uptime", "enable"); - let mpd_enabled = get_or_set_bool(doc, "mpd", "enable"); - - // parsing update_delay state (should be all seconds in f64 type) let time_delay = get_or_set_f64(doc, "time", "delay", 1.0); - let disk_delay = get_or_set_f64(doc, "disk", "delay", 120.0); - let memory_delay = get_or_set_f64(doc, "memory", "delay", 2.0); - let volume_delay = get_or_set_f64(doc, "volume", "delay", 0.17); - let weather_delay = get_or_set_f64(doc, "weather", "delay", 7200.0); - let battery_delay = get_or_set_f64(doc, "battery", "delay", 120.0); - let cpu_temperature_delay = get_or_set_f64(doc, "cpu_temperature", "delay", 120.0); - let uptime_delay = get_or_set_f64(doc, "uptime", "delay", 60.0); - let mpd_delay = get_or_set_f64(doc, "mpd", "delay", 15.0); - // parsing card for volume, ALSA or PULSE + // memory values + let mem_icon = get_or_set_string(doc, "memory", "icon", ""); + let memory_enabled = get_or_set_bool(doc, "memory", "enable"); + let memory_delay = get_or_set_f64(doc, "memory", "delay", 2.0); + + //disk values + let disk_icon = get_or_set_string(doc, "disk", "icon", ""); + let disk_enabled = get_or_set_bool(doc, "disk", "enable"); + let disk_delay = get_or_set_f64(doc, "disk", "delay", 120.0); + + // volume values + let volume_icon = get_or_set_string(doc, "volume", "icon", ""); + let volume_enabled = get_or_set_bool(doc, "volume", "enable"); + let volume_delay = get_or_set_f64(doc, "volume", "delay", 0.17); let volume_card = get_or_set_string(doc, "volume", "card", "ALSA"); - // parsing battery source name - let battery_source = get_or_set_string(doc, "battery", "source", "BAT0"); + // weather values + let weather_icon = get_or_set_string(doc, "weather", "icon", ""); + let weather_format = get_or_set_string(doc, "weather", "format", "%l:+%t"); + let weather_city = get_or_set_string(doc, "weather", "city", ""); + let weather_enabled = get_or_set_bool(doc, "weather", "enable"); + let weather_delay = get_or_set_f64(doc, "weather", "delay", 7200.0); - //parsing mpd host and port + // battery values + let battery_icon = get_or_set_string(doc, "battery", "icon", ""); + let battery_enabled = get_or_set_bool(doc, "battery", "enable"); + let battery_source = get_or_set_string(doc, "battery", "source", "BAT0"); + let battery_delay = get_or_set_f64(doc, "battery", "delay", 120.0); + + // cpu values + let cpu_temperature_icon = get_or_set_string(doc, "cpu_temperature", "icon", ""); + let cpu_temperature_enabled = get_or_set_bool(doc, "cpu_temperature", "enable"); + let cpu_temperature_delay = get_or_set_f64(doc, "cpu_temperature", "delay", 120.0); + + // uptime values + let uptime_icon = get_or_set_string(doc, "uptime", "icon", ""); + let uptime_enabled = get_or_set_bool(doc, "uptime", "enable"); + let uptime_delay = get_or_set_f64(doc, "uptime", "delay", 60.0); + + // mpd values + let mpd_icon = get_or_set_string(doc, "mpd", "icon", ""); let mpd_host = get_or_set_string(doc, "mpd", "host", "127.0.0.1"); let mpd_port = get_or_set_string(doc, "mpd", "port", "6600"); + let mpd_enabled = get_or_set_bool(doc, "mpd", "enable"); + let mpd_delay = get_or_set_f64(doc, "mpd", "delay", 15.0); + + //spotify values + let spotify_icon = get_or_set_string(doc, "spotify", "icon", ""); + let spotify_enabled = get_or_set_bool(doc, "spotify", "enable"); + let spotify_delay = get_or_set_f64(doc, "spotify", "delay", 10.0); Config { seperator, @@ -310,10 +384,15 @@ fn parse_config(doc: &yaml::Yaml) -> Config { enabled: mpd_enabled, delay: mpd_delay, }, + spotify: Spotify { + icon: spotify_icon, + enabled: spotify_enabled, + delay: spotify_delay, + }, } } -// getting a f32 value from rsblocks.yml file or set default (last argument) +// getting a f64 value from rsblocks.yml file or set default (last argument) fn get_or_set_f64(doc: &yaml::Yaml, parent: &str, child: &str, default: f64) -> f64 { let val: f64 = if doc[parent][child].is_badvalue() { default @@ -352,28 +431,21 @@ fn get_or_set_string(doc: &yaml::Yaml, parent: &str, child: &str, default_val: & // Running the program: -pub struct Blocks { - disp: Display, - root: Window, -} - -impl Blocks { - pub fn new() -> Self { - let disp = Display::create(None, None).expect("Failed to create x11 connection"); - let root = disp.default_screen().root; - Self { disp, root } - } -} - -impl Default for Blocks { - fn default() -> Self { - Self::new() - } -} - pub fn run(config: Config, mut blocks: Blocks) { let (tx, rx) = mpsc::channel(); + // spotify thread + if config.spotify.enabled { + let spotify_tx = tx.clone(); + let configcp = config.clone(); + let mut spotify_data = ThreadsData::Spotify(get_spotify(&configcp)); + thread::spawn(move || loop { + spotify_tx.send(spotify_data).unwrap(); + spotify_data = ThreadsData::Spotify(get_spotify(&configcp)); + thread::sleep(Duration::from_secs_f64(configcp.spotify.delay)) + }); + } + // mpd thread if config.mpd.enabled { let mpd_tx = tx.clone(); @@ -474,6 +546,7 @@ pub fn run(config: Config, mut blocks: Blocks) { thread::sleep(Duration::from_secs_f64(configcp.uptime.delay)) }); } + // Time thread { let time_tx = tx; @@ -489,19 +562,20 @@ pub fn run(config: Config, mut blocks: Blocks) { //Main { // NOTE: order matters to the final format - let mut bar: Vec = vec!["".to_string(); 9]; + let mut bar: Vec = vec!["".to_string(); 10]; //iterating the values recieved from the threads for data in rx { match data { - ThreadsData::Mpd(x) => bar[0] = x, - ThreadsData::Sound(x) => bar[1] = x, - ThreadsData::Weather(x) => bar[2] = x, - ThreadsData::Disk(x) => bar[3] = x, - ThreadsData::Memory(x) => bar[4] = x, - ThreadsData::CpuTemp(x) => bar[5] = x, - ThreadsData::Battery(x) => bar[6] = x, - ThreadsData::Uptime(x) => bar[7] = x, - ThreadsData::Time(x) => bar[8] = x, + ThreadsData::Spotify(x) => bar[0] = x, + ThreadsData::Mpd(x) => bar[1] = x, + ThreadsData::Sound(x) => bar[2] = x, + ThreadsData::Weather(x) => bar[3] = x, + ThreadsData::Disk(x) => bar[4] = x, + ThreadsData::Memory(x) => bar[5] = x, + ThreadsData::CpuTemp(x) => bar[6] = x, + ThreadsData::Battery(x) => bar[7] = x, + ThreadsData::Uptime(x) => bar[8] = x, + ThreadsData::Time(x) => bar[9] = x, } // match ends here @@ -511,7 +585,6 @@ pub fn run(config: Config, mut blocks: Blocks) { } pub fn update(bar: &[String], blocks: &mut Blocks) { - // TODO: FIX ME, this solution sucks let mut x = String::new(); for i in bar.iter() { x.push_str(i.as_str()); @@ -544,10 +617,7 @@ pub fn get_time(config: &Config) -> String { ) } -/* -CREDIT: thanks for wttr.in to use their API -will make a GET request from wttr.in -*/ +// will make a GET request from wttr.in fn get_weather(config: &Config) -> String { let format = if config.weather.format.is_empty() { String::from("%l:+%t") @@ -585,6 +655,7 @@ pub fn get_disk(config: &Config) -> String { ) } +// getting volume percentage pub fn get_volume(config: &Config) -> String { let card = if config.volume.card == "PULSE" { "pulse" @@ -763,19 +834,21 @@ pub fn get_uptime(config: &Config) -> Result { Ok(result) } +// yes, error handling looks fucking sucks! // getting mpd song file pub fn get_mpd_current(config: &Config) -> String { let stream_path = format!("{}:{}", config.mpd.host, config.mpd.port); + let empty_string = String::from(""); let mut conn = match Client::connect(&stream_path) { Ok(connection) => connection, - _ => return String::from(""), + _ => return empty_string, }; let current: Song = match conn.currentsong() { Ok(opt) => match opt { Some(song) => song, - _ => return String::from(""), + _ => return empty_string, }, - _ => return String::from(""), + _ => return empty_string, }; let result = format!( @@ -785,3 +858,44 @@ pub fn get_mpd_current(config: &Config) -> String { result } + +// getting spotify current artist and title. +// FIXME: I know im lazy asshole, this error handling looks ugly, i dont like it too, need to fix soon. +fn get_spotify(config: &Config) -> String { + let conn = match Connection::new_session() { + Ok(conn) => conn, + _ => return String::from(""), + }; + + let p = conn.with_proxy( + "org.mpris.MediaPlayer2.spotify", + "/org/mpris/MediaPlayer2", + Duration::from_millis(5000), + ); + + let metadata: arg::PropMap = match p.get("org.mpris.MediaPlayer2.Player", "Metadata") { + Ok(data) => data, + _ => return String::from(""), + }; + + let title: Option<&String> = arg::prop_cast(&metadata, "xesam:title"); + let artist: Option<&Vec> = arg::prop_cast(&metadata, "xesam:artist"); + + let title = match title { + Some(title) => title, + _ => "", + }; + + let artist = match artist { + Some(artist_vec) => match artist_vec.first() { + Some(name) => name, + _ => "", + }, + None => "", + }; + + format!( + " {} {} - {} {}", + config.spotify.icon, artist, title, config.seperator + ) +} From e2ada3238359e0a551db4b34cbe915664c1d2608 Mon Sep 17 00:00:00 2001 From: mustafa salih Date: Wed, 13 Jan 2021 01:33:25 +0300 Subject: [PATCH 2/4] Revert "rsblocks now support spotify using dbus" This reverts commit e7e258dcd38ae9ee9eea4c6f343eeff2fcaf6ec7. updated readme file --- Cargo.lock | 22 +---- Cargo.toml | 3 +- README.md | 11 +++ rsblocks.yml | 19 ++-- src/lib.rs | 260 +++++++++++++++------------------------------------ 5 files changed, 93 insertions(+), 222 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9de6fa..ff50eb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -194,16 +194,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7313c0d620d0cb4dbd9d019e461a4beb501071ff46ec0ab933efb4daa76d73e3" -[[package]] -name = "dbus" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b1334c0161ddfccd239ac81b188d62015b049c986c5cd0b7f9447cf2c54f4a3" -dependencies = [ - "libc", - "libdbus-sys", -] - [[package]] name = "event-listener" version = "2.5.1" @@ -270,15 +260,6 @@ version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" -[[package]] -name = "libdbus-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc12a3bc971424edbbf7edaf6e5740483444db63aa8e23d3751ff12a30f306f0" -dependencies = [ - "pkg-config", -] - [[package]] name = "linked-hash-map" version = "0.5.3" @@ -416,12 +397,11 @@ checksum = "4e1b7878800220a76a08f32c057829511440f65528b63b940f2f2bc145d7ac68" [[package]] name = "rsblocks" -version = "0.1.9" +version = "0.1.8" dependencies = [ "alsa", "breadx", "chrono", - "dbus", "minreq", "mpd", "nix 0.19.1", diff --git a/Cargo.toml b/Cargo.toml index 9e38b4b..d80267d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rsblocks" -version = "0.1.9" +version = "0.1.8" authors = ["mustafa salih "] edition = "2018" readme = "README.md" @@ -17,7 +17,6 @@ minreq = "2.2.1" alsa = "0.4.3" nix = "0.19.1" mpd = "0.0.12" -dbus = "0.9.1" [dependencies.breadx] version = "0.1.11" diff --git a/README.md b/README.md index 2b5350e..66e532c 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ A multi threaded fast status bar for dwm window manager written in **Rust** 🦀 * Cpu Temperature * Uptime * Mpd Current Song File +* Spotify Current Song * Easy to configure with `rsblocks.yml` file @@ -26,6 +27,16 @@ A multi threaded fast status bar for dwm window manager written in **Rust** 🦀 * This tool is still in development stage. * currently supports only linux. + +## Build Requirements +* [Libdbus](https://dbus.freedesktop.org/releases/dbus/) 1.6 or higher as a spotify requirement + +On Ubuntu, you can do : +```sh +sudo apt install libdbus-1-dev pkg-config +``` + + ## Cargo Installation You can install the binary crate directly ```sh diff --git a/rsblocks.yml b/rsblocks.yml index 65a5569..059016a 100644 --- a/rsblocks.yml +++ b/rsblocks.yml @@ -5,8 +5,7 @@ general: seperator: '┃' - -# Time always running, no enable option for this + time: icon: '' format: '%d %b, %I:%M:%S %p' @@ -14,21 +13,21 @@ time: memory: - icon: '▦' enable: true + icon: '▦' delay: 2.0 disk: - icon: '' enable: true + icon: '' delay: 120.0 battery: + source: 'BAT0' icon: '' enable: false - source: 'BAT0' delay: 120.0 @@ -46,19 +45,15 @@ mpd: delay: 5.0 +# reads from alsa, alsa-utils package should +# be installed on the system to make it work. volume: - icon: '' enable: true + icon: '' delay: 0.18 card: 'PULSE' -spotify: - icon: '' - enable: false - delay: 0.5 - - # weather format options is available on # https://github.com/chubin/wttr.in # NOTES: diff --git a/src/lib.rs b/src/lib.rs index c03c954..d417164 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,32 +1,6 @@ -//! `rsblocks` A multi threaded status bar for dwm window manager. - -/* - - Licence: MIT - - - Author: Mustafa Salih - [MustafaSalih](https://github.com/MustafaSalih1993) - - - Contributers: - - - AdaShoelace - for her/his contributions. - [AdaShoelace](https://github.com/AdaShoelace) - - - Thanks to: - - wttr.in - for using their weather API. - [wttr.in](https://github.com/chubin/wttr.in) - - -*/ - use alsa::mixer::{Mixer, SelemChannelId, SelemId}; use breadx::{display::*, window::Window}; use chrono::prelude::*; -use dbus::blocking::stdintf::org_freedesktop_dbus::Properties; -use dbus::{arg, blocking::Connection}; use mpd::{Client, Song}; use std::fs::File; use std::io::Error; @@ -47,7 +21,6 @@ pub enum ThreadsData { Battery(String), CpuTemp(String), Uptime(String), - Spotify(String), } #[derive(Clone)] @@ -62,7 +35,6 @@ pub struct Config { pub cpu_temperature: CpuTemp, pub uptime: Uptime, pub mpd: Mpd, - pub spotify: Spotify, } #[derive(Clone)] @@ -132,33 +104,6 @@ pub struct Mpd { pub enabled: bool, pub delay: f64, } - -#[derive(Clone)] -pub struct Spotify { - pub icon: String, - pub enabled: bool, - pub delay: f64, -} - -pub struct Blocks { - disp: Display, - root: Window, -} - -impl Blocks { - pub fn new() -> Self { - let disp = Display::create(None, None).expect("Failed to create x11 connection"); - let root = disp.default_screen().root; - Self { disp, root } - } -} - -impl Default for Blocks { - fn default() -> Self { - Self::new() - } -} - /* TODOS TODO 1: Error handling required if rsblocks.yml file is empty. @@ -172,12 +117,11 @@ impl Default for Blocks { TODO 5: Fix repeated code for threads in `run` function. - TODO 6: Getting song metadata by a format provided by the user. + TODO 6: Getting song metadata from mpd by a format provided by the user. */ -/* -this function is responsible to check if the rsblocks.yml file +/*this function is responsible to check if the rsblocks.yml file exists to call parse_config to read it OTHERWISE it will call load_defaults to load a hardcoded default configuration @@ -256,80 +200,62 @@ fn load_defaults() -> Config { enabled: false, delay: 15.0, }, - spotify: Spotify { - icon: String::from(""), - enabled: false, - delay: 15.0, - }, } } /* it will read and parse the rsblocks.yml file content and return a valid configuration IF some content is missing in the rsblocks.yml file, it will set -a default values to that. - -NOTE: (get_or_set) functions job below getting the values from the configuration doc IF - a value is not exist in the config it will SET a value givin in the last argument. +a default values to that */ 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", "|"); - - // time values let time_icon = get_or_set_string(doc, "time", "icon", ""); - let time_format = get_or_set_string(doc, "time", "format", "%T"); - let time_delay = get_or_set_f64(doc, "time", "delay", 1.0); - - // memory values let mem_icon = get_or_set_string(doc, "memory", "icon", ""); - let memory_enabled = get_or_set_bool(doc, "memory", "enable"); - let memory_delay = get_or_set_f64(doc, "memory", "delay", 2.0); - - //disk values let disk_icon = get_or_set_string(doc, "disk", "icon", ""); - let disk_enabled = get_or_set_bool(doc, "disk", "enable"); - let disk_delay = get_or_set_f64(doc, "disk", "delay", 120.0); - - // volume values let volume_icon = get_or_set_string(doc, "volume", "icon", ""); - let volume_enabled = get_or_set_bool(doc, "volume", "enable"); - let volume_delay = get_or_set_f64(doc, "volume", "delay", 0.17); - let volume_card = get_or_set_string(doc, "volume", "card", "ALSA"); - - // weather values let weather_icon = get_or_set_string(doc, "weather", "icon", ""); + let battery_icon = get_or_set_string(doc, "battery", "icon", ""); + let cpu_temperature_icon = get_or_set_string(doc, "cpu_temperature", "icon", ""); + let uptime_icon = get_or_set_string(doc, "uptime", "icon", ""); + let mpd_icon = get_or_set_string(doc, "mpd", "icon", ""); + + //parsing formats and city weather + let time_format = get_or_set_string(doc, "time", "format", "%T"); let weather_format = get_or_set_string(doc, "weather", "format", "%l:+%t"); let weather_city = get_or_set_string(doc, "weather", "city", ""); + + // 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"); let weather_enabled = get_or_set_bool(doc, "weather", "enable"); - let weather_delay = get_or_set_f64(doc, "weather", "delay", 7200.0); - - // battery values - let battery_icon = get_or_set_string(doc, "battery", "icon", ""); let battery_enabled = get_or_set_bool(doc, "battery", "enable"); - let battery_source = get_or_set_string(doc, "battery", "source", "BAT0"); - let battery_delay = get_or_set_f64(doc, "battery", "delay", 120.0); - - // cpu values - let cpu_temperature_icon = get_or_set_string(doc, "cpu_temperature", "icon", ""); let cpu_temperature_enabled = get_or_set_bool(doc, "cpu_temperature", "enable"); - let cpu_temperature_delay = get_or_set_f64(doc, "cpu_temperature", "delay", 120.0); - - // uptime values - let uptime_icon = get_or_set_string(doc, "uptime", "icon", ""); let uptime_enabled = get_or_set_bool(doc, "uptime", "enable"); - let uptime_delay = get_or_set_f64(doc, "uptime", "delay", 60.0); - - // mpd values - let mpd_icon = get_or_set_string(doc, "mpd", "icon", ""); - let mpd_host = get_or_set_string(doc, "mpd", "host", "127.0.0.1"); - let mpd_port = get_or_set_string(doc, "mpd", "port", "6600"); let mpd_enabled = get_or_set_bool(doc, "mpd", "enable"); + + // parsing update_delay state (should be all seconds in f64 type) + let time_delay = get_or_set_f64(doc, "time", "delay", 1.0); + let disk_delay = get_or_set_f64(doc, "disk", "delay", 120.0); + let memory_delay = get_or_set_f64(doc, "memory", "delay", 2.0); + let volume_delay = get_or_set_f64(doc, "volume", "delay", 0.17); + let weather_delay = get_or_set_f64(doc, "weather", "delay", 7200.0); + let battery_delay = get_or_set_f64(doc, "battery", "delay", 120.0); + let cpu_temperature_delay = get_or_set_f64(doc, "cpu_temperature", "delay", 120.0); + let uptime_delay = get_or_set_f64(doc, "uptime", "delay", 60.0); let mpd_delay = get_or_set_f64(doc, "mpd", "delay", 15.0); - //spotify values - let spotify_icon = get_or_set_string(doc, "spotify", "icon", ""); - let spotify_enabled = get_or_set_bool(doc, "spotify", "enable"); - let spotify_delay = get_or_set_f64(doc, "spotify", "delay", 10.0); + // parsing card for volume, ALSA or PULSE + let volume_card = get_or_set_string(doc, "volume", "card", "ALSA"); + + // parsing battery source name + let battery_source = get_or_set_string(doc, "battery", "source", "BAT0"); + + //parsing mpd host and port + let mpd_host = get_or_set_string(doc, "mpd", "host", "127.0.0.1"); + let mpd_port = get_or_set_string(doc, "mpd", "port", "6600"); Config { seperator, @@ -384,15 +310,10 @@ fn parse_config(doc: &yaml::Yaml) -> Config { enabled: mpd_enabled, delay: mpd_delay, }, - spotify: Spotify { - icon: spotify_icon, - enabled: spotify_enabled, - delay: spotify_delay, - }, } } -// getting a f64 value from rsblocks.yml file or set default (last argument) +// getting a f32 value from rsblocks.yml file or set default (last argument) fn get_or_set_f64(doc: &yaml::Yaml, parent: &str, child: &str, default: f64) -> f64 { let val: f64 = if doc[parent][child].is_badvalue() { default @@ -431,21 +352,28 @@ fn get_or_set_string(doc: &yaml::Yaml, parent: &str, child: &str, default_val: & // Running the program: +pub struct Blocks { + disp: Display, + root: Window, +} + +impl Blocks { + pub fn new() -> Self { + let disp = Display::create(None, None).expect("Failed to create x11 connection"); + let root = disp.default_screen().root; + Self { disp, root } + } +} + +impl Default for Blocks { + fn default() -> Self { + Self::new() + } +} + pub fn run(config: Config, mut blocks: Blocks) { let (tx, rx) = mpsc::channel(); - // spotify thread - if config.spotify.enabled { - let spotify_tx = tx.clone(); - let configcp = config.clone(); - let mut spotify_data = ThreadsData::Spotify(get_spotify(&configcp)); - thread::spawn(move || loop { - spotify_tx.send(spotify_data).unwrap(); - spotify_data = ThreadsData::Spotify(get_spotify(&configcp)); - thread::sleep(Duration::from_secs_f64(configcp.spotify.delay)) - }); - } - // mpd thread if config.mpd.enabled { let mpd_tx = tx.clone(); @@ -546,7 +474,6 @@ pub fn run(config: Config, mut blocks: Blocks) { thread::sleep(Duration::from_secs_f64(configcp.uptime.delay)) }); } - // Time thread { let time_tx = tx; @@ -562,20 +489,19 @@ pub fn run(config: Config, mut blocks: Blocks) { //Main { // NOTE: order matters to the final format - let mut bar: Vec = vec!["".to_string(); 10]; + let mut bar: Vec = vec!["".to_string(); 9]; //iterating the values recieved from the threads for data in rx { match data { - ThreadsData::Spotify(x) => bar[0] = x, - ThreadsData::Mpd(x) => bar[1] = x, - ThreadsData::Sound(x) => bar[2] = x, - ThreadsData::Weather(x) => bar[3] = x, - ThreadsData::Disk(x) => bar[4] = x, - ThreadsData::Memory(x) => bar[5] = x, - ThreadsData::CpuTemp(x) => bar[6] = x, - ThreadsData::Battery(x) => bar[7] = x, - ThreadsData::Uptime(x) => bar[8] = x, - ThreadsData::Time(x) => bar[9] = x, + ThreadsData::Mpd(x) => bar[0] = x, + ThreadsData::Sound(x) => bar[1] = x, + ThreadsData::Weather(x) => bar[2] = x, + ThreadsData::Disk(x) => bar[3] = x, + ThreadsData::Memory(x) => bar[4] = x, + ThreadsData::CpuTemp(x) => bar[5] = x, + ThreadsData::Battery(x) => bar[6] = x, + ThreadsData::Uptime(x) => bar[7] = x, + ThreadsData::Time(x) => bar[8] = x, } // match ends here @@ -585,6 +511,7 @@ pub fn run(config: Config, mut blocks: Blocks) { } pub fn update(bar: &[String], blocks: &mut Blocks) { + // TODO: FIX ME, this solution sucks let mut x = String::new(); for i in bar.iter() { x.push_str(i.as_str()); @@ -617,7 +544,10 @@ pub fn get_time(config: &Config) -> String { ) } -// will make a GET request from wttr.in +/* +CREDIT: thanks for wttr.in to use their API +will make a GET request from wttr.in +*/ fn get_weather(config: &Config) -> String { let format = if config.weather.format.is_empty() { String::from("%l:+%t") @@ -655,7 +585,6 @@ pub fn get_disk(config: &Config) -> String { ) } -// getting volume percentage pub fn get_volume(config: &Config) -> String { let card = if config.volume.card == "PULSE" { "pulse" @@ -834,21 +763,19 @@ pub fn get_uptime(config: &Config) -> Result { Ok(result) } -// yes, error handling looks fucking sucks! // getting mpd song file pub fn get_mpd_current(config: &Config) -> String { let stream_path = format!("{}:{}", config.mpd.host, config.mpd.port); - let empty_string = String::from(""); let mut conn = match Client::connect(&stream_path) { Ok(connection) => connection, - _ => return empty_string, + _ => return String::from(""), }; let current: Song = match conn.currentsong() { Ok(opt) => match opt { Some(song) => song, - _ => return empty_string, + _ => return String::from(""), }, - _ => return empty_string, + _ => return String::from(""), }; let result = format!( @@ -858,44 +785,3 @@ pub fn get_mpd_current(config: &Config) -> String { result } - -// getting spotify current artist and title. -// FIXME: I know im lazy asshole, this error handling looks ugly, i dont like it too, need to fix soon. -fn get_spotify(config: &Config) -> String { - let conn = match Connection::new_session() { - Ok(conn) => conn, - _ => return String::from(""), - }; - - let p = conn.with_proxy( - "org.mpris.MediaPlayer2.spotify", - "/org/mpris/MediaPlayer2", - Duration::from_millis(5000), - ); - - let metadata: arg::PropMap = match p.get("org.mpris.MediaPlayer2.Player", "Metadata") { - Ok(data) => data, - _ => return String::from(""), - }; - - let title: Option<&String> = arg::prop_cast(&metadata, "xesam:title"); - let artist: Option<&Vec> = arg::prop_cast(&metadata, "xesam:artist"); - - let title = match title { - Some(title) => title, - _ => "", - }; - - let artist = match artist { - Some(artist_vec) => match artist_vec.first() { - Some(name) => name, - _ => "", - }, - None => "", - }; - - format!( - " {} {} - {} {}", - config.spotify.icon, artist, title, config.seperator - ) -} From 2c18c32e5fdda712514f8e495f828ffe5fa48ee7 Mon Sep 17 00:00:00 2001 From: mustafa salih Date: Wed, 13 Jan 2021 01:44:02 +0300 Subject: [PATCH 3/4] rsblocks now support spotify using dbus --- Cargo.lock | 22 ++++- Cargo.toml | 3 +- README.md | 11 --- rsblocks.yml | 19 ++-- src/lib.rs | 270 ++++++++++++++++++++++++++++++++++++--------------- 5 files changed, 227 insertions(+), 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff50eb5..e9de6fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -194,6 +194,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7313c0d620d0cb4dbd9d019e461a4beb501071ff46ec0ab933efb4daa76d73e3" +[[package]] +name = "dbus" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b1334c0161ddfccd239ac81b188d62015b049c986c5cd0b7f9447cf2c54f4a3" +dependencies = [ + "libc", + "libdbus-sys", +] + [[package]] name = "event-listener" version = "2.5.1" @@ -260,6 +270,15 @@ version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" +[[package]] +name = "libdbus-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc12a3bc971424edbbf7edaf6e5740483444db63aa8e23d3751ff12a30f306f0" +dependencies = [ + "pkg-config", +] + [[package]] name = "linked-hash-map" version = "0.5.3" @@ -397,11 +416,12 @@ checksum = "4e1b7878800220a76a08f32c057829511440f65528b63b940f2f2bc145d7ac68" [[package]] name = "rsblocks" -version = "0.1.8" +version = "0.1.9" dependencies = [ "alsa", "breadx", "chrono", + "dbus", "minreq", "mpd", "nix 0.19.1", diff --git a/Cargo.toml b/Cargo.toml index d80267d..9e38b4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rsblocks" -version = "0.1.8" +version = "0.1.9" authors = ["mustafa salih "] edition = "2018" readme = "README.md" @@ -17,6 +17,7 @@ minreq = "2.2.1" alsa = "0.4.3" nix = "0.19.1" mpd = "0.0.12" +dbus = "0.9.1" [dependencies.breadx] version = "0.1.11" diff --git a/README.md b/README.md index 66e532c..2b5350e 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ A multi threaded fast status bar for dwm window manager written in **Rust** 🦀 * Cpu Temperature * Uptime * Mpd Current Song File -* Spotify Current Song * Easy to configure with `rsblocks.yml` file @@ -27,16 +26,6 @@ A multi threaded fast status bar for dwm window manager written in **Rust** 🦀 * This tool is still in development stage. * currently supports only linux. - -## Build Requirements -* [Libdbus](https://dbus.freedesktop.org/releases/dbus/) 1.6 or higher as a spotify requirement - -On Ubuntu, you can do : -```sh -sudo apt install libdbus-1-dev pkg-config -``` - - ## Cargo Installation You can install the binary crate directly ```sh diff --git a/rsblocks.yml b/rsblocks.yml index 059016a..65a5569 100644 --- a/rsblocks.yml +++ b/rsblocks.yml @@ -5,7 +5,8 @@ general: seperator: '┃' - + +# Time always running, no enable option for this time: icon: '' format: '%d %b, %I:%M:%S %p' @@ -13,21 +14,21 @@ time: memory: - enable: true icon: '▦' + enable: true delay: 2.0 disk: - enable: true icon: '' + enable: true delay: 120.0 battery: - source: 'BAT0' icon: '' enable: false + source: 'BAT0' delay: 120.0 @@ -45,15 +46,19 @@ mpd: delay: 5.0 -# reads from alsa, alsa-utils package should -# be installed on the system to make it work. volume: - enable: true icon: '' + enable: true delay: 0.18 card: 'PULSE' +spotify: + icon: '' + enable: false + delay: 0.5 + + # weather format options is available on # https://github.com/chubin/wttr.in # NOTES: diff --git a/src/lib.rs b/src/lib.rs index d417164..c03c954 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,32 @@ +//! `rsblocks` A multi threaded status bar for dwm window manager. + +/* + + Licence: MIT + + + Author: Mustafa Salih + [MustafaSalih](https://github.com/MustafaSalih1993) + + + Contributers: + + - AdaShoelace - for her/his contributions. + [AdaShoelace](https://github.com/AdaShoelace) + + + Thanks to: + - wttr.in - for using their weather API. + [wttr.in](https://github.com/chubin/wttr.in) + + +*/ + use alsa::mixer::{Mixer, SelemChannelId, SelemId}; use breadx::{display::*, window::Window}; use chrono::prelude::*; +use dbus::blocking::stdintf::org_freedesktop_dbus::Properties; +use dbus::{arg, blocking::Connection}; use mpd::{Client, Song}; use std::fs::File; use std::io::Error; @@ -21,6 +47,7 @@ pub enum ThreadsData { Battery(String), CpuTemp(String), Uptime(String), + Spotify(String), } #[derive(Clone)] @@ -35,6 +62,7 @@ pub struct Config { pub cpu_temperature: CpuTemp, pub uptime: Uptime, pub mpd: Mpd, + pub spotify: Spotify, } #[derive(Clone)] @@ -104,6 +132,33 @@ pub struct Mpd { pub enabled: bool, pub delay: f64, } + +#[derive(Clone)] +pub struct Spotify { + pub icon: String, + pub enabled: bool, + pub delay: f64, +} + +pub struct Blocks { + disp: Display, + root: Window, +} + +impl Blocks { + pub fn new() -> Self { + let disp = Display::create(None, None).expect("Failed to create x11 connection"); + let root = disp.default_screen().root; + Self { disp, root } + } +} + +impl Default for Blocks { + fn default() -> Self { + Self::new() + } +} + /* TODOS TODO 1: Error handling required if rsblocks.yml file is empty. @@ -117,11 +172,12 @@ pub struct Mpd { TODO 5: Fix repeated code for threads in `run` function. - TODO 6: Getting song metadata from mpd by a format provided by the user. + TODO 6: Getting song metadata by a format provided by the user. */ -/*this function is responsible to check if the rsblocks.yml file +/* +this function is responsible to check if the rsblocks.yml file exists to call parse_config to read it OTHERWISE it will call load_defaults to load a hardcoded default configuration @@ -200,62 +256,80 @@ fn load_defaults() -> Config { enabled: false, delay: 15.0, }, + spotify: Spotify { + icon: String::from(""), + enabled: false, + delay: 15.0, + }, } } /* it will read and parse the rsblocks.yml file content and return a valid configuration IF some content is missing in the rsblocks.yml file, it will set -a default values to that +a default values to that. + +NOTE: (get_or_set) functions job below getting the values from the configuration doc IF + a value is not exist in the config it will SET a value givin in the last argument. */ 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", "|"); + + // time values let time_icon = get_or_set_string(doc, "time", "icon", ""); - let mem_icon = get_or_set_string(doc, "memory", "icon", ""); - let disk_icon = get_or_set_string(doc, "disk", "icon", ""); - let volume_icon = get_or_set_string(doc, "volume", "icon", ""); - let weather_icon = get_or_set_string(doc, "weather", "icon", ""); - let battery_icon = get_or_set_string(doc, "battery", "icon", ""); - let cpu_temperature_icon = get_or_set_string(doc, "cpu_temperature", "icon", ""); - let uptime_icon = get_or_set_string(doc, "uptime", "icon", ""); - let mpd_icon = get_or_set_string(doc, "mpd", "icon", ""); - - //parsing formats and city weather let time_format = get_or_set_string(doc, "time", "format", "%T"); - let weather_format = get_or_set_string(doc, "weather", "format", "%l:+%t"); - let weather_city = get_or_set_string(doc, "weather", "city", ""); - - // 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"); - let weather_enabled = get_or_set_bool(doc, "weather", "enable"); - let battery_enabled = get_or_set_bool(doc, "battery", "enable"); - let cpu_temperature_enabled = get_or_set_bool(doc, "cpu_temperature", "enable"); - let uptime_enabled = get_or_set_bool(doc, "uptime", "enable"); - let mpd_enabled = get_or_set_bool(doc, "mpd", "enable"); - - // parsing update_delay state (should be all seconds in f64 type) let time_delay = get_or_set_f64(doc, "time", "delay", 1.0); - let disk_delay = get_or_set_f64(doc, "disk", "delay", 120.0); - let memory_delay = get_or_set_f64(doc, "memory", "delay", 2.0); - let volume_delay = get_or_set_f64(doc, "volume", "delay", 0.17); - let weather_delay = get_or_set_f64(doc, "weather", "delay", 7200.0); - let battery_delay = get_or_set_f64(doc, "battery", "delay", 120.0); - let cpu_temperature_delay = get_or_set_f64(doc, "cpu_temperature", "delay", 120.0); - let uptime_delay = get_or_set_f64(doc, "uptime", "delay", 60.0); - let mpd_delay = get_or_set_f64(doc, "mpd", "delay", 15.0); - // parsing card for volume, ALSA or PULSE + // memory values + let mem_icon = get_or_set_string(doc, "memory", "icon", ""); + let memory_enabled = get_or_set_bool(doc, "memory", "enable"); + let memory_delay = get_or_set_f64(doc, "memory", "delay", 2.0); + + //disk values + let disk_icon = get_or_set_string(doc, "disk", "icon", ""); + let disk_enabled = get_or_set_bool(doc, "disk", "enable"); + let disk_delay = get_or_set_f64(doc, "disk", "delay", 120.0); + + // volume values + let volume_icon = get_or_set_string(doc, "volume", "icon", ""); + let volume_enabled = get_or_set_bool(doc, "volume", "enable"); + let volume_delay = get_or_set_f64(doc, "volume", "delay", 0.17); let volume_card = get_or_set_string(doc, "volume", "card", "ALSA"); - // parsing battery source name - let battery_source = get_or_set_string(doc, "battery", "source", "BAT0"); + // weather values + let weather_icon = get_or_set_string(doc, "weather", "icon", ""); + let weather_format = get_or_set_string(doc, "weather", "format", "%l:+%t"); + let weather_city = get_or_set_string(doc, "weather", "city", ""); + let weather_enabled = get_or_set_bool(doc, "weather", "enable"); + let weather_delay = get_or_set_f64(doc, "weather", "delay", 7200.0); - //parsing mpd host and port + // battery values + let battery_icon = get_or_set_string(doc, "battery", "icon", ""); + let battery_enabled = get_or_set_bool(doc, "battery", "enable"); + let battery_source = get_or_set_string(doc, "battery", "source", "BAT0"); + let battery_delay = get_or_set_f64(doc, "battery", "delay", 120.0); + + // cpu values + let cpu_temperature_icon = get_or_set_string(doc, "cpu_temperature", "icon", ""); + let cpu_temperature_enabled = get_or_set_bool(doc, "cpu_temperature", "enable"); + let cpu_temperature_delay = get_or_set_f64(doc, "cpu_temperature", "delay", 120.0); + + // uptime values + let uptime_icon = get_or_set_string(doc, "uptime", "icon", ""); + let uptime_enabled = get_or_set_bool(doc, "uptime", "enable"); + let uptime_delay = get_or_set_f64(doc, "uptime", "delay", 60.0); + + // mpd values + let mpd_icon = get_or_set_string(doc, "mpd", "icon", ""); let mpd_host = get_or_set_string(doc, "mpd", "host", "127.0.0.1"); let mpd_port = get_or_set_string(doc, "mpd", "port", "6600"); + let mpd_enabled = get_or_set_bool(doc, "mpd", "enable"); + let mpd_delay = get_or_set_f64(doc, "mpd", "delay", 15.0); + + //spotify values + let spotify_icon = get_or_set_string(doc, "spotify", "icon", ""); + let spotify_enabled = get_or_set_bool(doc, "spotify", "enable"); + let spotify_delay = get_or_set_f64(doc, "spotify", "delay", 10.0); Config { seperator, @@ -310,10 +384,15 @@ fn parse_config(doc: &yaml::Yaml) -> Config { enabled: mpd_enabled, delay: mpd_delay, }, + spotify: Spotify { + icon: spotify_icon, + enabled: spotify_enabled, + delay: spotify_delay, + }, } } -// getting a f32 value from rsblocks.yml file or set default (last argument) +// getting a f64 value from rsblocks.yml file or set default (last argument) fn get_or_set_f64(doc: &yaml::Yaml, parent: &str, child: &str, default: f64) -> f64 { let val: f64 = if doc[parent][child].is_badvalue() { default @@ -352,28 +431,21 @@ fn get_or_set_string(doc: &yaml::Yaml, parent: &str, child: &str, default_val: & // Running the program: -pub struct Blocks { - disp: Display, - root: Window, -} - -impl Blocks { - pub fn new() -> Self { - let disp = Display::create(None, None).expect("Failed to create x11 connection"); - let root = disp.default_screen().root; - Self { disp, root } - } -} - -impl Default for Blocks { - fn default() -> Self { - Self::new() - } -} - pub fn run(config: Config, mut blocks: Blocks) { let (tx, rx) = mpsc::channel(); + // spotify thread + if config.spotify.enabled { + let spotify_tx = tx.clone(); + let configcp = config.clone(); + let mut spotify_data = ThreadsData::Spotify(get_spotify(&configcp)); + thread::spawn(move || loop { + spotify_tx.send(spotify_data).unwrap(); + spotify_data = ThreadsData::Spotify(get_spotify(&configcp)); + thread::sleep(Duration::from_secs_f64(configcp.spotify.delay)) + }); + } + // mpd thread if config.mpd.enabled { let mpd_tx = tx.clone(); @@ -474,6 +546,7 @@ pub fn run(config: Config, mut blocks: Blocks) { thread::sleep(Duration::from_secs_f64(configcp.uptime.delay)) }); } + // Time thread { let time_tx = tx; @@ -489,19 +562,20 @@ pub fn run(config: Config, mut blocks: Blocks) { //Main { // NOTE: order matters to the final format - let mut bar: Vec = vec!["".to_string(); 9]; + let mut bar: Vec = vec!["".to_string(); 10]; //iterating the values recieved from the threads for data in rx { match data { - ThreadsData::Mpd(x) => bar[0] = x, - ThreadsData::Sound(x) => bar[1] = x, - ThreadsData::Weather(x) => bar[2] = x, - ThreadsData::Disk(x) => bar[3] = x, - ThreadsData::Memory(x) => bar[4] = x, - ThreadsData::CpuTemp(x) => bar[5] = x, - ThreadsData::Battery(x) => bar[6] = x, - ThreadsData::Uptime(x) => bar[7] = x, - ThreadsData::Time(x) => bar[8] = x, + ThreadsData::Spotify(x) => bar[0] = x, + ThreadsData::Mpd(x) => bar[1] = x, + ThreadsData::Sound(x) => bar[2] = x, + ThreadsData::Weather(x) => bar[3] = x, + ThreadsData::Disk(x) => bar[4] = x, + ThreadsData::Memory(x) => bar[5] = x, + ThreadsData::CpuTemp(x) => bar[6] = x, + ThreadsData::Battery(x) => bar[7] = x, + ThreadsData::Uptime(x) => bar[8] = x, + ThreadsData::Time(x) => bar[9] = x, } // match ends here @@ -511,7 +585,6 @@ pub fn run(config: Config, mut blocks: Blocks) { } pub fn update(bar: &[String], blocks: &mut Blocks) { - // TODO: FIX ME, this solution sucks let mut x = String::new(); for i in bar.iter() { x.push_str(i.as_str()); @@ -544,10 +617,7 @@ pub fn get_time(config: &Config) -> String { ) } -/* -CREDIT: thanks for wttr.in to use their API -will make a GET request from wttr.in -*/ +// will make a GET request from wttr.in fn get_weather(config: &Config) -> String { let format = if config.weather.format.is_empty() { String::from("%l:+%t") @@ -585,6 +655,7 @@ pub fn get_disk(config: &Config) -> String { ) } +// getting volume percentage pub fn get_volume(config: &Config) -> String { let card = if config.volume.card == "PULSE" { "pulse" @@ -763,19 +834,21 @@ pub fn get_uptime(config: &Config) -> Result { Ok(result) } +// yes, error handling looks fucking sucks! // getting mpd song file pub fn get_mpd_current(config: &Config) -> String { let stream_path = format!("{}:{}", config.mpd.host, config.mpd.port); + let empty_string = String::from(""); let mut conn = match Client::connect(&stream_path) { Ok(connection) => connection, - _ => return String::from(""), + _ => return empty_string, }; let current: Song = match conn.currentsong() { Ok(opt) => match opt { Some(song) => song, - _ => return String::from(""), + _ => return empty_string, }, - _ => return String::from(""), + _ => return empty_string, }; let result = format!( @@ -785,3 +858,44 @@ pub fn get_mpd_current(config: &Config) -> String { result } + +// getting spotify current artist and title. +// FIXME: I know im lazy asshole, this error handling looks ugly, i dont like it too, need to fix soon. +fn get_spotify(config: &Config) -> String { + let conn = match Connection::new_session() { + Ok(conn) => conn, + _ => return String::from(""), + }; + + let p = conn.with_proxy( + "org.mpris.MediaPlayer2.spotify", + "/org/mpris/MediaPlayer2", + Duration::from_millis(5000), + ); + + let metadata: arg::PropMap = match p.get("org.mpris.MediaPlayer2.Player", "Metadata") { + Ok(data) => data, + _ => return String::from(""), + }; + + let title: Option<&String> = arg::prop_cast(&metadata, "xesam:title"); + let artist: Option<&Vec> = arg::prop_cast(&metadata, "xesam:artist"); + + let title = match title { + Some(title) => title, + _ => "", + }; + + let artist = match artist { + Some(artist_vec) => match artist_vec.first() { + Some(name) => name, + _ => "", + }, + None => "", + }; + + format!( + " {} {} - {} {}", + config.spotify.icon, artist, title, config.seperator + ) +} From ff577f316a8e1bb20dbcb1b655e56d9f91466cc1 Mon Sep 17 00:00:00 2001 From: mustafa salih Date: Wed, 13 Jan 2021 01:59:55 +0300 Subject: [PATCH 4/4] updated readme file --- README.md | 15 +++++++++++++-- screenshots/1.png | Bin 8259 -> 0 bytes screenshots/2.png | Bin 0 -> 8322 bytes 3 files changed, 13 insertions(+), 2 deletions(-) delete mode 100644 screenshots/1.png create mode 100644 screenshots/2.png diff --git a/README.md b/README.md index 2b5350e..212ed3f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A multi threaded fast status bar for dwm window manager written in **Rust** 🦀

- +


## Features @@ -18,7 +18,8 @@ A multi threaded fast status bar for dwm window manager written in **Rust** 🦀 * Battery Percentage * Cpu Temperature * Uptime -* Mpd Current Song File +* Mpd Current Song +* Spotify Current Song * Easy to configure with `rsblocks.yml` file @@ -26,6 +27,16 @@ A multi threaded fast status bar for dwm window manager written in **Rust** 🦀 * This tool is still in development stage. * currently supports only linux. + +## Build Requirements +* [Libdbus](https://dbus.freedesktop.org/releases/dbus/) 1.6 or higher as a requirement to spotify. + +On ubuntu you can do: +```sh +sudo apt install libdbus-1-dev pkg-config +``` + + ## Cargo Installation You can install the binary crate directly ```sh diff --git a/screenshots/1.png b/screenshots/1.png deleted file mode 100644 index 2715a31fb631a5123127be264775d0782d1108bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8259 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 B=(GR; diff --git a/screenshots/2.png b/screenshots/2.png new file mode 100644 index 0000000000000000000000000000000000000000..d821876daeac7386f2067243cf9271032da2ecd9 GIT binary patch literal 8322 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