use anyhow::{Context, Result}; use mpris::{DBusError, Player, PlayerFinder}; use std::borrow::Cow; const VALUE_INDENTATION: usize = 25; trait CustomDisplay { fn string_for_display(&self) -> Cow<'_, str>; } fn main() { match print_capabilities_for_all_players() { Ok(_) => {} Err(error) => { println!("Error: {}", error); for (i, cause) in error.chain().skip(1).enumerate() { print!("{}", " ".repeat(i + 1)); println!("Caused by: {}", cause); } std::process::exit(1); } } } fn print_capabilities_for_all_players() -> Result<()> { for player in PlayerFinder::new() .context("Failed to connect to D-Bus")? .find_all() .context("Could not fetch list of players")? { print_capabilities_for_player(player)?; println!(); } Ok(()) } fn print_capabilities_for_player(player: Player) -> Result<()> { println!( ">> Player: {} ({})", player.identity(), player.unique_name() ); println!(); println!("\t─── MediaPlayer2 ───"); print_value("CanQuit", player.can_quit()); print_value("CanRaise", player.can_raise()); print_value("CanSetFullscreen", player.can_set_fullscreen()); print_value("HasTrackList", player.get_has_track_list()); print_value("SupportedMimeTypes", player.get_supported_mime_types()); print_value("SupportedUriSchemes", player.get_supported_uri_schemes()); println!(); println!("\t─── MediaPlayer2.Player ───"); print_value("CanControl", player.can_control()); print_value("CanGoNext", player.can_go_next()); print_value("CanGoPrevious", player.can_go_previous()); print_value("CanLoop", player.can_loop()); print_value("CanPause", player.can_pause()); print_value("CanPlay", player.can_play()); print_value("CanSeek", player.can_seek()); print_value("CanSetPaybackRate", player.can_set_playback_rate()); print_value("CanShuffle", player.can_shuffle()); print_value("CanStop", player.can_stop()); print_value("HasPlaybackRate", player.has_playback_rate()); print_value("HasPosition", player.has_position()); print_value("HasVolume", player.has_volume()); print_value("Rate", player.get_playback_rate()); print_value("MaximumRate", player.get_maximum_playback_rate()); print_value("MinimumRate", player.get_minimum_playback_rate()); println!(); println!("\t─── MediaPlayer2.TrackList ───"); if player.supports_track_lists() { print_value("CanEditTracks", player.can_edit_tracks()); } else { println!("\tPlayer does not support TrackList interface!\n\tNote how they fail.\n"); print_value("CanEditTracks", player.can_edit_tracks()); println!( "\n\tYou can used the \"Checked\" variants to hide\n\terror handling for these cases:" ); print_value("CheckedCanEditTracks", player.checked_can_edit_tracks()); } Ok(()) } fn print_value(name: &str, value: T) { println!( "\t{1:>0$}:\t{2}", VALUE_INDENTATION, name, value.string_for_display() ); } impl CustomDisplay for bool { fn string_for_display(&self) -> Cow<'_, str> { match self { true => "✔ Yes".into(), false => "✖ No".into(), } } } impl CustomDisplay for f64 { fn string_for_display(&self) -> Cow<'_, str> { format!("{:.3}", self).into() } } impl CustomDisplay for String { fn string_for_display(&self) -> Cow<'_, str> { self.into() } } impl CustomDisplay for DBusError { fn string_for_display(&self) -> Cow<'_, str> { format!("Error: {}", self).into() } } impl CustomDisplay for Vec where T: CustomDisplay, { fn string_for_display(&self) -> Cow<'_, str> { let mut buf = String::new(); for val in self { if buf.is_empty() { buf.push_str(&val.string_for_display()); } else { buf.push_str(&format!( "\n\t{1:>0$} \t{2}", VALUE_INDENTATION, "", val.string_for_display() )); } } buf.into() } } impl CustomDisplay for Result where T: CustomDisplay, E: CustomDisplay, { fn string_for_display(&self) -> Cow<'_, str> { match self { Ok(val) => val.string_for_display(), Err(err) => err.string_for_display(), } } }