use std::collections::HashMap; use std::rc::Rc; use std::sync::{mpsc, Arc}; use dbus::arg::{RefArg, Variant}; use dbus::stdintf::org_freedesktop_dbus::PropertiesPropertiesChanged; use dbus::tree::{Access, Factory}; use dbus::{Path, SignalArgs}; use queue::{Queue, RepeatSetting}; use spotify::{PlayerEvent, Spotify}; fn get_playbackstatus(spotify: Arc) -> String { match spotify.get_current_status() { PlayerEvent::Playing => "Playing", PlayerEvent::Paused => "Paused", _ => "Stopped", } .to_string() } fn get_metadata(queue: Arc) -> HashMap>> { let mut hm: HashMap>> = HashMap::new(); let t = queue.get_current(); let track = t.as_ref(); // TODO hm.insert( "mpris:trackid".to_string(), Variant(Box::new( track .map(|t| format!("spotify:track:{}", t.id)) .unwrap_or_default(), )), ); hm.insert( "mpris:length".to_string(), Variant(Box::new(track.map(|t| t.duration * 1_000).unwrap_or(0))), ); hm.insert( "mpris:artUrl".to_string(), Variant(Box::new( track.map(|t| t.cover_url.clone()).unwrap_or_default(), )), ); hm.insert( "xesam:album".to_string(), Variant(Box::new(track.map(|t| t.album.clone()).unwrap_or_default())), ); hm.insert( "xesam:albumArtist".to_string(), Variant(Box::new( track.map(|t| t.album_artists.clone()).unwrap_or_default(), )), ); hm.insert( "xesam:artist".to_string(), Variant(Box::new( track.map(|t| t.artists.clone()).unwrap_or_default(), )), ); hm.insert( "xesam:discNumber".to_string(), Variant(Box::new(track.map(|t| t.disc_number).unwrap_or(0))), ); hm.insert( "xesam:title".to_string(), Variant(Box::new(track.map(|t| t.title.clone()).unwrap_or_default())), ); hm.insert( "xesam:trackNumber".to_string(), Variant(Box::new(track.map(|t| t.track_number).unwrap_or(0))), ); hm.insert( "xesam:url".to_string(), Variant(Box::new(track.map(|t| t.url.clone()).unwrap_or_default())), ); hm } fn run_dbus_server(spotify: Arc, queue: Arc, rx: mpsc::Receiver<()>) { let conn = Rc::new( dbus::Connection::get_private(dbus::BusType::Session).expect("Failed to connect to dbus"), ); conn.register_name( "org.mpris.MediaPlayer2.ncspot", dbus::NameFlag::ReplaceExisting as u32, ) .expect("Failed to register dbus player name"); let f = Factory::new_fn::<()>(); let property_canquit = f .property::("CanQuit", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(false); // TODO Ok(()) }); let property_canraise = f .property::("CanRaise", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(false); Ok(()) }); let property_cansetfullscreen = f .property::("CanSetFullscreen", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(false); Ok(()) }); let property_hastracklist = f .property::("HasTrackList", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(false); // TODO Ok(()) }); let property_identity = f .property::("Identity", ()) .access(Access::Read) .on_get(|iter, _| { iter.append("ncspot".to_string()); Ok(()) }); let property_urischemes = f .property::, _>("SupportedUriSchemes", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(vec!["spotify".to_string()]); Ok(()) }); let property_mimetypes = f .property::, _>("SupportedMimeTypes", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(Vec::new() as Vec); Ok(()) }); // https://specifications.freedesktop.org/mpris-spec/latest/Media_Player.html let interface = f .interface("org.mpris.MediaPlayer2", ()) .add_p(property_canquit) .add_p(property_canraise) .add_p(property_cansetfullscreen) .add_p(property_hastracklist) .add_p(property_identity) .add_p(property_urischemes) .add_p(property_mimetypes); let property_playbackstatus = { let spotify = spotify.clone(); f.property::("PlaybackStatus", ()) .access(Access::Read) .on_get(move |iter, _| { let status = get_playbackstatus(spotify.clone()); iter.append(status); Ok(()) }) }; let property_loopstatus = { let queue = queue.clone(); f.property::("LoopStatus", ()) .access(Access::Read) .on_get(move |iter, _| { iter.append( match queue.get_repeat() { RepeatSetting::None => "None", RepeatSetting::RepeatTrack => "Track", RepeatSetting::RepeatPlaylist => "Playlist", } .to_string(), ); Ok(()) }) }; let property_metadata = { let queue = queue.clone(); f.property::>>, _>("Metadata", ()) .access(Access::Read) .on_get(move |iter, _| { let hm = get_metadata(queue.clone()); iter.append(hm); Ok(()) }) }; let property_position = { let spotify = spotify.clone(); let progress = spotify.get_current_progress(); f.property::("Position", ()) .access(Access::Read) .on_get(move |iter, _| { iter.append(progress.as_secs() as i64 * 1_000_000); Ok(()) }) }; let property_volume = f .property::("Volume", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(1.0); Ok(()) }); let property_rate = f .property::("Rate", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(1.0); Ok(()) }); let property_minrate = f .property::("MinimumRate", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(1.0); Ok(()) }); let property_maxrate = f .property::("MaximumRate", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(1.0); Ok(()) }); let property_canplay = f .property::("CanPlay", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(true); Ok(()) }); let property_canpause = f .property::("CanPause", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(true); Ok(()) }); let property_canseek = f .property::("CanSeek", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(false); // TODO Ok(()) }); let property_cancontrol = f .property::("CanControl", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(true); Ok(()) }); let property_cangonext = f .property::("CanGoNext", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(true); Ok(()) }); let property_cangoprevious = f .property::("CanGoPrevious", ()) .access(Access::Read) .on_get(|iter, _| { iter.append(true); Ok(()) }); let property_shuffle = { let queue = queue.clone(); f.property::("Shuffle", ()) .access(Access::Read) .on_get(move |iter, _| { iter.append(queue.get_shuffle()); Ok(()) }) }; let method_playpause = { let spotify = spotify.clone(); f.method("PlayPause", (), move |m| { spotify.toggleplayback(); Ok(vec![m.msg.method_return()]) }) }; let method_play = { let spotify = spotify.clone(); f.method("Play", (), move |m| { spotify.play(); Ok(vec![m.msg.method_return()]) }) }; let method_pause = { let spotify = spotify.clone(); f.method("Pause", (), move |m| { spotify.pause(); Ok(vec![m.msg.method_return()]) }) }; let method_stop = { let spotify = spotify.clone(); f.method("Stop", (), move |m| { spotify.stop(); Ok(vec![m.msg.method_return()]) }) }; let method_next = { let queue = queue.clone(); f.method("Next", (), move |m| { queue.next(true); Ok(vec![m.msg.method_return()]) }) }; let method_previous = { let queue = queue.clone(); f.method("Previous", (), move |m| { queue.previous(); Ok(vec![m.msg.method_return()]) }) }; // TODO: Seek, SetPosition, OpenUri (?) // https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html let interface_player = f .interface("org.mpris.MediaPlayer2.Player", ()) .add_p(property_playbackstatus) .add_p(property_loopstatus) .add_p(property_metadata) .add_p(property_position) .add_p(property_volume) .add_p(property_rate) .add_p(property_minrate) .add_p(property_maxrate) .add_p(property_canplay) .add_p(property_canpause) .add_p(property_canseek) .add_p(property_cancontrol) .add_p(property_cangonext) .add_p(property_cangoprevious) .add_p(property_shuffle) .add_m(method_playpause) .add_m(method_play) .add_m(method_pause) .add_m(method_stop) .add_m(method_next) .add_m(method_previous); let tree = f.tree(()).add( f.object_path("/org/mpris/MediaPlayer2", ()) .introspectable() .add(interface) .add(interface_player), ); tree.set_registered(&conn, true) .expect("failed to register tree"); conn.add_handler(tree); loop { if let Some(m) = conn.incoming(200).next() { warn!("Unhandled dbus message: {:?}", m); } if rx.try_recv().is_ok() { let mut changed: PropertiesPropertiesChanged = Default::default(); changed.interface_name = "org.mpris.MediaPlayer2.Player".to_string(); changed.changed_properties.insert( "Metadata".to_string(), Variant(Box::new(get_metadata(queue.clone()))), ); changed.changed_properties.insert( "PlaybackStatus".to_string(), Variant(Box::new(get_playbackstatus(spotify.clone()))), ); conn.send( changed.to_emit_message(&Path::new("/org/mpris/MediaPlayer2".to_string()).unwrap()), ) .unwrap(); } } } #[derive(Clone)] pub struct MprisManager { tx: mpsc::Sender<()>, } impl MprisManager { pub fn new(spotify: Arc, queue: Arc) -> Self { let (tx, rx) = mpsc::channel::<()>(); std::thread::spawn(move || { run_dbus_server(spotify, queue, rx); }); MprisManager { tx } } pub fn update(&self) { self.tx.send(()).unwrap(); } }