feat: mangled cfg/data structs so the player_prefixes field now accepts strings as well, allowing for more customization
This commit is contained in:
parent
2ea0e57338
commit
6ce280bb9c
51
src/main.rs
51
src/main.rs
|
|
@ -1,24 +1,24 @@
|
|||
//! This file contains all driver code for the program.
|
||||
use core::time;
|
||||
use std::ffi::OsString;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::thread;
|
||||
use crate::print_players::print_players;
|
||||
use crate::print_text::print_text;
|
||||
use crate::update_message::update_message;
|
||||
use crate::update_players::update_players;
|
||||
use clap::Parser;
|
||||
use core::time;
|
||||
use log::{error, info, warn};
|
||||
use mpris::PlayerFinder;
|
||||
use std::ffi::OsString;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use structs::cli::Cli;
|
||||
use structs::{config::Config, data::Data};
|
||||
use crate::update_players::update_players;
|
||||
use crate::update_message::update_message;
|
||||
use crate::print_text::print_text;
|
||||
use crate::print_players::print_players;
|
||||
|
||||
mod update_players;
|
||||
mod update_message;
|
||||
mod print_players;
|
||||
mod print_text;
|
||||
mod structs;
|
||||
mod print_players;
|
||||
mod update_message;
|
||||
mod update_players;
|
||||
|
||||
/// This function deals with an incoming (USR1) signal.
|
||||
/// It is hard-coded to play/pause the active player.
|
||||
|
|
@ -28,11 +28,9 @@ mod print_players;
|
|||
fn handle_signal(data: &Data) {
|
||||
if let Some(p) = &data.current_player {
|
||||
match p.checked_play_pause() {
|
||||
Ok(b) => {
|
||||
match b {
|
||||
Ok(b) => match b {
|
||||
true => info!("Player play/paused succesfully!"),
|
||||
false => warn!("Failed to send play/pause signal!"),
|
||||
}
|
||||
},
|
||||
Err(e) => error!("{e}"),
|
||||
}
|
||||
|
|
@ -62,12 +60,17 @@ fn main() {
|
|||
std::env::set_var::<&str, OsString>("RUST_LOG", cli.log_level.into());
|
||||
if let Err(e) = env_logger::init() {
|
||||
error!("{e}");
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// Config, Data, and PlayerFinder initialisation
|
||||
match confy::load::<Config>("polybar-now-playing", cli.config_file.as_str()) {
|
||||
Ok(cfg) => {
|
||||
Ok(mut cfg) => {
|
||||
if let None = cfg.player_prefixes.get("default") {
|
||||
cfg.player_prefixes
|
||||
.insert("default".to_owned(), ">".to_owned());
|
||||
}
|
||||
|
||||
let mut data: Data = Data::default();
|
||||
let rating_strings = cfg.build_rating_strings();
|
||||
|
||||
|
|
@ -76,15 +79,17 @@ fn main() {
|
|||
Ok(finder) => pf = finder,
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
return
|
||||
},
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// signal interception initialisation
|
||||
let term = Arc::new(AtomicBool::new(false));
|
||||
if let Err(e) = signal_hook::flag::register(signal_hook::consts::SIGUSR1, Arc::clone(&term)) {
|
||||
if let Err(e) =
|
||||
signal_hook::flag::register(signal_hook::consts::SIGUSR1, Arc::clone(&term))
|
||||
{
|
||||
error!("{e}");
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// main body loop
|
||||
|
|
@ -100,9 +105,9 @@ fn main() {
|
|||
term.swap(false, Ordering::Relaxed);
|
||||
};
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ fn cutoff(
|
|||
/// b: mutable String builder to append to.
|
||||
/// data: Data struct containing the current prefix character.
|
||||
fn append_prefix(b: &mut Builder, data: &Data) {
|
||||
b.append(data.prefix);
|
||||
b.append(data.prefix.to_owned());
|
||||
b.append(" ");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! This file contains structs and functions concerning themselves with the configuration of the program.
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
/// This struct represents one metadata field to be rendered, as well as the maximum length of its' output.
|
||||
/// There is also support for custom formatting.
|
||||
|
|
@ -11,7 +11,7 @@ pub struct Field {
|
|||
/// The maximum length of the metadata field's output.
|
||||
pub num_chars: u32,
|
||||
/// Formatting to apply. (the value "{}" is substituted with the actual string)
|
||||
pub format: String
|
||||
pub format: String,
|
||||
}
|
||||
|
||||
impl Field {
|
||||
|
|
@ -27,7 +27,7 @@ impl Field {
|
|||
Field {
|
||||
field,
|
||||
num_chars,
|
||||
format
|
||||
format,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +48,6 @@ impl Field {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// This struct contains the 3 symbols used to represent a given userRating in a media field.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Rating {
|
||||
|
|
@ -57,7 +56,7 @@ pub struct Rating {
|
|||
/// character for a half token
|
||||
pub half: char,
|
||||
/// character for a full token
|
||||
pub full: char
|
||||
pub full: char,
|
||||
}
|
||||
|
||||
impl Rating {
|
||||
|
|
@ -84,15 +83,54 @@ impl Rating {
|
|||
fn build_rating_strings(&self) -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
out.push(Self::repeat(self.nil, 5));
|
||||
out.push(format!("{}{}", Self::repeat(self.half, 1), Self::repeat(self.nil, 4)));
|
||||
out.push(format!("{}{}", Self::repeat(self.full, 1), Self::repeat(self.nil, 4)));
|
||||
out.push(format!("{}{}{}", Self::repeat(self.full, 1), Self::repeat(self.half, 1), Self::repeat(self.nil, 3)));
|
||||
out.push(format!("{}{}", Self::repeat(self.full, 2), Self::repeat(self.nil, 3)));
|
||||
out.push(format!("{}{}{}", Self::repeat(self.full, 2), Self::repeat(self.half, 1), Self::repeat(self.nil, 2)));
|
||||
out.push(format!("{}{}", Self::repeat(self.full, 3), Self::repeat(self.nil, 2)));
|
||||
out.push(format!("{}{}{}", Self::repeat(self.full, 3), Self::repeat(self.half, 1), Self::repeat(self.nil, 1)));
|
||||
out.push(format!("{}{}", Self::repeat(self.full, 4), Self::repeat(self.nil, 1)));
|
||||
out.push(format!("{}{}", Self::repeat(self.full, 4), Self::repeat(self.half, 1)));
|
||||
out.push(format!(
|
||||
"{}{}",
|
||||
Self::repeat(self.half, 1),
|
||||
Self::repeat(self.nil, 4)
|
||||
));
|
||||
out.push(format!(
|
||||
"{}{}",
|
||||
Self::repeat(self.full, 1),
|
||||
Self::repeat(self.nil, 4)
|
||||
));
|
||||
out.push(format!(
|
||||
"{}{}{}",
|
||||
Self::repeat(self.full, 1),
|
||||
Self::repeat(self.half, 1),
|
||||
Self::repeat(self.nil, 3)
|
||||
));
|
||||
out.push(format!(
|
||||
"{}{}",
|
||||
Self::repeat(self.full, 2),
|
||||
Self::repeat(self.nil, 3)
|
||||
));
|
||||
out.push(format!(
|
||||
"{}{}{}",
|
||||
Self::repeat(self.full, 2),
|
||||
Self::repeat(self.half, 1),
|
||||
Self::repeat(self.nil, 2)
|
||||
));
|
||||
out.push(format!(
|
||||
"{}{}",
|
||||
Self::repeat(self.full, 3),
|
||||
Self::repeat(self.nil, 2)
|
||||
));
|
||||
out.push(format!(
|
||||
"{}{}{}",
|
||||
Self::repeat(self.full, 3),
|
||||
Self::repeat(self.half, 1),
|
||||
Self::repeat(self.nil, 1)
|
||||
));
|
||||
out.push(format!(
|
||||
"{}{}",
|
||||
Self::repeat(self.full, 4),
|
||||
Self::repeat(self.nil, 1)
|
||||
));
|
||||
out.push(format!(
|
||||
"{}{}",
|
||||
Self::repeat(self.full, 4),
|
||||
Self::repeat(self.half, 1)
|
||||
));
|
||||
out.push(Self::repeat(self.full, 5));
|
||||
out
|
||||
}
|
||||
|
|
@ -105,12 +143,11 @@ impl Default for Rating {
|
|||
Self {
|
||||
nil: '-',
|
||||
half: '/',
|
||||
full: '+'
|
||||
full: '+',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// This struct contains all possible configuration fields.
|
||||
/// It should not be used as mutable; all data in this struct should effectively be treated as read-only.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
|
@ -139,7 +176,7 @@ pub struct Config {
|
|||
pub metadata_fields: Vec<Field>,
|
||||
/// Hashmap which maps Player Identities (strings; key) to prefixes (char; value).
|
||||
/// If left blank all players will use the default prefix character ('>').
|
||||
pub player_prefixes: HashMap<String, char>,
|
||||
pub player_prefixes: HashMap<String, String>,
|
||||
/// Boolean which tells the program to escape special characters or not.
|
||||
/// This is useful for some bar implementations (i.e. waybar needs to escape the '&' character).
|
||||
/// Currently only escapes '&', i will be adding more as i run into them.
|
||||
|
|
@ -205,24 +242,23 @@ impl Config {
|
|||
fn default_metadata_fields() -> Vec<Field> {
|
||||
vec![
|
||||
Field::constructor("xesam:title", 40, None),
|
||||
Field::constructor("xesam:artist", 20, None)
|
||||
Field::constructor("xesam:artist", 20, None),
|
||||
]
|
||||
}
|
||||
|
||||
/// This function returns the default prefixes, used when a non-existent config file is requested.
|
||||
/// Like the player priorities function, this is mostly just based on my own experience.
|
||||
fn default_player_prefixes() -> HashMap<String, char> {
|
||||
let mut out: HashMap<String, char> = HashMap::new();
|
||||
fn default_player_prefixes() -> HashMap<String, String> {
|
||||
let mut out: HashMap<String, String> = HashMap::new();
|
||||
|
||||
out.insert("chromium".to_owned(), 'g');
|
||||
out.insert("Clementine".to_owned(), 'c');
|
||||
out.insert("default".to_owned(), '>');
|
||||
out.insert("Firefox".to_owned(), 'f');
|
||||
out.insert("mpv".to_owned(), 'm');
|
||||
out.insert("Spotify".to_owned(), 's');
|
||||
out.insert("VLC Media Player".to_owned(), 'v');
|
||||
out.insert("chromium".to_owned(), "g".to_owned());
|
||||
out.insert("Clementine".to_owned(), "c".to_owned());
|
||||
out.insert("default".to_owned(), ">".to_owned());
|
||||
out.insert("Firefox".to_owned(), "f".to_owned());
|
||||
out.insert("mpv".to_owned(), "m".to_owned());
|
||||
out.insert("Spotify".to_owned(), "s".to_owned());
|
||||
out.insert("VLC Media Player".to_owned(), "v".to_owned());
|
||||
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ pub struct Data {
|
|||
/// HashMap representing the current output strings for each configured field.
|
||||
pub field_text: HashMap<String, String>,
|
||||
/// What character to render as prefix.
|
||||
pub prefix: char,
|
||||
pub prefix: String,
|
||||
}
|
||||
|
||||
/// Defaults for Data struct.
|
||||
|
|
@ -22,7 +22,7 @@ impl Default for Data {
|
|||
Self {
|
||||
current_player: None,
|
||||
field_text: HashMap::new(),
|
||||
prefix: ' ',
|
||||
prefix: "".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -114,6 +114,10 @@ pub fn update_message(cfg: &Config, data: &mut Data, ratings: &Vec<String>) {
|
|||
}
|
||||
None => {
|
||||
trace!("update_messages: field {} has no value!", key);
|
||||
if field.field.eq("bs:isFavorite") {
|
||||
data.field_text.remove(key);
|
||||
continue;
|
||||
}
|
||||
data.field_text.insert(
|
||||
key.to_owned(),
|
||||
format!("No {}", key.trim_start_matches("xesam:")),
|
||||
|
|
|
|||
|
|
@ -13,13 +13,13 @@ use mpris::PlayerFinder;
|
|||
/// cfg: Config struct for the program, containing the hashmap of prefixes.
|
||||
/// data: mutable char containing the active prefix.
|
||||
/// name: name of active player, to fetch the appropriate prefix from cfg.
|
||||
fn update_prefix(cfg: &Config, data: &mut char, name: &str) {
|
||||
fn update_prefix(cfg: &Config, data: &mut Data, name: &str) {
|
||||
if let Some(char) = cfg.player_prefixes.get(name) {
|
||||
*data = *char;
|
||||
trace!("updated prefix to {}", data);
|
||||
data.prefix = char.to_owned();
|
||||
trace!("updated prefix to {}", data.prefix);
|
||||
} else {
|
||||
*data = *cfg.player_prefixes.get("default").unwrap_or(&'>');
|
||||
trace!("set prefix to default ({})", data);
|
||||
data.prefix = cfg.player_prefixes.get("default").unwrap().to_owned();
|
||||
trace!("set prefix to default ({})", data.prefix);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -38,14 +38,14 @@ pub fn update_players(pf: &PlayerFinder, cfg: &Config, data: &mut Data) {
|
|||
data.current_player = None;
|
||||
debug!("update_players: no players found!")
|
||||
} else {
|
||||
let mut trees = vec![BTreeMap::new(), BTreeMap::new(), BTreeMap::new()];
|
||||
let mut trees = vec![BTreeMap::new(), BTreeMap::new()];
|
||||
for player in players {
|
||||
if let Ok(status) = player.get_playback_status() {
|
||||
let idx = cfg.find_player_priorities_idx(player.identity());
|
||||
match status {
|
||||
mpris::PlaybackStatus::Playing => trees[0].insert(idx, player),
|
||||
mpris::PlaybackStatus::Paused => trees[1].insert(idx, player),
|
||||
mpris::PlaybackStatus::Stopped => trees[2].insert(idx, player),
|
||||
mpris::PlaybackStatus::Paused => trees[0].insert(idx, player),
|
||||
mpris::PlaybackStatus::Stopped => trees[1].insert(idx, player),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ pub fn update_players(pf: &PlayerFinder, cfg: &Config, data: &mut Data) {
|
|||
// select the player with the highest priority.
|
||||
for mut tree in trees {
|
||||
if let Some((_, player)) = tree.pop_first() {
|
||||
update_prefix(cfg, &mut data.prefix, player.identity());
|
||||
update_prefix(cfg, data, player.identity());
|
||||
debug!("update_players: updated player to {}!", player.identity());
|
||||
data.current_player = Some(player);
|
||||
break;
|
||||
|
|
|
|||
Loading…
Reference in New Issue