From e941c19f7835249aaee596b80079ee6e161a1615 Mon Sep 17 00:00:00 2001 From: Dave Patrick Caberto Date: Wed, 20 Sep 2023 17:38:56 +0800 Subject: [PATCH] allow emitting signals from interfaces --- src/lib.rs | 69 +++--- src/local_server.rs | 208 ++++++++-------- src/player.rs | 36 +-- src/server.rs | 575 ++++++++++++++++++++++++++++---------------- 4 files changed, 531 insertions(+), 357 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ace93c3..8eca369 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,7 @@ pub use crate::{ playlist::{MaybePlaylist, Playlist}, playlist_ordering::PlaylistOrdering, property::{PlaylistsProperty, Property, TrackListProperty}, - server::Server, + server::{Server, ServerProxy}, signal::{PlaylistsSignal, Signal, TrackListSignal}, time::Time, track_id::TrackId, @@ -89,17 +89,23 @@ pub use crate::{ macro_rules! define_iface { (#[$attr:meta], - $root_iface_ident:ident$(: $bound:tt $(+ $other_bounds:tt)* )? extra_docs $extra_root_docs:literal, - $player_iface_ident:ident extra_docs $extra_player_docs:literal, + $player_iface_ident:ident$(: $bound:tt $(+ $other_bounds:tt)* )? extra_docs $extra_player_docs:literal, $track_list_iface_ident:ident extra_docs $extra_track_list_docs:literal, $playlists_iface_ident:ident extra_docs $extra_playlists_docs:literal) => { - #[doc = $extra_root_docs] + #[doc = $extra_player_docs] #[doc = ""] - /// Used to implement [org.mpris.MediaPlayer2] interface. + /// Used to implement [org.mpris.MediaPlayer2] and [org.mpris.MediaPlayer2.Player] + /// interfaces. + /// + /// [org.mpris.MediaPlayer2.Player] implements the methods for querying and + /// providing basic control over what is currently playing. /// /// [org.mpris.MediaPlayer2]: https://specifications.freedesktop.org/mpris-spec/2.2/Media_Player.html + /// [org.mpris.MediaPlayer2.Player]: https://specifications.freedesktop.org/mpris-spec/2.2/Player_Interface.html #[$attr] - pub trait $root_iface_ident$(: $bound $(+ $other_bounds)* )? { + #[doc(alias = "org.mpris.MediaPlayer2")] + #[doc(alias = "org.mpris.MediaPlayer2.Player")] + pub trait $player_iface_ident$(: $bound $(+ $other_bounds)* )? { /// Brings the media player's user interface to the front using any /// appropriate mechanism available. /// @@ -110,7 +116,7 @@ macro_rules! define_iface { /// /// [`CanRaise`]: Self::can_raise #[doc(alias = "Raise")] - async fn raise(&self) -> fdo::Result<()>; + async fn raise(&self, _proxy: &ServerProxy<'_, Self>) -> fdo::Result<()>; /// Causes the media player to stop running. /// @@ -127,7 +133,7 @@ macro_rules! define_iface { /// /// [`CanQuit`]: Self::can_quit #[doc(alias = "Quit")] - async fn quit(&self) -> fdo::Result<()>; + async fn quit(&self, _proxy: &ServerProxy<'_, Self>) -> fdo::Result<()>; /// Whether the player may be asked to quit. /// @@ -327,18 +333,7 @@ macro_rules! define_iface { /// [`Playlists interface`]: PlaylistsInterface #[doc(alias = "SupportedMimeTypes")] async fn supported_mime_types(&self) -> fdo::Result>; - } - #[doc = $extra_player_docs] - #[doc = ""] - /// Used to implement [org.mpris.MediaPlayer2.Player] interface, which - /// implements the methods for querying and providing basic control over what is - /// currently playing. - /// - /// [org.mpris.MediaPlayer2.Player]: https://specifications.freedesktop.org/mpris-spec/2.2/Player_Interface.html - #[$attr] - #[doc(alias = "org.mpris.MediaPlayer2.Player")] - pub trait $player_iface_ident: $root_iface_ident { /// Skips to the next track in the tracklist. /// /// If there is no next track (and endless playback and track repeat are @@ -351,7 +346,7 @@ macro_rules! define_iface { /// /// [`CanGoNext`]: Self::can_go_next #[doc(alias = "Next")] - async fn next(&self) -> fdo::Result<()>; + async fn next(&self, _proxy: &ServerProxy<'_, Self>) -> fdo::Result<()>; /// Skips to the previous track in the tracklist. /// @@ -365,7 +360,7 @@ macro_rules! define_iface { /// /// [`CanGoPrevious`]: Self::can_go_previous #[doc(alias = "Previous")] - async fn previous(&self) -> fdo::Result<()>; + async fn previous(&self, _proxy: &ServerProxy<'_, Self>) -> fdo::Result<()>; /// Pauses playback. /// @@ -380,7 +375,7 @@ macro_rules! define_iface { /// [`Play`]: Self::play /// [`CanPause`]: Self::can_pause #[doc(alias = "Pause")] - async fn pause(&self) -> fdo::Result<()>; + async fn pause(&self, _proxy: &ServerProxy<'_, Self>) -> fdo::Result<()>; /// Pauses playback. /// @@ -393,7 +388,7 @@ macro_rules! define_iface { /// /// [`CanPause`]: Self::can_pause #[doc(alias = "PlayPause")] - async fn play_pause(&self) -> fdo::Result<()>; + async fn play_pause(&self, _proxy: &ServerProxy<'_, Self>) -> fdo::Result<()>; /// Stops playback. /// @@ -407,7 +402,7 @@ macro_rules! define_iface { /// /// [`CanControl`]: Self::can_control #[doc(alias = "Stop")] - async fn stop(&self) -> fdo::Result<()>; + async fn stop(&self, _proxy: &ServerProxy<'_, Self>) -> fdo::Result<()>; /// Starts or resumes playback. /// @@ -422,7 +417,7 @@ macro_rules! define_iface { /// /// [`CanPlay`]: Self::can_play #[doc(alias = "Play")] - async fn play(&self) -> fdo::Result<()>; + async fn play(&self, _proxy: &ServerProxy<'_, Self>) -> fdo::Result<()>; /// Seeks forward in the current track by the specified offset in time. /// @@ -440,7 +435,7 @@ macro_rules! define_iface { /// /// [`CanSeek`]: Self::can_seek #[doc(alias = "Seek")] - async fn seek(&self, offset: Time) -> fdo::Result<()>; + async fn seek(&self, _proxy: &ServerProxy<'_, Self>, offset: Time) -> fdo::Result<()>; /// Sets the current track position. /// @@ -472,7 +467,7 @@ macro_rules! define_iface { /// [`CanSeek`]: Self::can_seek /// [`Position`]: Self::position #[doc(alias = "SetPosition")] - async fn set_position(&self, track_id: TrackId, position: Time) -> fdo::Result<()>; + async fn set_position(&self,_proxy: &ServerProxy<'_, Self>, track_id: TrackId, position: Time) -> fdo::Result<()>; /// Opens the `uri` given as an argument /// @@ -507,7 +502,7 @@ macro_rules! define_iface { /// [`TrackAdded`]: TrackListSignal::TrackAdded /// [`TrackListReplaced`]: TrackListSignal::TrackListReplaced #[doc(alias = "OpenUri")] - async fn open_uri(&self, uri: String) -> fdo::Result<()>; + async fn open_uri(&self, _proxy: &ServerProxy<'_, Self>, uri: String) -> fdo::Result<()>; /// The current playback status. /// @@ -943,7 +938,7 @@ macro_rules! define_iface { /// [`mpris:trackid`]: Metadata::set_trackid #[doc(alias = "GetTracksMetadata")] async fn get_tracks_metadata( - &self, + &self, _proxy: &ServerProxy<'_, Self>, track_ids: Vec, ) -> fdo::Result>; @@ -978,7 +973,7 @@ macro_rules! define_iface { /// [`TrackListReplaced`]: TrackListSignal::TrackListReplaced #[doc(alias = "AddTrack")] async fn add_track( - &self, + &self, _proxy: &ServerProxy<'_, Self>, uri: Uri, after_track: TrackId, set_as_current: bool, @@ -1005,7 +1000,7 @@ macro_rules! define_iface { /// [`TrackRemoved`]: TrackListSignal::TrackRemoved /// [`TrackListReplaced`]: TrackListSignal::TrackListReplaced #[doc(alias = "RemoveTrack")] - async fn remove_track(&self, track_id: TrackId) -> fdo::Result<()>; + async fn remove_track(&self, _proxy: &ServerProxy<'_, Self>, track_id: TrackId) -> fdo::Result<()>; /// Skip to the specified TrackId. /// @@ -1024,7 +1019,7 @@ macro_rules! define_iface { /// /// [`TrackListReplaced`]: TrackListSignal::TrackListReplaced #[doc(alias = "GoTo")] - async fn go_to(&self, track_id: TrackId) -> fdo::Result<()>; + async fn go_to(&self, _proxy: &ServerProxy<'_, Self>, track_id: TrackId) -> fdo::Result<()>; /// An array which contains the identifier of each track in the tracklist, /// in order. @@ -1094,7 +1089,7 @@ macro_rules! define_iface { /// operating in a "jukebox" mode, it may just append the playlist to the /// list of upcoming tracks, and skip to the first track in the playlist. #[doc(alias = "ActivatePlaylist")] - async fn activate_playlist(&self, playlist_id: PlaylistId) -> fdo::Result<()>; + async fn activate_playlist(&self, _proxy: &ServerProxy<'_, Self>, playlist_id: PlaylistId) -> fdo::Result<()>; /// Gets a set of playlists. /// @@ -1111,7 +1106,7 @@ macro_rules! define_iface { /// * `playlists` - A list of (at most `max_count`) playlists. #[doc(alias = "GetPlaylists")] async fn get_playlists( - &self, + &self, _proxy: &ServerProxy<'_, Self>, index: u32, max_count: u32, order: PlaylistOrdering, @@ -1171,16 +1166,14 @@ macro_rules! define_iface { define_iface!( #[async_trait], - RootInterface: Send + Sync extra_docs "", - PlayerInterface extra_docs "", + PlayerInterface: Sized + Send + Sync extra_docs "", TrackListInterface extra_docs "", PlaylistsInterface extra_docs "" ); define_iface!( #[async_trait(?Send)], - LocalRootInterface extra_docs "Local version of [`RootInterface`] to be used with [`LocalServer`].", - LocalPlayerInterface extra_docs "Local version of [`PlayerInterface`] to be used with [`LocalServer`].", + LocalPlayerInterface: Sized extra_docs "Local version of [`PlayerInterface`] to be used with [`LocalServer`].", LocalTrackListInterface extra_docs "Local version of [`TrackListInterface`] to be used with [`LocalServer`].", LocalPlaylistsInterface extra_docs "Local version of [`PlaylistsInterface`] to be used with [`LocalServer`]." ); diff --git a/src/local_server.rs b/src/local_server.rs index f420542..b876ece 100644 --- a/src/local_server.rs +++ b/src/local_server.rs @@ -17,17 +17,17 @@ use zbus::{fdo, Connection, Result}; use crate::{ LocalPlayerInterface, LocalPlaylistsInterface, LocalTrackListInterface, LoopStatus, MaybePlaylist, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Playlist, PlaylistId, - PlaylistOrdering, PlaylistsInterface, PlaylistsProperty, PlaylistsSignal, Property, - RootInterface, Server, Signal, Time, TrackId, TrackListInterface, TrackListProperty, - TrackListSignal, Uri, Volume, + PlaylistOrdering, PlaylistsInterface, PlaylistsProperty, PlaylistsSignal, Property, Server, + ServerProxy, Signal, Time, TrackId, TrackListInterface, TrackListProperty, TrackListSignal, + Uri, Volume, }; -enum RootAction { - // Methods +enum PlayerAction { + // Methods Raise(oneshot::Sender>), Quit(oneshot::Sender>), - // `org.mpris.MediaPlayer2` Properties + // Properties CanQuit(oneshot::Sender>), Fullscreen(oneshot::Sender>), SetFullscreen(bool, oneshot::Sender>), @@ -38,9 +38,7 @@ enum RootAction { DesktopEntry(oneshot::Sender>), SupportedUriSchemes(oneshot::Sender>>), SupportedMimeTypes(oneshot::Sender>>), -} -enum PlayerAction { // Methods Next(oneshot::Sender>), Previous(oneshot::Sender>), @@ -104,7 +102,6 @@ enum TrackListAction { } enum Action { - Root(RootAction), Player(PlayerAction), TrackList(TrackListAction), Playlists(PlaylistsAction), @@ -122,10 +119,6 @@ struct InnerImp { } impl InnerImp { - fn send_root(&self, action: RootAction) { - self.tx.unbounded_send(Action::Root(action)).unwrap(); - } - fn send_player(&self, action: PlayerAction) { self.tx.unbounded_send(Action::Player(action)).unwrap(); } @@ -140,131 +133,133 @@ impl InnerImp { } #[async_trait] -impl RootInterface for InnerImp { - async fn raise(&self) -> fdo::Result<()> { +impl PlayerInterface for InnerImp { + async fn raise(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let (tx, rx) = oneshot::channel(); - self.send_root(RootAction::Raise(tx)); + self.send_player(PlayerAction::Raise(tx)); rx.await.unwrap() } - async fn quit(&self) -> fdo::Result<()> { + async fn quit(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let (tx, rx) = oneshot::channel(); - self.send_root(RootAction::Quit(tx)); + self.send_player(PlayerAction::Quit(tx)); rx.await.unwrap() } async fn can_quit(&self) -> fdo::Result { let (tx, rx) = oneshot::channel(); - self.send_root(RootAction::CanQuit(tx)); + self.send_player(PlayerAction::CanQuit(tx)); rx.await.unwrap() } async fn fullscreen(&self) -> fdo::Result { let (tx, rx) = oneshot::channel(); - self.send_root(RootAction::Fullscreen(tx)); + self.send_player(PlayerAction::Fullscreen(tx)); rx.await.unwrap() } async fn set_fullscreen(&self, fullscreen: bool) -> Result<()> { let (tx, rx) = oneshot::channel(); - self.send_root(RootAction::SetFullscreen(fullscreen, tx)); + self.send_player(PlayerAction::SetFullscreen(fullscreen, tx)); rx.await.unwrap() } async fn can_set_fullscreen(&self) -> fdo::Result { let (tx, rx) = oneshot::channel(); - self.send_root(RootAction::CanSetFullScreen(tx)); + self.send_player(PlayerAction::CanSetFullScreen(tx)); rx.await.unwrap() } async fn can_raise(&self) -> fdo::Result { let (tx, rx) = oneshot::channel(); - self.send_root(RootAction::CanRaise(tx)); + self.send_player(PlayerAction::CanRaise(tx)); rx.await.unwrap() } async fn has_track_list(&self) -> fdo::Result { let (tx, rx) = oneshot::channel(); - self.send_root(RootAction::HasTrackList(tx)); + self.send_player(PlayerAction::HasTrackList(tx)); rx.await.unwrap() } async fn identity(&self) -> fdo::Result { let (tx, rx) = oneshot::channel(); - self.send_root(RootAction::Identity(tx)); + self.send_player(PlayerAction::Identity(tx)); rx.await.unwrap() } async fn desktop_entry(&self) -> fdo::Result { let (tx, rx) = oneshot::channel(); - self.send_root(RootAction::DesktopEntry(tx)); + self.send_player(PlayerAction::DesktopEntry(tx)); rx.await.unwrap() } async fn supported_uri_schemes(&self) -> fdo::Result> { let (tx, rx) = oneshot::channel(); - self.send_root(RootAction::SupportedUriSchemes(tx)); + self.send_player(PlayerAction::SupportedUriSchemes(tx)); rx.await.unwrap() } async fn supported_mime_types(&self) -> fdo::Result> { let (tx, rx) = oneshot::channel(); - self.send_root(RootAction::SupportedMimeTypes(tx)); + self.send_player(PlayerAction::SupportedMimeTypes(tx)); rx.await.unwrap() } -} -#[async_trait] -impl PlayerInterface for InnerImp { - async fn next(&self) -> fdo::Result<()> { + async fn next(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let (tx, rx) = oneshot::channel(); self.send_player(PlayerAction::Next(tx)); rx.await.unwrap() } - async fn previous(&self) -> fdo::Result<()> { + async fn previous(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let (tx, rx) = oneshot::channel(); self.send_player(PlayerAction::Previous(tx)); rx.await.unwrap() } - async fn pause(&self) -> fdo::Result<()> { + async fn pause(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let (tx, rx) = oneshot::channel(); self.send_player(PlayerAction::Pause(tx)); rx.await.unwrap() } - async fn play_pause(&self) -> fdo::Result<()> { + async fn play_pause(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let (tx, rx) = oneshot::channel(); self.send_player(PlayerAction::PlayPause(tx)); rx.await.unwrap() } - async fn stop(&self) -> fdo::Result<()> { + async fn stop(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let (tx, rx) = oneshot::channel(); self.send_player(PlayerAction::Stop(tx)); rx.await.unwrap() } - async fn play(&self) -> fdo::Result<()> { + async fn play(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let (tx, rx) = oneshot::channel(); self.send_player(PlayerAction::Play(tx)); rx.await.unwrap() } - async fn seek(&self, offset: Time) -> fdo::Result<()> { + async fn seek(&self, _: &ServerProxy<'_, Self>, offset: Time) -> fdo::Result<()> { let (tx, rx) = oneshot::channel(); self.send_player(PlayerAction::Seek(offset, tx)); rx.await.unwrap() } - async fn set_position(&self, track_id: TrackId, position: Time) -> fdo::Result<()> { + async fn set_position( + &self, + _: &ServerProxy<'_, Self>, + track_id: TrackId, + position: Time, + ) -> fdo::Result<()> { let (tx, rx) = oneshot::channel(); self.send_player(PlayerAction::SetPosition(track_id, position, tx)); rx.await.unwrap() } - async fn open_uri(&self, uri: String) -> fdo::Result<()> { + async fn open_uri(&self, _: &ServerProxy<'_, Self>, uri: String) -> fdo::Result<()> { let (tx, rx) = oneshot::channel(); self.send_player(PlayerAction::OpenUri(uri, tx)); rx.await.unwrap() @@ -390,7 +385,11 @@ impl TrackListInterface for InnerImp where T: LocalTrackListInterface, { - async fn get_tracks_metadata(&self, track_ids: Vec) -> fdo::Result> { + async fn get_tracks_metadata( + &self, + _: &ServerProxy<'_, Self>, + track_ids: Vec, + ) -> fdo::Result> { let (tx, rx) = oneshot::channel(); self.send_track_list(TrackListAction::GetTracksMetadata(track_ids, tx)); rx.await.unwrap() @@ -398,6 +397,7 @@ where async fn add_track( &self, + _: &ServerProxy<'_, Self>, uri: Uri, after_track: TrackId, set_as_current: bool, @@ -412,13 +412,13 @@ where rx.await.unwrap() } - async fn remove_track(&self, track_id: TrackId) -> fdo::Result<()> { + async fn remove_track(&self, _: &ServerProxy<'_, Self>, track_id: TrackId) -> fdo::Result<()> { let (tx, rx) = oneshot::channel(); self.send_track_list(TrackListAction::RemoveTrack(track_id, tx)); rx.await.unwrap() } - async fn go_to(&self, track_id: TrackId) -> fdo::Result<()> { + async fn go_to(&self, _: &ServerProxy<'_, Self>, track_id: TrackId) -> fdo::Result<()> { let (tx, rx) = oneshot::channel(); self.send_track_list(TrackListAction::GoTo(track_id, tx)); rx.await.unwrap() @@ -442,7 +442,11 @@ impl PlaylistsInterface for InnerImp where T: LocalPlaylistsInterface, { - async fn activate_playlist(&self, playlist_id: PlaylistId) -> fdo::Result<()> { + async fn activate_playlist( + &self, + _: &ServerProxy<'_, Self>, + playlist_id: PlaylistId, + ) -> fdo::Result<()> { let (tx, rx) = oneshot::channel(); self.send_playlists(PlaylistsAction::ActivatePlaylist(playlist_id, tx)); rx.await.unwrap() @@ -450,6 +454,7 @@ where async fn get_playlists( &self, + _: &ServerProxy<'_, Self>, index: u32, max_count: u32, order: PlaylistOrdering, @@ -570,11 +575,12 @@ where bus_name_suffix, imp, Server::new, - |mut rx, imp| async move { + |mut rx, connection, imp| async move { while let Some(action) = rx.next().await { match action { - Action::Root(action) => Self::handle_root_action(&imp, action).await, - Action::Player(action) => Self::handle_player_action(&imp, action).await, + Action::Player(action) => { + Self::handle_player_action(&connection, &imp, action).await + } Action::TrackList(_) | Action::Playlists(_) => unreachable!(), } } @@ -630,98 +636,94 @@ where self.inner.properties_changed(properties).await } - async fn handle_root_action(imp: &T, action: RootAction) { + async fn handle_player_action(connection: &Connection, imp: &T, action: PlayerAction) { + let proxy = ServerProxy::new(connection, imp); match action { // Methods - RootAction::Raise(sender) => { - let ret = imp.raise().await; + PlayerAction::Raise(sender) => { + let ret = imp.raise(&proxy).await; sender.send(ret).unwrap(); } - RootAction::Quit(sender) => { - let ret = imp.quit().await; + PlayerAction::Quit(sender) => { + let ret = imp.quit(&proxy).await; sender.send(ret).unwrap(); } // Properties - RootAction::CanQuit(sender) => { + PlayerAction::CanQuit(sender) => { let ret = imp.can_quit().await; sender.send(ret).unwrap(); } - RootAction::Fullscreen(sender) => { + PlayerAction::Fullscreen(sender) => { let ret = imp.fullscreen().await; sender.send(ret).unwrap(); } - RootAction::SetFullscreen(fullscreen, sender) => { + PlayerAction::SetFullscreen(fullscreen, sender) => { let ret = imp.set_fullscreen(fullscreen).await; sender.send(ret).unwrap(); } - RootAction::CanSetFullScreen(sender) => { + PlayerAction::CanSetFullScreen(sender) => { let ret = imp.can_set_fullscreen().await; sender.send(ret).unwrap(); } - RootAction::CanRaise(sender) => { + PlayerAction::CanRaise(sender) => { let ret = imp.can_raise().await; sender.send(ret).unwrap(); } - RootAction::HasTrackList(sender) => { + PlayerAction::HasTrackList(sender) => { let ret = imp.has_track_list().await; sender.send(ret).unwrap(); } - RootAction::Identity(sender) => { + PlayerAction::Identity(sender) => { let ret = imp.identity().await; sender.send(ret).unwrap(); } - RootAction::DesktopEntry(sender) => { + PlayerAction::DesktopEntry(sender) => { let ret = imp.desktop_entry().await; sender.send(ret).unwrap(); } - RootAction::SupportedUriSchemes(sender) => { + PlayerAction::SupportedUriSchemes(sender) => { let ret = imp.supported_uri_schemes().await; sender.send(ret).unwrap(); } - RootAction::SupportedMimeTypes(sender) => { + PlayerAction::SupportedMimeTypes(sender) => { let ret = imp.supported_mime_types().await; sender.send(ret).unwrap(); } - } - } - - async fn handle_player_action(imp: &T, action: PlayerAction) { - match action { // Methods PlayerAction::Next(sender) => { - let ret = imp.next().await; + let ret = imp.next(&proxy).await; sender.send(ret).unwrap(); } PlayerAction::Previous(sender) => { - let ret = imp.previous().await; + let ret = imp.previous(&proxy).await; sender.send(ret).unwrap(); } PlayerAction::Pause(sender) => { - let ret = imp.pause().await; + let ret = imp.pause(&proxy).await; sender.send(ret).unwrap(); } PlayerAction::PlayPause(sender) => { - let ret = imp.play_pause().await; + let ret = imp.play_pause(&proxy).await; sender.send(ret).unwrap(); } PlayerAction::Stop(sender) => { - let ret = imp.stop().await; + let ret = imp.stop(&proxy).await; sender.send(ret).unwrap(); } PlayerAction::Play(sender) => { - let ret = imp.play().await; + let ret = imp.play(&proxy).await; sender.send(ret).unwrap(); } PlayerAction::Seek(offset, sender) => { - let ret = imp.seek(offset).await; + let ret = imp.seek(&proxy, offset).await; sender.send(ret).unwrap(); } PlayerAction::SetPosition(track_id, position, sender) => { - let ret = imp.set_position(track_id, position).await; + let ret = imp.set_position(&proxy, track_id, position).await; sender.send(ret).unwrap(); } PlayerAction::OpenUri(uri, sender) => { - let ret = imp.open_uri(uri).await; + let ret = imp.open_uri(&proxy, uri).await; sender.send(ret).unwrap(); } // Properties @@ -808,7 +810,7 @@ where bus_name_suffix: &str, imp: T, server_func: impl FnOnce(&str, InnerImp) -> Server>, - runner_func: impl FnOnce(mpsc::UnboundedReceiver, Rc) -> R + 'static, + runner_func: impl FnOnce(mpsc::UnboundedReceiver, Connection, Rc) -> R + 'static, ) -> Self where R: Future + 'static, @@ -829,7 +831,8 @@ where let imp_clone = Rc::clone(&imp); let runner = Box::pin(async move { inner_clone.init().await?; - runner_func(rx, imp_clone).await; + let connection = inner_clone.connection().await?; + runner_func(rx, connection.clone(), imp_clone).await; Ok(()) }); @@ -857,13 +860,14 @@ where bus_name_suffix, imp, Server::new_with_track_list, - |mut rx, imp| async move { + |mut rx, connection, imp| async move { while let Some(action) = rx.next().await { match action { - Action::Root(action) => Self::handle_root_action(&imp, action).await, - Action::Player(action) => Self::handle_player_action(&imp, action).await, + Action::Player(action) => { + Self::handle_player_action(&connection, &imp, action).await + } Action::TrackList(action) => { - Self::handle_track_list_action(&imp, action).await + Self::handle_track_list_action(&connection, &imp, action).await } Action::Playlists(_) => unreachable!(), } @@ -890,23 +894,26 @@ where self.inner.track_list_properties_changed(properties).await } - async fn handle_track_list_action(imp: &T, action: TrackListAction) { + async fn handle_track_list_action(connection: &Connection, imp: &T, action: TrackListAction) { + let proxy = ServerProxy::new(connection, imp); match action { // Methods TrackListAction::GetTracksMetadata(track_ids, sender) => { - let ret = imp.get_tracks_metadata(track_ids).await; + let ret = imp.get_tracks_metadata(&proxy, track_ids).await; sender.send(ret).unwrap(); } TrackListAction::AddTrack(uri, after_track, set_as_current, sender) => { - let ret = imp.add_track(uri, after_track, set_as_current).await; + let ret = imp + .add_track(&proxy, uri, after_track, set_as_current) + .await; sender.send(ret).unwrap(); } TrackListAction::RemoveTrack(track_id, sender) => { - let ret = imp.remove_track(track_id).await; + let ret = imp.remove_track(&proxy, track_id).await; sender.send(ret).unwrap(); } TrackListAction::GoTo(track_id, sender) => { - let ret = imp.go_to(track_id).await; + let ret = imp.go_to(&proxy, track_id).await; sender.send(ret).unwrap(); } // Properties @@ -938,13 +945,14 @@ where bus_name_suffix, imp, Server::new_with_playlists, - |mut rx, imp| async move { + |mut rx, connection, imp| async move { while let Some(action) = rx.next().await { match action { - Action::Root(action) => Self::handle_root_action(&imp, action).await, - Action::Player(action) => Self::handle_player_action(&imp, action).await, + Action::Player(action) => { + Self::handle_player_action(&connection, &imp, action).await + } Action::Playlists(action) => { - Self::handle_playlists_actions(&imp, action).await + Self::handle_playlists_actions(&connection, &imp, action).await } Action::TrackList(_) => unreachable!(), } @@ -971,15 +979,16 @@ where self.inner.playlists_properties_changed(properties).await } - async fn handle_playlists_actions(imp: &T, action: PlaylistsAction) { + async fn handle_playlists_actions(connection: &Connection, imp: &T, action: PlaylistsAction) { + let proxy = ServerProxy::new(connection, imp); match action { PlaylistsAction::ActivatePlaylist(playlist_id, sender) => { - let ret = imp.activate_playlist(playlist_id).await; + let ret = imp.activate_playlist(&proxy, playlist_id).await; sender.send(ret).unwrap(); } PlaylistsAction::GetPlaylists(index, max_count, order, reverse_order, sender) => { let ret = imp - .get_playlists(index, max_count, order, reverse_order) + .get_playlists(&proxy, index, max_count, order, reverse_order) .await; sender.send(ret).unwrap(); } @@ -1016,16 +1025,17 @@ where bus_name_suffix, imp, Server::new_with_all, - |mut rx, imp| async move { + |mut rx, connection, imp| async move { while let Some(action) = rx.next().await { match action { - Action::Root(action) => Self::handle_root_action(&imp, action).await, - Action::Player(action) => Self::handle_player_action(&imp, action).await, + Action::Player(action) => { + Self::handle_player_action(&connection, &imp, action).await + } Action::Playlists(action) => { - Self::handle_playlists_actions(&imp, action).await + Self::handle_playlists_actions(&connection, &imp, action).await } Action::TrackList(action) => { - Self::handle_track_list_action(&imp, action).await + Self::handle_track_list_action(&connection, &imp, action).await } } } diff --git a/src/player.rs b/src/player.rs index 2af1cd5..5c82a9f 100644 --- a/src/player.rs +++ b/src/player.rs @@ -7,8 +7,8 @@ use async_trait::async_trait; use zbus::{fdo, Result}; use crate::{ - LocalPlayerInterface, LocalRootInterface, LocalServer, LocalServerRunTask, LoopStatus, - Metadata, PlaybackRate, PlaybackStatus, Property, Signal, Time, TrackId, Volume, + LocalPlayerInterface, LocalServer, LocalServerRunTask, LoopStatus, Metadata, PlaybackRate, + PlaybackStatus, Property, ServerProxy, Signal, Time, TrackId, Volume, }; /// Ready-to-use mutable *service*-side object that internally implements @@ -82,8 +82,8 @@ impl State { } #[async_trait(?Send)] -impl LocalRootInterface for State { - async fn raise(&self) -> fdo::Result<()> { +impl LocalPlayerInterface for State { + async fn raise(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let player = self.player(); for cb in self.raise_cbs.borrow().iter() { cb(&player); @@ -91,7 +91,7 @@ impl LocalRootInterface for State { Ok(()) } - async fn quit(&self) -> fdo::Result<()> { + async fn quit(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let player = self.player(); for cb in self.quit_cbs.borrow().iter() { cb(&player); @@ -142,11 +142,8 @@ impl LocalRootInterface for State { async fn supported_mime_types(&self) -> fdo::Result> { Ok(self.supported_mime_types.borrow().clone()) } -} -#[async_trait(?Send)] -impl LocalPlayerInterface for State { - async fn next(&self) -> fdo::Result<()> { + async fn next(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let player = self.player(); for cb in self.next_cbs.borrow().iter() { cb(&player); @@ -154,7 +151,7 @@ impl LocalPlayerInterface for State { Ok(()) } - async fn previous(&self) -> fdo::Result<()> { + async fn previous(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let player = self.player(); for cb in self.previous_cbs.borrow().iter() { cb(&player); @@ -162,7 +159,7 @@ impl LocalPlayerInterface for State { Ok(()) } - async fn pause(&self) -> fdo::Result<()> { + async fn pause(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let player = self.player(); for cb in self.pause_cbs.borrow().iter() { cb(&player); @@ -170,7 +167,7 @@ impl LocalPlayerInterface for State { Ok(()) } - async fn play_pause(&self) -> fdo::Result<()> { + async fn play_pause(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let player = self.player(); for cb in self.play_pause_cbs.borrow().iter() { cb(&player); @@ -178,7 +175,7 @@ impl LocalPlayerInterface for State { Ok(()) } - async fn stop(&self) -> fdo::Result<()> { + async fn stop(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let player = self.player(); for cb in self.stop_cbs.borrow().iter() { cb(&player); @@ -186,7 +183,7 @@ impl LocalPlayerInterface for State { Ok(()) } - async fn play(&self) -> fdo::Result<()> { + async fn play(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { let player = self.player(); for cb in self.play_cbs.borrow().iter() { cb(&player); @@ -194,7 +191,7 @@ impl LocalPlayerInterface for State { Ok(()) } - async fn seek(&self, offset: Time) -> fdo::Result<()> { + async fn seek(&self, _: &ServerProxy<'_, Self>, offset: Time) -> fdo::Result<()> { let player = self.player(); for cb in self.seek_cbs.borrow().iter() { cb(&player, offset); @@ -202,7 +199,12 @@ impl LocalPlayerInterface for State { Ok(()) } - async fn set_position(&self, track_id: TrackId, position: Time) -> fdo::Result<()> { + async fn set_position( + &self, + _: &ServerProxy<'_, Self>, + track_id: TrackId, + position: Time, + ) -> fdo::Result<()> { let player = self.player(); for cb in self.set_position_cbs.borrow().iter() { cb(&player, &track_id, position); @@ -210,7 +212,7 @@ impl LocalPlayerInterface for State { Ok(()) } - async fn open_uri(&self, uri: String) -> fdo::Result<()> { + async fn open_uri(&self, _: &ServerProxy<'_, Self>, uri: String) -> fdo::Result<()> { let player = self.player(); for cb in self.open_uri_cbs.borrow().iter() { cb(&player, &uri); diff --git a/src/server.rs b/src/server.rs index 27d4ef3..2d10911 100644 --- a/src/server.rs +++ b/src/server.rs @@ -17,8 +17,7 @@ use zbus::{ use crate::{ LoopStatus, MaybePlaylist, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Playlist, PlaylistId, PlaylistOrdering, PlaylistsInterface, PlaylistsProperty, PlaylistsSignal, Property, - RootInterface, Signal, Time, TrackId, TrackListInterface, TrackListProperty, TrackListSignal, - Uri, Volume, + Signal, Time, TrackId, TrackListInterface, TrackListProperty, TrackListSignal, Uri, Volume, }; const OBJECT_PATH: ObjectPath<'static> = @@ -31,14 +30,16 @@ struct RawRootInterface { #[dbus_interface(name = "org.mpris.MediaPlayer2")] impl RawRootInterface where - T: RootInterface + 'static, + T: PlayerInterface + 'static, { - async fn raise(&self) -> fdo::Result<()> { - self.imp.raise().await + async fn raise(&self, #[zbus(connection)] connection: &zbus::Connection) -> fdo::Result<()> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp.raise(&proxy).await } - async fn quit(&self) -> fdo::Result<()> { - self.imp.quit().await + async fn quit(&self, #[zbus(connection)] connection: &zbus::Connection) -> fdo::Result<()> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp.quit(&proxy).await } #[dbus_interface(property)] @@ -101,40 +102,65 @@ impl RawPlayerInterface where T: PlayerInterface + 'static, { - async fn next(&self) -> fdo::Result<()> { - self.imp.next().await + async fn next(&self, #[zbus(connection)] connection: &zbus::Connection) -> fdo::Result<()> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp.next(&proxy).await } - async fn previous(&self) -> fdo::Result<()> { - self.imp.previous().await + async fn previous(&self, #[zbus(connection)] connection: &zbus::Connection) -> fdo::Result<()> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp.previous(&proxy).await } - async fn pause(&self) -> fdo::Result<()> { - self.imp.pause().await + async fn pause(&self, #[zbus(connection)] connection: &zbus::Connection) -> fdo::Result<()> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp.pause(&proxy).await } - async fn play_pause(&self) -> fdo::Result<()> { - self.imp.play_pause().await + async fn play_pause( + &self, + #[zbus(connection)] connection: &zbus::Connection, + ) -> fdo::Result<()> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp.play_pause(&proxy).await } - async fn stop(&self) -> fdo::Result<()> { - self.imp.stop().await + async fn stop(&self, #[zbus(connection)] connection: &zbus::Connection) -> fdo::Result<()> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp.stop(&proxy).await } - async fn play(&self) -> fdo::Result<()> { - self.imp.play().await + async fn play(&self, #[zbus(connection)] connection: &zbus::Connection) -> fdo::Result<()> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp.play(&proxy).await } - async fn seek(&self, offset: Time) -> fdo::Result<()> { - self.imp.seek(offset).await + async fn seek( + &self, + #[zbus(connection)] connection: &zbus::Connection, + offset: Time, + ) -> fdo::Result<()> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp.seek(&proxy, offset).await } - async fn set_position(&self, track_id: TrackId, position: Time) -> fdo::Result<()> { - self.imp.set_position(track_id, position).await + async fn set_position( + &self, + #[zbus(connection)] connection: &zbus::Connection, + track_id: TrackId, + position: Time, + ) -> fdo::Result<()> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp.set_position(&proxy, track_id, position).await } - async fn open_uri(&self, uri: String) -> fdo::Result<()> { - self.imp.open_uri(uri).await + async fn open_uri( + &self, + #[zbus(connection)] connection: &zbus::Connection, + uri: String, + ) -> fdo::Result<()> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp.open_uri(&proxy, uri).await } #[dbus_interface(signal)] @@ -245,25 +271,44 @@ impl RawTrackListInterface where T: TrackListInterface + 'static, { - async fn get_tracks_metadata(&self, track_ids: Vec) -> fdo::Result> { - self.imp.get_tracks_metadata(track_ids).await + async fn get_tracks_metadata( + &self, + #[zbus(connection)] connection: &zbus::Connection, + track_ids: Vec, + ) -> fdo::Result> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp.get_tracks_metadata(&proxy, track_ids).await } async fn add_track( &self, + #[zbus(connection)] connection: &zbus::Connection, uri: Uri, after_track: TrackId, set_as_current: bool, ) -> fdo::Result<()> { - self.imp.add_track(uri, after_track, set_as_current).await + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp + .add_track(&proxy, uri, after_track, set_as_current) + .await } - async fn remove_track(&self, track_id: TrackId) -> fdo::Result<()> { - self.imp.remove_track(track_id).await + async fn remove_track( + &self, + #[zbus(connection)] connection: &zbus::Connection, + track_id: TrackId, + ) -> fdo::Result<()> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp.remove_track(&proxy, track_id).await } - async fn go_to(&self, track_id: TrackId) -> fdo::Result<()> { - self.imp.go_to(track_id).await + async fn go_to( + &self, + #[zbus(connection)] connection: &zbus::Connection, + track_id: TrackId, + ) -> fdo::Result<()> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp.go_to(&proxy, track_id).await } #[dbus_interface(signal)] @@ -310,19 +355,26 @@ impl RawPlaylistsInterface where T: PlaylistsInterface + 'static, { - async fn activate_playlist(&self, playlist_id: PlaylistId) -> fdo::Result<()> { - self.imp.activate_playlist(playlist_id).await + async fn activate_playlist( + &self, + #[zbus(connection)] connection: &zbus::Connection, + playlist_id: PlaylistId, + ) -> fdo::Result<()> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); + self.imp.activate_playlist(&proxy, playlist_id).await } async fn get_playlists( &self, + #[zbus(connection)] connection: &zbus::Connection, index: u32, max_count: u32, order: PlaylistOrdering, reverse_order: bool, ) -> fdo::Result> { + let proxy = ServerProxy::new(connection, self.imp.as_ref()); self.imp - .get_playlists(index, max_count, order, reverse_order) + .get_playlists(&proxy, index, max_count, order, reverse_order) .await } @@ -345,36 +397,6 @@ where } } -/// Thin wrapper around [`zbus::Connection`] that calls to `T`'s implementation -/// of [`RootInterface`], [`PlayerInterface`], [`TrackListInterface`], and -/// [`PlaylistsInterface`] to implement `org.mpris.MediaPlayer2` and its -/// sub-interfaces. -/// -/// When implementing using [`Server`], it is important to note that properties -/// changed signals are *not* emitted automatically; they must be emitted -/// manually using [`Server::properties_changed`], -/// [`Server::track_list_properties_changed`], or -/// [`Server::playlists_properties_changed`], when they changed internally. -pub struct Server -where - T: PlayerInterface + 'static, -{ - connection: OnceCell, - #[allow(clippy::type_complexity)] - connection_init: - Mutex Result> + Send + Sync>>>, - imp: Arc, -} - -impl fmt::Debug for Server -where - T: PlayerInterface + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Server").finish() - } -} - macro_rules! insert_property { ($item:ident, $property_type:ident, $source:ident => $($map:ident, $property:ident, $getter:ident);*) => { match $item { @@ -388,56 +410,29 @@ macro_rules! insert_property { }; } -impl Server -where - T: PlayerInterface + 'static, -{ - /// Creates a new [`Server`] with the given bus name suffix and - /// implementation, `imp`, which must implement [`RootInterface`] and - /// [`PlayerInterface`]. - /// - /// To start the connection, [`Server::init`] must be called. - /// - /// The resulting bus name will be - /// `org.mpris.MediaPlayer2.`, where - /// ``must be a unique identifier, such as one based on a - /// UNIX process id. For example, this could be: - /// - /// * `org.mpris.MediaPlayer2.vlc.instance7389` - /// - /// **Note:** According to the [`D-Bus specification`], the unique - /// identifier "must only contain the ASCII characters - /// `[A-Z][a-z][0-9]_-`" and "must not begin with a digit". - /// - /// [`D-Bus specification`]: dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-bus - pub fn new(bus_name_suffix: &str, imp: T) -> Self { - Self::new_inner(bus_name_suffix, imp, |builder, _| Ok(builder)) - } +/// A proxy to a server that allows emitting signals and accessing connection. +pub struct ServerProxy<'a, T> { + connection: &'a Connection, + imp: &'a T, +} - /// Initializes the connection. - /// - /// This is a no-op if the connection has already been initialized. - /// - /// This is also called automatically when emitting signals and properties - /// changed. - pub async fn init(&self) -> Result<()> { - self.get_or_init_connection().await?; - Ok(()) +impl fmt::Debug for ServerProxy<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ServerProxy").finish() } +} - /// Returns a reference to the underlying implementation. +impl<'a, T> ServerProxy<'a, T> { #[inline] - pub fn imp(&self) -> &T { - &self.imp - } - - /// Returns a reference to the inner [`Connection`]. - /// - /// If you needed to call this, consider filing an issue. - pub async fn connection(&self) -> Result<&Connection> { - self.get_or_init_connection().await + pub(crate) fn new(connection: &'a Connection, imp: &'a T) -> Self { + Self { connection, imp } } +} +impl ServerProxy<'_, T> +where + T: PlayerInterface + 'static, +{ /// Emits the given signal. pub async fn emit(&self, signal: Signal) -> Result<()> { match signal { @@ -507,54 +502,6 @@ where Ok(()) } - fn new_inner( - bus_name_suffix: &str, - imp: T, - builder_ext_func: impl FnOnce(ConnectionBuilder<'_>, Arc) -> Result> - + Send - + Sync - + 'static, - ) -> Self { - let bus_name = format!("org.mpris.MediaPlayer2.{}", bus_name_suffix); - let imp = Arc::new(imp); - - let imp_clone = Arc::clone(&imp); - let connection_init = Box::new(|| { - let builder = ConnectionBuilder::session()? - .name(bus_name)? - .serve_at( - OBJECT_PATH, - RawRootInterface { - imp: Arc::clone(&imp_clone), - }, - )? - .serve_at( - OBJECT_PATH, - RawPlayerInterface { - imp: Arc::clone(&imp_clone), - }, - )?; - builder_ext_func(builder, imp_clone) - }); - - Self { - connection: OnceCell::new(), - connection_init: Mutex::new(Some(connection_init)), - imp, - } - } - - async fn get_or_init_connection(&self) -> Result<&Connection> { - self.connection - .get_or_try_init(|| async { - // Safety: connection only initialized once - let connection_init = self.connection_init.lock().unwrap().take().unwrap(); - let connection = connection_init()?.build().await?; - Ok(connection) - }) - .await - } - async fn properties_changed_inner( &self, changed_properties: HashMap<&str, Value<'_>>, @@ -578,18 +525,17 @@ where where I: Interface, { - let connection = self.get_or_init_connection().await?; - // FIXME Hold a lock to the interface until the signal is emitted. // This is a workaround for `Invalid client serial` errors. // See https://github.com/flatpak/xdg-dbus-proxy/issues/46 - let iface_ref = connection + let iface_ref = self + .connection .object_server() .interface::<_, I>(OBJECT_PATH) .await?; let _guard = iface_ref.get_mut().await; - connection + self.connection .emit_signal( None::>, OBJECT_PATH, @@ -601,21 +547,10 @@ where } } -impl Server +impl ServerProxy<'_, T> where T: TrackListInterface + 'static, { - /// Creates a new [`Server`] with the given bus name suffix and - /// implementation, which must implement [`TrackListInterface`] in addition - /// to [`RootInterface`] and [`PlayerInterface`]. - /// - /// See also [`Server::new`]. - pub fn new_with_track_list(bus_name_suffix: &str, imp: T) -> Self { - Self::new_inner(bus_name_suffix, imp, |builder, imp| { - builder.serve_at(OBJECT_PATH, RawTrackListInterface { imp }) - }) - } - /// Emits the given signal on the `TrackList` interface. pub async fn track_list_emit(&self, signal: TrackListSignal) -> Result<()> { match signal { @@ -685,21 +620,10 @@ where } } -impl Server +impl ServerProxy<'_, T> where T: PlaylistsInterface + 'static, { - /// Creates a new [`Server`] with the given bus name suffix and - /// implementation, which must implement [`PlaylistsInterface`] in addition - /// to [`RootInterface`] and [`PlayerInterface`]. - /// - /// See also [`Server::new`]. - pub fn new_with_playlists(bus_name_suffix: &str, imp: T) -> Self { - Self::new_inner(bus_name_suffix, imp, |builder, imp| { - builder.serve_at(OBJECT_PATH, RawPlaylistsInterface { imp }) - }) - } - /// Emits the given signal on the `Playlists` interface. pub async fn playlists_emit(&self, signal: PlaylistsSignal) -> Result<()> { match signal { @@ -740,6 +664,238 @@ where } } +/// Thin wrapper around [`zbus::Connection`] that calls to `T`'s implementation +/// of [`RootInterface`], [`PlayerInterface`], [`TrackListInterface`], and +/// [`PlaylistsInterface`] to implement `org.mpris.MediaPlayer2` and its +/// sub-interfaces. +/// +/// When implementing using [`Server`], it is important to note that properties +/// changed signals are *not* emitted automatically; they must be emitted +/// manually using [`Server::properties_changed`], +/// [`Server::track_list_properties_changed`], or +/// [`Server::playlists_properties_changed`], when they changed internally. +pub struct Server +where + T: PlayerInterface + 'static, +{ + connection: OnceCell, + #[allow(clippy::type_complexity)] + connection_init: + Mutex Result> + Send + Sync>>>, + imp: Arc, +} + +impl fmt::Debug for Server +where + T: PlayerInterface + 'static, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Server").finish() + } +} + +impl Server +where + T: PlayerInterface + 'static, +{ + /// Creates a new [`Server`] with the given bus name suffix and + /// implementation, `imp`, which must implement [`RootInterface`] and + /// [`PlayerInterface`]. + /// + /// To start the connection, [`Server::init`] must be called. + /// + /// The resulting bus name will be + /// `org.mpris.MediaPlayer2.`, where + /// ``must be a unique identifier, such as one based on a + /// UNIX process id. For example, this could be: + /// + /// * `org.mpris.MediaPlayer2.vlc.instance7389` + /// + /// **Note:** According to the [`D-Bus specification`], the unique + /// identifier "must only contain the ASCII characters + /// `[A-Z][a-z][0-9]_-`" and "must not begin with a digit". + /// + /// [`D-Bus specification`]: dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-bus + pub fn new(bus_name_suffix: &str, imp: T) -> Self { + Self::new_inner(bus_name_suffix, imp, |builder, _| Ok(builder)) + } + + /// Initializes the connection. + /// + /// This is a no-op if the connection has already been initialized. + /// + /// This is also called automatically when emitting signals and properties + /// changed. + pub async fn init(&self) -> Result<()> { + self.get_or_init_connection().await?; + Ok(()) + } + + /// Returns a reference to the underlying implementation. + #[inline] + pub fn imp(&self) -> &T { + &self.imp + } + + /// Returns a reference to the inner [`Connection`]. + /// + /// If you needed to call this, consider filing an issue. + pub async fn connection(&self) -> Result<&Connection> { + self.get_or_init_connection().await + } + + /// Emits the given signal. + #[inline] + pub async fn emit(&self, signal: Signal) -> Result<()> { + ServerProxy::new(self.get_or_init_connection().await?, self.imp()) + .emit(signal) + .await + } + + /// Emits the `PropertiesChanged` signal for the given properties. + /// + /// This categorizes the property in the `changed` or `invalidated` + /// properties as defined by the spec. + /// + /// [`Server::track_list_properties_changed`] or + /// [`Server::playlists_properties_changed`] are used + /// to emit `PropertiesChanged` for the `TrackList` or `Playlists` + /// interfaces respectively. + #[inline] + pub async fn properties_changed( + &self, + properties: impl Into>, + ) -> Result<()> { + ServerProxy::new(self.get_or_init_connection().await?, self.imp()) + .properties_changed(properties) + .await + } + + fn new_inner( + bus_name_suffix: &str, + imp: T, + builder_ext_func: impl FnOnce(ConnectionBuilder<'_>, Arc) -> Result> + + Send + + Sync + + 'static, + ) -> Self { + let bus_name = format!("org.mpris.MediaPlayer2.{}", bus_name_suffix); + let imp = Arc::new(imp); + + let imp_clone = Arc::clone(&imp); + let connection_init = Box::new(|| { + let builder = ConnectionBuilder::session()? + .name(bus_name)? + .serve_at( + OBJECT_PATH, + RawRootInterface { + imp: Arc::clone(&imp_clone), + }, + )? + .serve_at( + OBJECT_PATH, + RawPlayerInterface { + imp: Arc::clone(&imp_clone), + }, + )?; + builder_ext_func(builder, imp_clone) + }); + + Self { + connection: OnceCell::new(), + connection_init: Mutex::new(Some(connection_init)), + imp, + } + } + + async fn get_or_init_connection(&self) -> Result<&Connection> { + self.connection + .get_or_try_init(|| async { + // Safety: connection only initialized once + let connection_init = self.connection_init.lock().unwrap().take().unwrap(); + let connection = connection_init()?.build().await?; + Ok(connection) + }) + .await + } +} + +impl Server +where + T: TrackListInterface + 'static, +{ + /// Creates a new [`Server`] with the given bus name suffix and + /// implementation, which must implement [`TrackListInterface`] in addition + /// to [`RootInterface`] and [`PlayerInterface`]. + /// + /// See also [`Server::new`]. + pub fn new_with_track_list(bus_name_suffix: &str, imp: T) -> Self { + Self::new_inner(bus_name_suffix, imp, |builder, imp| { + builder.serve_at(OBJECT_PATH, RawTrackListInterface { imp }) + }) + } + + /// Emits the given signal on the `TrackList` interface. + #[inline] + pub async fn track_list_emit(&self, signal: TrackListSignal) -> Result<()> { + ServerProxy::new(self.get_or_init_connection().await?, self.imp()) + .track_list_emit(signal) + .await + } + + /// Emits the `PropertiesChanged` signal for the given properties. + /// + /// This categorizes the property in the `changed` or `invalidated` + /// properties as defined by the spec. + #[inline] + pub async fn track_list_properties_changed( + &self, + properties: impl Into>, + ) -> Result<()> { + ServerProxy::new(self.get_or_init_connection().await?, self.imp()) + .track_list_properties_changed(properties) + .await + } +} + +impl Server +where + T: PlaylistsInterface + 'static, +{ + /// Creates a new [`Server`] with the given bus name suffix and + /// implementation, which must implement [`PlaylistsInterface`] in addition + /// to [`RootInterface`] and [`PlayerInterface`]. + /// + /// See also [`Server::new`]. + pub fn new_with_playlists(bus_name_suffix: &str, imp: T) -> Self { + Self::new_inner(bus_name_suffix, imp, |builder, imp| { + builder.serve_at(OBJECT_PATH, RawPlaylistsInterface { imp }) + }) + } + + /// Emits the given signal on the `Playlists` interface. + #[inline] + pub async fn playlists_emit(&self, signal: PlaylistsSignal) -> Result<()> { + ServerProxy::new(self.get_or_init_connection().await?, self.imp()) + .playlists_emit(signal) + .await + } + + /// Emits the `PropertiesChanged` signal for the given properties. + /// + /// This categorizes the property in the `changed` or `invalidated` + /// properties as defined by the spec. + #[inline] + pub async fn playlists_properties_changed( + &self, + properties: impl Into>, + ) -> Result<()> { + ServerProxy::new(self.get_or_init_connection().await?, self.imp()) + .playlists_properties_changed(properties) + .await + } +} + impl Server where T: TrackListInterface + PlaylistsInterface + 'static, @@ -774,12 +930,12 @@ mod tests { struct TestPlayer; #[async_trait] - impl RootInterface for TestPlayer { - async fn raise(&self) -> fdo::Result<()> { + impl PlayerInterface for TestPlayer { + async fn raise(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { unreachable!() } - async fn quit(&self) -> fdo::Result<()> { + async fn quit(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { unreachable!() } @@ -822,43 +978,45 @@ mod tests { async fn supported_mime_types(&self) -> fdo::Result> { unreachable!() } - } - #[async_trait] - impl PlayerInterface for TestPlayer { - async fn next(&self) -> fdo::Result<()> { + async fn next(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { unreachable!() } - async fn previous(&self) -> fdo::Result<()> { + async fn previous(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { unreachable!() } - async fn pause(&self) -> fdo::Result<()> { + async fn pause(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { unreachable!() } - async fn play_pause(&self) -> fdo::Result<()> { + async fn play_pause(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { unreachable!() } - async fn stop(&self) -> fdo::Result<()> { + async fn stop(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { unreachable!() } - async fn play(&self) -> fdo::Result<()> { + async fn play(&self, _: &ServerProxy<'_, Self>) -> fdo::Result<()> { unreachable!() } - async fn seek(&self, _offset: Time) -> fdo::Result<()> { + async fn seek(&self, _: &ServerProxy<'_, Self>, _offset: Time) -> fdo::Result<()> { unreachable!() } - async fn set_position(&self, _track_id: TrackId, _position: Time) -> fdo::Result<()> { + async fn set_position( + &self, + _: &ServerProxy<'_, Self>, + _track_id: TrackId, + _position: Time, + ) -> fdo::Result<()> { unreachable!() } - async fn open_uri(&self, _uri: String) -> fdo::Result<()> { + async fn open_uri(&self, _: &ServerProxy<'_, Self>, _uri: String) -> fdo::Result<()> { unreachable!() } @@ -943,6 +1101,7 @@ mod tests { impl TrackListInterface for TestPlayer { async fn get_tracks_metadata( &self, + _: &ServerProxy<'_, Self>, _track_ids: Vec, ) -> fdo::Result> { unreachable!() @@ -950,6 +1109,7 @@ mod tests { async fn add_track( &self, + _: &ServerProxy<'_, Self>, _uri: Uri, _after_track: TrackId, _set_as_current: bool, @@ -957,11 +1117,15 @@ mod tests { unreachable!() } - async fn remove_track(&self, _track_id: TrackId) -> fdo::Result<()> { + async fn remove_track( + &self, + _: &ServerProxy<'_, Self>, + _track_id: TrackId, + ) -> fdo::Result<()> { unreachable!() } - async fn go_to(&self, _track_id: TrackId) -> fdo::Result<()> { + async fn go_to(&self, _: &ServerProxy<'_, Self>, _track_id: TrackId) -> fdo::Result<()> { unreachable!() } @@ -976,12 +1140,17 @@ mod tests { #[async_trait] impl PlaylistsInterface for TestPlayer { - async fn activate_playlist(&self, _playlist_id: PlaylistId) -> fdo::Result<()> { + async fn activate_playlist( + &self, + _: &ServerProxy<'_, Self>, + _playlist_id: PlaylistId, + ) -> fdo::Result<()> { unreachable!() } async fn get_playlists( &self, + _: &ServerProxy<'_, Self>, _index: u32, _max_count: u32, _order: PlaylistOrdering,