1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-07-01 18:51:04 +02:00

feat(music): support for using images in name_map, additional icon options

This commit is contained in:
Jake Stanger 2023-01-29 22:49:22 +00:00
parent b054c17d14
commit 96141d4990
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
3 changed files with 158 additions and 79 deletions

View file

@ -17,11 +17,18 @@ in MPRIS mode, the widget will listen to all players and automatically detect/di
| `format` | `string` | `{icon} {title} / {artist}` | Format string for the widget. More info below. | | `format` | `string` | `{icon} {title} / {artist}` | Format string for the widget. More info below. |
| `truncate` or `truncate.mode` | `start` or `middle` or `end` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand version if specifying a length. | | `truncate` or `truncate.mode` | `start` or `middle` or `end` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand version if specifying a length. |
| `truncate.length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. | | `truncate.length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
| `icons.play` | `string` | `` | Icon to show when playing. | | `icons.play` | `string/image` | `` | Icon to show when playing. |
| `icons.pause` | `string` | `` | Icon to show when paused. | | `icons.pause` | `string/image` | `` | Icon to show when paused. |
| `icons.volume` | `string` | `墳` | Icon to show under popup volume slider. | | `icons.prev` | `string/image` | `玲` | Icon to show on previous button. |
| `host` | `string` | `localhost:6600` | [MPD Only] TCP or Unix socket for the MPD server. | | `icons.next` | `string/image` | `怜` | Icon to show on next button. |
| `music_dir` | `string` | `$HOME/Music` | [MPD Only] Path to MPD server's music directory on disc. Required for album art. | | `icons.volume` | `string/image` | `墳` | Icon to show under popup volume slider. |
| `icons.track` | `string/image` | `` | Icon to show next to track title. |
| `icons.album` | `string/image` | `` | Icon to show next to album name. |
| `icons.artist` | `string/image` | `ﴁ` | Icon to show next to artist name. |
| `host` | `string/image` | `localhost:6600` | [MPD Only] TCP or Unix socket for the MPD server. |
| `music_dir` | `string/image` | `$HOME/Music` | [MPD Only] Path to MPD server's music directory on disc. Required for album art. |
See [here](images) for information on images.
<details> <details>
<summary>JSON</summary> <summary>JSON</summary>
@ -122,24 +129,25 @@ and will be replaced with values from the currently playing track:
## Styling ## Styling
| Selector | Description | | Selector | Description |
|------------------------------------------|------------------------------------------| |-------------------------------------|------------------------------------------|
| `#music` | Tray widget button | | `#music` | Tray widget button |
| `#popup-music` | Popup box | | `#popup-music` | Popup box |
| `#popup-music #album-art` | Album art image inside popup box | | `#popup-music #album-art` | Album art image inside popup box |
| `#popup-music #title` | Track title container inside popup box | | `#popup-music #title` | Track title container inside popup box |
| `#popup-music #title .icon` | Track title icon label inside popup box | | `#popup-music #title .icon` | Track title icon label inside popup box |
| `#popup-music #title .label` | Track title label inside popup box | | `#popup-music #title .label` | Track title label inside popup box |
| `#popup-music #album` | Track album container inside popup box | | `#popup-music #album` | Track album container inside popup box |
| `#popup-music #album .icon` | Track album icon label inside popup box | | `#popup-music #album .icon` | Track album icon label inside popup box |
| `#popup-music #album .label` | Track album label inside popup box | | `#popup-music #album .label` | Track album label inside popup box |
| `#popup-music #artist` | Track artist container inside popup box | | `#popup-music #artist` | Track artist container inside popup box |
| `#popup-music #artist .icon` | Track artist icon label inside popup box | | `#popup-music #artist .icon` | Track artist icon label inside popup box |
| `#popup-music #artist .label` | Track artist label inside popup box | | `#popup-music #artist .label` | Track artist label inside popup box |
| `#popup-music #controls` | Controls container inside popup box | | `#popup-music #controls` | Controls container inside popup box |
| `#popup-music #controls #btn-prev` | Previous button inside popup box | | `#popup-music #controls #btn-prev` | Previous button inside popup box |
| `#popup-music #controls #btn-play-pause` | Play/pause button inside popup box | | `#popup-music #controls #btn-play` | Play button inside popup box |
| `#popup-music #controls #btn-next` | Next button inside popup box | | `#popup-music #controls #btn-pause` | Pause button inside popup box |
| `#popup-music #volume` | Volume container inside popup box | | `#popup-music #controls #btn-next` | Next button inside popup box |
| `#popup-music #volume #slider` | Volume slider popup box | | `#popup-music #volume` | Volume container inside popup box |
| `#popup-music #volume .icon` | Volume icon label inside popup box | | `#popup-music #volume #slider` | Volume slider popup box |
| `#popup-music #volume .icon` | Volume icon label inside popup box |

View file

@ -8,12 +8,34 @@ pub struct Icons {
/// Icon to display when playing. /// Icon to display when playing.
#[serde(default = "default_icon_play")] #[serde(default = "default_icon_play")]
pub(crate) play: String, pub(crate) play: String,
/// Icon to display when paused. /// Icon to display when paused.
#[serde(default = "default_icon_pause")] #[serde(default = "default_icon_pause")]
pub(crate) pause: String, pub(crate) pause: String,
/// Icon to display for previous button.
#[serde(default = "default_icon_prev")]
pub(crate) prev: String,
/// Icon to display for next button.
#[serde(default = "default_icon_next")]
pub(crate) next: String,
/// Icon to display under volume slider /// Icon to display under volume slider
#[serde(default = "default_icon_volume")] #[serde(default = "default_icon_volume")]
pub(crate) volume: String, pub(crate) volume: String,
/// Icon to display nex to track title
#[serde(default = "default_icon_track")]
pub(crate) track: String,
/// Icon to display nex to album name
#[serde(default = "default_icon_album")]
pub(crate) album: String,
/// Icon to display nex to artist name
#[serde(default = "default_icon_artist")]
pub(crate) artist: String,
} }
impl Default for Icons { impl Default for Icons {
@ -21,7 +43,12 @@ impl Default for Icons {
Self { Self {
pause: default_icon_pause(), pause: default_icon_pause(),
play: default_icon_play(), play: default_icon_play(),
prev: default_icon_prev(),
next: default_icon_next(),
volume: default_icon_volume(), volume: default_icon_volume(),
track: default_icon_track(),
album: default_icon_album(),
artist: default_icon_artist(),
} }
} }
} }
@ -73,7 +100,7 @@ fn default_socket() -> String {
} }
fn default_format() -> String { fn default_format() -> String {
String::from("{icon} {title} / {artist}") String::from("{title} / {artist}")
} }
fn default_icon_play() -> String { fn default_icon_play() -> String {
@ -84,10 +111,30 @@ fn default_icon_pause() -> String {
String::from("") String::from("")
} }
fn default_icon_prev() -> String {
String::from("\u{f9ad}")
}
fn default_icon_next() -> String {
String::from("\u{f9ac}")
}
fn default_icon_volume() -> String { fn default_icon_volume() -> String {
String::from("") String::from("")
} }
fn default_icon_track() -> String {
String::from("\u{f886}")
}
fn default_icon_album() -> String {
String::from("\u{f524}")
}
fn default_icon_artist() -> String {
String::from("\u{fd01}")
}
fn default_music_dir() -> PathBuf { fn default_music_dir() -> PathBuf {
audio_dir().unwrap_or_else(|| home_dir().map(|dir| dir.join("Music")).unwrap_or_default()) audio_dir().unwrap_or_else(|| home_dir().map(|dir| dir.join("Music")).unwrap_or_default())
} }

View file

@ -1,7 +1,7 @@
mod config; mod config;
use crate::clients::music::{self, MusicClient, PlayerState, PlayerUpdate, Status, Track}; use crate::clients::music::{self, MusicClient, PlayerState, PlayerUpdate, Status, Track};
use crate::image::ImageProvider; use crate::image::{new_icon_button, new_icon_label, ImageProvider};
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::popup::Popup; use crate::popup::Popup;
use crate::{send_async, try_send}; use crate::{send_async, try_send};
@ -18,7 +18,7 @@ use tokio::sync::mpsc::{Receiver, Sender};
use tracing::error; use tracing::error;
pub use self::config::MusicModule; pub use self::config::MusicModule;
use self::config::{Icons, PlayerType}; use self::config::PlayerType;
#[derive(Debug)] #[derive(Debug)]
pub enum PlayerCommand { pub enum PlayerCommand {
@ -80,7 +80,6 @@ impl Module<Button> for MusicModule {
mut rx: Receiver<Self::ReceiveMessage>, mut rx: Receiver<Self::ReceiveMessage>,
) -> Result<()> { ) -> Result<()> {
let format = self.format.clone(); let format = self.format.clone();
let icons = self.icons.clone();
let re = Regex::new(r"\{([\w-]+)}")?; let re = Regex::new(r"\{([\w-]+)}")?;
let tokens = get_tokens(&re, self.format.as_str()); let tokens = get_tokens(&re, self.format.as_str());
@ -102,13 +101,8 @@ impl Module<Button> for MusicModule {
match update { match update {
PlayerUpdate::Update(track, status) => match *track { PlayerUpdate::Update(track, status) => match *track {
Some(track) => { Some(track) => {
let display_string = replace_tokens( let display_string =
format.as_str(), replace_tokens(format.as_str(), &tokens, &track, &status);
&tokens,
&track,
&status,
&icons,
);
let update = SongUpdate { let update = SongUpdate {
song: track, song: track,
@ -160,15 +154,22 @@ impl Module<Button> for MusicModule {
info: &ModuleInfo, info: &ModuleInfo,
) -> Result<ModuleWidget<Button>> { ) -> Result<ModuleWidget<Button>> {
let button = Button::new(); let button = Button::new();
let button_contents = gtk::Box::new(Orientation::Horizontal, 5);
button.add(&button_contents);
let icon_play = new_icon_label(&self.icons.play, info.icon_theme, 24);
let icon_pause = new_icon_label(&self.icons.pause, info.icon_theme, 24);
let label = Label::new(None); let label = Label::new(None);
label.set_angle(info.bar_position.get_angle()); label.set_angle(info.bar_position.get_angle());
if let Some(truncate) = self.truncate { if let Some(truncate) = self.truncate {
truncate.truncate_label(&label); truncate.truncate_label(&label);
} }
button.add(&label); button_contents.add(&icon_pause);
button_contents.add(&icon_play);
button_contents.add(&label);
let orientation = info.bar_position.get_orientation(); let orientation = info.bar_position.get_orientation();
@ -190,6 +191,21 @@ impl Module<Button> for MusicModule {
context.widget_rx.attach(None, move |mut event| { context.widget_rx.attach(None, move |mut event| {
if let Some(event) = event.take() { if let Some(event) = event.take() {
label.set_label(&event.display_string); label.set_label(&event.display_string);
match event.status.state {
PlayerState::Playing => {
icon_play.show();
icon_pause.hide();
}
PlayerState::Paused => {
icon_pause.show();
icon_play.hide();
}
PlayerState::Stopped => {
button.hide();
}
}
button.show(); button.show();
} else { } else {
button.hide(); button.hide();
@ -212,9 +228,9 @@ impl Module<Button> for MusicModule {
self, self,
tx: Sender<Self::ReceiveMessage>, tx: Sender<Self::ReceiveMessage>,
rx: glib::Receiver<Self::SendMessage>, rx: glib::Receiver<Self::SendMessage>,
_info: &ModuleInfo, info: &ModuleInfo,
) -> Option<gtk::Box> { ) -> Option<gtk::Box> {
let icon_theme = IconTheme::new(); let icon_theme = info.icon_theme;
let container = gtk::Box::builder() let container = gtk::Box::builder()
.orientation(Orientation::Horizontal) .orientation(Orientation::Horizontal)
@ -228,10 +244,12 @@ impl Module<Button> for MusicModule {
.name("album-art") .name("album-art")
.build(); .build();
let icons = self.icons;
let info_box = gtk::Box::new(Orientation::Vertical, 10); let info_box = gtk::Box::new(Orientation::Vertical, 10);
let title_label = IconLabel::new("\u{f886}", None); let title_label = IconLabel::new(&icons.track, None, icon_theme);
let album_label = IconLabel::new("\u{f524}", None); let album_label = IconLabel::new(&icons.album, None, icon_theme);
let artist_label = IconLabel::new("\u{fd01}", None); let artist_label = IconLabel::new(&icons.artist, None, icon_theme);
title_label.container.set_widget_name("title"); title_label.container.set_widget_name("title");
album_label.container.set_widget_name("album"); album_label.container.set_widget_name("album");
@ -242,12 +260,22 @@ impl Module<Button> for MusicModule {
info_box.add(&artist_label.container); info_box.add(&artist_label.container);
let controls_box = gtk::Box::builder().name("controls").build(); let controls_box = gtk::Box::builder().name("controls").build();
let btn_prev = Button::builder().label("\u{f9ad}").name("btn-prev").build();
let btn_play_pause = Button::builder().label("").name("btn-play-pause").build(); let btn_prev = new_icon_button(&icons.prev, icon_theme, 24);
let btn_next = Button::builder().label("\u{f9ac}").name("btn-next").build(); btn_prev.set_widget_name("btn-prev");
let btn_play = new_icon_button(&icons.play, icon_theme, 24);
btn_play.set_widget_name("btn-play");
let btn_pause = new_icon_button(&icons.pause, icon_theme, 24);
btn_pause.set_widget_name("btn-pause");
let btn_next = new_icon_button(&icons.next, icon_theme, 24);
btn_next.set_widget_name("btn-next");
controls_box.add(&btn_prev); controls_box.add(&btn_prev);
controls_box.add(&btn_play_pause); controls_box.add(&btn_play);
controls_box.add(&btn_pause);
controls_box.add(&btn_next); controls_box.add(&btn_next);
info_box.add(&controls_box); info_box.add(&controls_box);
@ -262,7 +290,7 @@ impl Module<Button> for MusicModule {
volume_slider.set_inverted(true); volume_slider.set_inverted(true);
volume_slider.set_widget_name("slider"); volume_slider.set_widget_name("slider");
let volume_icon = Label::new(Some(&self.icons.volume)); let volume_icon = new_icon_label(&icons.volume, icon_theme, 24);
volume_icon.style_context().add_class("icon"); volume_icon.style_context().add_class("icon");
volume_box.pack_start(&volume_slider, true, true, 0); volume_box.pack_start(&volume_slider, true, true, 0);
@ -277,13 +305,14 @@ impl Module<Button> for MusicModule {
try_send!(tx_prev, PlayerCommand::Previous); try_send!(tx_prev, PlayerCommand::Previous);
}); });
let tx_toggle = tx.clone(); let tx_play = tx.clone();
btn_play_pause.connect_clicked(move |button| { btn_play.connect_clicked(move |_| {
if button.style_context().has_class("playing") { try_send!(tx_play, PlayerCommand::Play);
try_send!(tx_toggle, PlayerCommand::Pause); });
} else {
try_send!(tx_toggle, PlayerCommand::Play); let tx_pause = tx.clone();
} btn_pause.connect_clicked(move |_| {
try_send!(tx_pause, PlayerCommand::Pause);
}); });
let tx_next = tx.clone(); let tx_next = tx.clone();
@ -300,6 +329,8 @@ impl Module<Button> for MusicModule {
container.show_all(); container.show_all();
{ {
let icon_theme = icon_theme.clone();
let mut prev_cover = None; let mut prev_cover = None;
rx.attach(None, move |update| { rx.attach(None, move |update| {
if let Some(update) = update { if let Some(update) = update {
@ -308,7 +339,7 @@ impl Module<Button> for MusicModule {
if prev_cover != new_cover { if prev_cover != new_cover {
prev_cover = new_cover.clone(); prev_cover = new_cover.clone();
let res = match new_cover let res = match new_cover
.map(|cover_path| ImageProvider::parse(cover_path, &icon_theme, 128)) .map(|cover_path| ImageProvider::parse(&cover_path, &icon_theme, 128))
{ {
Some(Ok(image)) => image.load_into_image(album_image.clone()), Some(Ok(image)) => image.load_into_image(album_image.clone()),
Some(Err(err)) => { Some(Err(err)) => {
@ -337,23 +368,23 @@ impl Module<Button> for MusicModule {
match update.status.state { match update.status.state {
PlayerState::Stopped => { PlayerState::Stopped => {
btn_play_pause.set_sensitive(false); btn_pause.hide();
btn_play.show();
btn_play.set_sensitive(false);
} }
PlayerState::Playing => { PlayerState::Playing => {
btn_play_pause.set_sensitive(true); btn_play.set_sensitive(false);
btn_play_pause.set_label(&self.icons.pause); btn_play.hide();
let style_context = btn_play_pause.style_context(); btn_pause.set_sensitive(true);
style_context.add_class("playing"); btn_pause.show();
style_context.remove_class("paused");
} }
PlayerState::Paused => { PlayerState::Paused => {
btn_play_pause.set_sensitive(true); btn_pause.set_sensitive(false);
btn_play_pause.set_label(&self.icons.play); btn_pause.hide();
let style_context = btn_play_pause.style_context(); btn_play.set_sensitive(true);
style_context.add_class("paused"); btn_play.show();
style_context.remove_class("playing");
} }
} }
@ -383,11 +414,10 @@ fn replace_tokens(
tokens: &Vec<String>, tokens: &Vec<String>,
song: &Track, song: &Track,
status: &Status, status: &Status,
icons: &Icons,
) -> String { ) -> String {
let mut compiled_string = format_string.to_string(); let mut compiled_string = format_string.to_string();
for token in tokens { for token in tokens {
let value = get_token_value(song, status, icons, token); let value = get_token_value(song, status, token);
compiled_string = compiled_string.replace(format!("{{{token}}}").as_str(), value.as_str()); compiled_string = compiled_string.replace(format!("{{{token}}}").as_str(), value.as_str());
} }
compiled_string compiled_string
@ -395,14 +425,8 @@ fn replace_tokens(
/// Converts a string format token value /// Converts a string format token value
/// into its respective value. /// into its respective value.
fn get_token_value(song: &Track, status: &Status, icons: &Icons, token: &str) -> String { fn get_token_value(song: &Track, status: &Status, token: &str) -> String {
match token { match token {
"icon" => match status.state {
PlayerState::Stopped => None,
PlayerState::Playing => Some(&icons.play),
PlayerState::Paused => Some(&icons.pause),
}
.map(std::string::ToString::to_string),
"title" => song.title.clone(), "title" => song.title.clone(),
"album" => song.album.clone(), "album" => song.album.clone(),
"artist" => song.artist.clone(), "artist" => song.artist.clone(),
@ -417,17 +441,17 @@ fn get_token_value(song: &Track, status: &Status, icons: &Icons, token: &str) ->
.unwrap_or_default() .unwrap_or_default()
} }
#[derive(Clone)] #[derive(Clone, Debug)]
struct IconLabel { struct IconLabel {
label: Label, label: Label,
container: gtk::Box, container: gtk::Box,
} }
impl IconLabel { impl IconLabel {
fn new(icon: &str, label: Option<&str>) -> Self { fn new(icon_input: &str, label: Option<&str>, icon_theme: &IconTheme) -> Self {
let container = gtk::Box::new(Orientation::Horizontal, 5); let container = gtk::Box::new(Orientation::Horizontal, 5);
let icon = Label::new(Some(icon)); let icon = new_icon_label(icon_input, icon_theme, 32);
let label = Label::new(label); let label = Label::new(label);
icon.style_context().add_class("icon"); icon.style_context().add_class("icon");