use crate::clients::mpd::{get_client, get_duration, get_elapsed, MpdConnectionError}; use crate::config::CommonConfig; use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext}; use crate::popup::Popup; use crate::try_send; use color_eyre::Result; use dirs::{audio_dir, home_dir}; use glib::Continue; use gtk::gdk_pixbuf::Pixbuf; use gtk::prelude::*; use gtk::{Button, Image, Label, Orientation, Scale}; use mpd_client::commands; use mpd_client::responses::{PlayState, Song, Status}; use mpd_client::tag::Tag; use regex::Regex; use serde::Deserialize; use std::path::PathBuf; use tokio::spawn; use tokio::sync::mpsc; use tokio::sync::mpsc::{Receiver, Sender}; use tracing::error; #[derive(Debug)] pub enum PlayerCommand { Previous, Toggle, Next, Volume(u8), } #[derive(Debug, Deserialize, Clone)] pub struct Icons { /// Icon to display when playing. #[serde(default = "default_icon_play")] play: String, /// Icon to display when paused. #[serde(default = "default_icon_pause")] pause: String, /// Icon to display under volume slider #[serde(default = "default_icon_volume")] volume: String, } impl Default for Icons { fn default() -> Self { Self { pause: default_icon_pause(), play: default_icon_play(), volume: default_icon_volume(), } } } #[derive(Debug, Deserialize, Clone)] pub struct MpdModule { /// TCP or Unix socket address. #[serde(default = "default_socket")] host: String, /// Format of current song info to display on the bar. #[serde(default = "default_format")] format: String, /// Player state icons #[serde(default)] icons: Icons, /// Path to root of music directory. #[serde(default = "default_music_dir")] music_dir: PathBuf, #[serde(flatten)] pub common: Option, } fn default_socket() -> String { String::from("localhost:6600") } fn default_format() -> String { String::from("{icon} {title} / {artist}") } fn default_icon_play() -> String { String::from("") } fn default_icon_pause() -> String { String::from("") } fn default_icon_volume() -> String { String::from("墳") } fn default_music_dir() -> PathBuf { audio_dir().unwrap_or_else(|| home_dir().map(|dir| dir.join("Music")).unwrap_or_default()) } /// Attempts to read the first value for a tag /// (since the MPD client returns a vector of tags, or None) pub fn try_get_first_tag(vec: Option<&Vec>) -> Option<&str> { vec.and_then(|vec| vec.first().map(String::as_str)) } /// Formats a duration given in seconds /// in hh:mm format fn format_time(time: u64) -> String { let minutes = (time / 60) % 60; let seconds = time % 60; format!("{:0>2}:{:0>2}", minutes, seconds) } /// Extracts the formatting tokens from a formatting string fn get_tokens(re: &Regex, format_string: &str) -> Vec { re.captures_iter(format_string) .map(|caps| caps[1].to_string()) .collect::>() } #[derive(Clone, Debug)] pub struct SongUpdate { song: Song, status: Status, display_string: String, } impl Module