feat: mangled cfg/data structs so the player_prefixes field now accepts strings as well, allowing for more customization

This commit is contained in:
Djairo Hougee 2024-04-23 23:59:52 +02:00
parent 2ea0e57338
commit 6ce280bb9c
6 changed files with 304 additions and 259 deletions

View File

@ -1,24 +1,24 @@
//! This file contains all driver code for the program. //! This file contains all driver code for the program.
use core::time; use crate::print_players::print_players;
use std::ffi::OsString; use crate::print_text::print_text;
use std::sync::Arc; use crate::update_message::update_message;
use std::sync::atomic::{AtomicBool, Ordering}; use crate::update_players::update_players;
use std::thread;
use clap::Parser; use clap::Parser;
use core::time;
use log::{error, info, warn}; use log::{error, info, warn};
use mpris::PlayerFinder; 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::cli::Cli;
use structs::{config::Config, data::Data}; 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 print_players;
mod update_message;
mod print_text; mod print_text;
mod structs; mod structs;
mod print_players; mod update_message;
mod update_players;
/// This function deals with an incoming (USR1) signal. /// This function deals with an incoming (USR1) signal.
/// It is hard-coded to play/pause the active player. /// It is hard-coded to play/pause the active player.
@ -26,17 +26,15 @@ mod print_players;
/// input: /// input:
/// data: Data struct for active configuration. /// data: Data struct for active configuration.
fn handle_signal(data: &Data) { fn handle_signal(data: &Data) {
if let Some(p) = &data.current_player { if let Some(p) = &data.current_player {
match p.checked_play_pause() { match p.checked_play_pause() {
Ok(b) => { Ok(b) => match b {
match b { true => info!("Player play/paused succesfully!"),
true => info!("Player play/paused succesfully!"), false => warn!("Failed to send play/pause signal!"),
false => warn!("Failed to send play/pause signal!"), },
Err(e) => error!("{e}"),
} }
},
Err(e) => error!("{e}"),
} }
}
} }
/// This function contains the default maim loop body of the program. /// This function contains the default maim loop body of the program.
@ -48,9 +46,9 @@ fn handle_signal(data: &Data) {
/// data: mutable Data struct, active state of the program /// data: mutable Data struct, active state of the program
/// r: pre-computed rating strings /// r: pre-computed rating strings
fn default_loop(pf: &PlayerFinder, cfg: &Config, data: &mut Data, r: &Vec<String>) { fn default_loop(pf: &PlayerFinder, cfg: &Config, data: &mut Data, r: &Vec<String>) {
update_players(pf, cfg, data); update_players(pf, cfg, data);
update_message(cfg, data, r); update_message(cfg, data, r);
print_text(cfg, data); print_text(cfg, data);
} }
/// Main function. Mostly concerned with initialisation. /// Main function. Mostly concerned with initialisation.
@ -61,48 +59,55 @@ fn main() {
// logging initialisation // logging initialisation
std::env::set_var::<&str, OsString>("RUST_LOG", cli.log_level.into()); std::env::set_var::<&str, OsString>("RUST_LOG", cli.log_level.into());
if let Err(e) = env_logger::init() { if let Err(e) = env_logger::init() {
error!("{e}"); error!("{e}");
return return;
} }
// Config, Data, and PlayerFinder initialisation // Config, Data, and PlayerFinder initialisation
match confy::load::<Config>("polybar-now-playing", cli.config_file.as_str()) { match confy::load::<Config>("polybar-now-playing", cli.config_file.as_str()) {
Ok(cfg) => { Ok(mut cfg) => {
let mut data: Data = Data::default(); if let None = cfg.player_prefixes.get("default") {
let rating_strings = cfg.build_rating_strings(); cfg.player_prefixes
.insert("default".to_owned(), ">".to_owned());
}
let pf: PlayerFinder; let mut data: Data = Data::default();
match PlayerFinder::new() { let rating_strings = cfg.build_rating_strings();
Ok(finder) => pf = finder,
Err(e) => { let pf: PlayerFinder;
error!("{e}"); match PlayerFinder::new() {
return Ok(finder) => pf = finder,
}, Err(e) => {
error!("{e}");
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))
{
error!("{e}");
return;
}
// main body loop
loop {
thread::sleep(time::Duration::from_millis(cfg.update_delay));
match cli.list {
true => print_players(&pf),
false => default_loop(&pf, &cfg, &mut data, &rating_strings),
}
if term.load(Ordering::Relaxed) {
handle_signal(&data);
term.swap(false, Ordering::Relaxed);
};
}
} }
Err(e) => {
// signal interception initialisation error!("{e}");
let term = Arc::new(AtomicBool::new(false));
if let Err(e) = signal_hook::flag::register(signal_hook::consts::SIGUSR1, Arc::clone(&term)) {
error!("{e}");
return
} }
};
// main body loop
loop {
thread::sleep(time::Duration::from_millis(cfg.update_delay));
match cli.list {
true => print_players(&pf),
false => default_loop(&pf, &cfg, &mut data, &rating_strings),
}
if term.load(Ordering::Relaxed) {
handle_signal(&data);
term.swap(false, Ordering::Relaxed);
};
}
},
Err(e) => {
error!("{e}");
},
};
} }

View File

@ -72,7 +72,7 @@ fn cutoff(
/// b: mutable String builder to append to. /// b: mutable String builder to append to.
/// data: Data struct containing the current prefix character. /// data: Data struct containing the current prefix character.
fn append_prefix(b: &mut Builder, data: &Data) { fn append_prefix(b: &mut Builder, data: &Data) {
b.append(data.prefix); b.append(data.prefix.to_owned());
b.append(" "); b.append(" ");
} }

View File

@ -1,101 +1,139 @@
//! This file contains structs and functions concerning themselves with the configuration of the program. //! This file contains structs and functions concerning themselves with the configuration of the program.
use serde::{Deserialize, Serialize};
use std::collections::HashMap; 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. /// 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. /// There is also support for custom formatting.
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Field { pub struct Field {
/// The name of the metadata field. /// The name of the metadata field.
pub field: String, pub field: String,
/// The maximum length of the metadata field's output. /// The maximum length of the metadata field's output.
pub num_chars: u32, pub num_chars: u32,
/// Formatting to apply. (the value "{}" is substituted with the actual string) /// Formatting to apply. (the value "{}" is substituted with the actual string)
pub format: String pub format: String,
} }
impl Field { impl Field {
/// Create a new field from given values. /// Create a new field from given values.
/// input: /// input:
/// field: name of the field /// field: name of the field
/// num_chars: maximum length of the field /// num_chars: maximum length of the field
/// format: what formatting to apply to the field /// format: what formatting to apply to the field
/// ///
/// returns: /// returns:
/// a new Field with the given parameters. /// a new Field with the given parameters.
fn new(field: String, num_chars: u32, format: String) -> Self { fn new(field: String, num_chars: u32, format: String) -> Self {
Field { Field {
field, field,
num_chars, num_chars,
format format,
}
} }
}
/// Create a new field from given values. /// Create a new field from given values.
/// input: /// input:
/// field: name of the field /// field: name of the field
/// num_chars: maximum length of the field /// num_chars: maximum length of the field
/// format: (optional), formatting to apply. /// format: (optional), formatting to apply.
/// ///
/// returns: /// returns:
/// a new Field with the given parameters. /// a new Field with the given parameters.
pub fn constructor(field: &str, num_chars: u32, format: Option<String>) -> Self { pub fn constructor(field: &str, num_chars: u32, format: Option<String>) -> Self {
if let Some(format) = format { if let Some(format) = format {
Self::new(field.to_owned(), num_chars, format) Self::new(field.to_owned(), num_chars, format)
} else { } else {
Self::new(field.to_owned(), num_chars, "{}".to_owned()) Self::new(field.to_owned(), num_chars, "{}".to_owned())
}
} }
}
} }
/// This struct contains the 3 symbols used to represent a given userRating in a media field. /// This struct contains the 3 symbols used to represent a given userRating in a media field.
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Rating { pub struct Rating {
/// character for an empty token /// character for an empty token
pub nil: char, pub nil: char,
/// character for a half token /// character for a half token
pub half: char, pub half: char,
/// character for a full token /// character for a full token
pub full: char pub full: char,
} }
impl Rating { impl Rating {
/// This function repeats a given character n times, interspersing each occurence with a space. /// This function repeats a given character n times, interspersing each occurence with a space.
/// It's kinda unwieldy here, but this is the least odd place to put it. /// It's kinda unwieldy here, but this is the least odd place to put it.
/// ///
/// input: /// input:
/// c: character to repeat /// c: character to repeat
/// n: number of times to repeat the character /// n: number of times to repeat the character
/// ///
/// returns: /// returns:
/// string of the form '\<c\> '{n} /// string of the form '\<c\> '{n}
fn repeat(c: char, n: usize) -> String { fn repeat(c: char, n: usize) -> String {
let mut s = c.to_string(); let mut s = c.to_string();
s.push(' '); s.push(' ');
s.repeat(n) s.repeat(n)
} }
/// As there are only a small, run-time defined variances on possible ratings (from 5 empty tokens to 5 full ones), /// As there are only a small, run-time defined variances on possible ratings (from 5 empty tokens to 5 full ones),
/// this function computes all these strings during initialization. This saves a near negligble amount of operations during run-time. /// this function computes all these strings during initialization. This saves a near negligble amount of operations during run-time.
/// ///
/// output: /// output:
/// Vec of Strings representing all possible rating configurations /// Vec of Strings representing all possible rating configurations
fn build_rating_strings(&self) -> Vec<String> { fn build_rating_strings(&self) -> Vec<String> {
let mut out = Vec::new(); let mut out = Vec::new();
out.push(Self::repeat(self.nil, 5)); out.push(Self::repeat(self.nil, 5));
out.push(format!("{}{}", Self::repeat(self.half, 1), Self::repeat(self.nil, 4))); out.push(format!(
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))); Self::repeat(self.half, 1),
out.push(format!("{}{}", Self::repeat(self.full, 2), Self::repeat(self.nil, 3))); Self::repeat(self.nil, 4)
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!(
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))); Self::repeat(self.full, 1),
out.push(format!("{}{}", Self::repeat(self.full, 4), Self::repeat(self.half, 1))); Self::repeat(self.nil, 4)
out.push(Self::repeat(self.full, 5)); ));
out 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
}
} }
/// Defaults for Rating struct. /// Defaults for Rating struct.
@ -103,126 +141,124 @@ impl Rating {
impl Default for Rating { impl Default for Rating {
fn default() -> Self { fn default() -> Self {
Self { Self {
nil: '-', nil: '-',
half: '/', half: '/',
full: '+' full: '+',
} }
} }
} }
/// This struct contains all possible configuration fields. /// 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. /// It should not be used as mutable; all data in this struct should effectively be treated as read-only.
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Config { pub struct Config {
/// Whether to hide the last output if there are currently no accepted players. /// Whether to hide the last output if there are currently no accepted players.
pub hide_output: bool, pub hide_output: bool,
/// Whether to apply 'fuzzy' cutoff to strings exceeding their maximum lenght. /// Whether to apply 'fuzzy' cutoff to strings exceeding their maximum lenght.
pub fuzzy: bool, pub fuzzy: bool,
/// Whether to render the prefix at all. /// Whether to render the prefix at all.
pub render_prefix: bool, pub render_prefix: bool,
/// Time in milliseconds to wait between loops of the program. /// Time in milliseconds to wait between loops of the program.
pub update_delay: u64, pub update_delay: u64,
/// String to insert between different metadata fields. /// String to insert between different metadata fields.
pub metadata_separator: String, pub metadata_separator: String,
/// Character to insert between Array values (used when a MetadataVaue is of type Vec (ie multiple artists on one track)). /// Character to insert between Array values (used when a MetadataVaue is of type Vec (ie multiple artists on one track)).
pub array_separator: char, pub array_separator: char,
/// Character to insert when a string is truncated. None implies no cut off character is inserted and the strings are truncated as is. /// Character to insert when a string is truncated. None implies no cut off character is inserted and the strings are truncated as is.
pub break_character: Option<char>, pub break_character: Option<char>,
/// Hashmap of mpris identities, describing what players are considered acceptable. /// Hashmap of mpris identities, describing what players are considered acceptable.
pub player_priorities: HashMap<String, u8>, pub player_priorities: HashMap<String, u8>,
/// Characters to use for the xesam:userRating field. /// Characters to use for the xesam:userRating field.
/// If None, default values are used ('-', '/', '+'). /// If None, default values are used ('-', '/', '+').
pub rating_icons: Option<Rating>, pub rating_icons: Option<Rating>,
/// Vec of Fields. Each field represents one metadata_string to be shown in output, as well as the maximum number of characters for this field. /// Vec of Fields. Each field represents one metadata_string to be shown in output, as well as the maximum number of characters for this field.
/// Output is shown based on Vec index (vec\[0\] first, vec\[1\] second, etc). /// Output is shown based on Vec index (vec\[0\] first, vec\[1\] second, etc).
pub metadata_fields: Vec<Field>, pub metadata_fields: Vec<Field>,
/// Hashmap which maps Player Identities (strings; key) to prefixes (char; value). /// Hashmap which maps Player Identities (strings; key) to prefixes (char; value).
/// If left blank all players will use the default prefix character ('>'). /// 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. /// 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). /// 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. /// Currently only escapes '&', i will be adding more as i run into them.
pub escape_chars: bool, pub escape_chars: bool,
} }
/// Defaults for the Config struct. /// Defaults for the Config struct.
/// This is generated when a non-existant config file is specified in the command line. /// This is generated when a non-existant config file is specified in the command line.
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Config { Config {
hide_output: true, hide_output: true,
fuzzy: false, fuzzy: false,
render_prefix: true, render_prefix: true,
update_delay: 300_u64, update_delay: 300_u64,
metadata_separator: " | ".to_owned(), metadata_separator: " | ".to_owned(),
array_separator: '+', array_separator: '+',
break_character: Some('-'), break_character: Some('-'),
player_priorities: Config::default_player_priorities(), player_priorities: Config::default_player_priorities(),
rating_icons: Some(Rating::default()), rating_icons: Some(Rating::default()),
metadata_fields: Config::default_metadata_fields(), metadata_fields: Config::default_metadata_fields(),
player_prefixes: Config::default_player_prefixes(), player_prefixes: Config::default_player_prefixes(),
escape_chars: false, escape_chars: false,
} }
} }
} }
impl Config { impl Config {
/// This function returns the index of a given player identity in the player_priorities hashmap. /// This function returns the index of a given player identity in the player_priorities hashmap.
/// If the given identity is not in the map, the value of i32::MAX is returned instead. /// If the given identity is not in the map, the value of i32::MAX is returned instead.
pub fn find_player_priorities_idx(&self, name: &str) -> u8 { pub fn find_player_priorities_idx(&self, name: &str) -> u8 {
match self.player_priorities.get(name) { match self.player_priorities.get(name) {
Some(val) => *val, Some(val) => *val,
None => u8::MAX, None => u8::MAX,
}
} }
}
/// This function builds the pre-computed rating strings for a given Rating_icons field. /// This function builds the pre-computed rating strings for a given Rating_icons field.
pub fn build_rating_strings(&self) -> Vec<String> { pub fn build_rating_strings(&self) -> Vec<String> {
match self.rating_icons.as_ref() { match self.rating_icons.as_ref() {
Some(r) => r.build_rating_strings(), Some(r) => r.build_rating_strings(),
None => Rating::default().build_rating_strings(), None => Rating::default().build_rating_strings(),
}
} }
}
/// This function returns the default player_priorities, used when a non-existent config file is requested. /// This function returns the default player_priorities, used when a non-existent config file is requested.
/// The values of these are based on nothing but my own experience; in fact I'm not even sure if the Spotify app's identity is correct. /// The values of these are based on nothing but my own experience; in fact I'm not even sure if the Spotify app's identity is correct.
fn default_player_priorities() -> HashMap< String, u8> { fn default_player_priorities() -> HashMap<String, u8> {
let mut out = HashMap::new(); let mut out = HashMap::new();
out.insert("Clementine".to_owned(), 1); out.insert("Clementine".to_owned(), 1);
out.insert("Spotify".to_owned(), 2); out.insert("Spotify".to_owned(), 2);
out.insert("mpv".to_owned(), 3); out.insert("mpv".to_owned(), 3);
out.insert("VLC Media Player".to_owned(), 4); out.insert("VLC Media Player".to_owned(), 4);
out.insert("Firefox".to_owned(), 5); out.insert("Firefox".to_owned(), 5);
out.insert("Chromium".to_owned(), 6); out.insert("Chromium".to_owned(), 6);
out out
} }
/// This function returns the default metadata fields, used when a non-existent config file is requested. /// This function returns the default metadata fields, used when a non-existent config file is requested.
/// It contains the "title" and "artist" fields, with 40 and 20 maximum characters respectively. /// It contains the "title" and "artist" fields, with 40 and 20 maximum characters respectively.
fn default_metadata_fields() -> Vec<Field> { fn default_metadata_fields() -> Vec<Field> {
vec![ vec![
Field::constructor("xesam:title", 40, None), 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. /// 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. /// Like the player priorities function, this is mostly just based on my own experience.
fn default_player_prefixes() -> HashMap<String, char> { fn default_player_prefixes() -> HashMap<String, String> {
let mut out: HashMap<String, char> = HashMap::new(); let mut out: HashMap<String, String> = HashMap::new();
out.insert("chromium".to_owned(), 'g'); out.insert("chromium".to_owned(), "g".to_owned());
out.insert("Clementine".to_owned(), 'c'); out.insert("Clementine".to_owned(), "c".to_owned());
out.insert("default".to_owned(), '>'); out.insert("default".to_owned(), ">".to_owned());
out.insert("Firefox".to_owned(), 'f'); out.insert("Firefox".to_owned(), "f".to_owned());
out.insert("mpv".to_owned(), 'm'); out.insert("mpv".to_owned(), "m".to_owned());
out.insert("Spotify".to_owned(), 's'); out.insert("Spotify".to_owned(), "s".to_owned());
out.insert("VLC Media Player".to_owned(), 'v'); out.insert("VLC Media Player".to_owned(), "v".to_owned());
out out
} }
} }

View File

@ -6,13 +6,13 @@ use mpris::Player;
/// This struct concerns itself with the current state of the program. /// This struct concerns itself with the current state of the program.
pub struct Data { pub struct Data {
/// Represents the media player marked as active. /// Represents the media player marked as active.
/// Should be None when no (accepted) players are active. /// Should be None when no (accepted) players are active.
pub current_player: Option<Player>, pub current_player: Option<Player>,
/// HashMap representing the current output strings for each configured field. /// HashMap representing the current output strings for each configured field.
pub field_text: HashMap<String, String>, pub field_text: HashMap<String, String>,
/// What character to render as prefix. /// What character to render as prefix.
pub prefix: char, pub prefix: String,
} }
/// Defaults for Data struct. /// Defaults for Data struct.
@ -20,9 +20,9 @@ pub struct Data {
impl Default for Data { impl Default for Data {
fn default() -> Self { fn default() -> Self {
Self { Self {
current_player: None, current_player: None,
field_text: HashMap::new(), field_text: HashMap::new(),
prefix: ' ', prefix: "".to_owned(),
} }
} }
} }

View File

@ -114,6 +114,10 @@ pub fn update_message(cfg: &Config, data: &mut Data, ratings: &Vec<String>) {
} }
None => { None => {
trace!("update_messages: field {} has no value!", key); trace!("update_messages: field {} has no value!", key);
if field.field.eq("bs:isFavorite") {
data.field_text.remove(key);
continue;
}
data.field_text.insert( data.field_text.insert(
key.to_owned(), key.to_owned(),
format!("No {}", key.trim_start_matches("xesam:")), format!("No {}", key.trim_start_matches("xesam:")),

View File

@ -13,13 +13,13 @@ use mpris::PlayerFinder;
/// cfg: Config struct for the program, containing the hashmap of prefixes. /// cfg: Config struct for the program, containing the hashmap of prefixes.
/// data: mutable char containing the active prefix. /// data: mutable char containing the active prefix.
/// name: name of active player, to fetch the appropriate prefix from cfg. /// 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) { if let Some(char) = cfg.player_prefixes.get(name) {
*data = *char; data.prefix = char.to_owned();
trace!("updated prefix to {}", data); trace!("updated prefix to {}", data.prefix);
} else { } else {
*data = *cfg.player_prefixes.get("default").unwrap_or(&'>'); data.prefix = cfg.player_prefixes.get("default").unwrap().to_owned();
trace!("set prefix to default ({})", data); 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; data.current_player = None;
debug!("update_players: no players found!") debug!("update_players: no players found!")
} else { } else {
let mut trees = vec![BTreeMap::new(), BTreeMap::new(), BTreeMap::new()]; let mut trees = vec![BTreeMap::new(), BTreeMap::new()];
for player in players { for player in players {
if let Ok(status) = player.get_playback_status() { if let Ok(status) = player.get_playback_status() {
let idx = cfg.find_player_priorities_idx(player.identity()); let idx = cfg.find_player_priorities_idx(player.identity());
match status { match status {
mpris::PlaybackStatus::Playing => trees[0].insert(idx, player), mpris::PlaybackStatus::Playing => trees[0].insert(idx, player),
mpris::PlaybackStatus::Paused => trees[1].insert(idx, player), mpris::PlaybackStatus::Paused => trees[0].insert(idx, player),
mpris::PlaybackStatus::Stopped => trees[2].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. // select the player with the highest priority.
for mut tree in trees { for mut tree in trees {
if let Some((_, player)) = tree.pop_first() { 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()); debug!("update_players: updated player to {}!", player.identity());
data.current_player = Some(player); data.current_player = Some(player);
break; break;