mpris_server/
server.rs

1use std::{collections::HashMap, fmt, sync::Arc};
2
3use serde::Serialize;
4use zbus::{
5    Connection, Result, conn, fdo,
6    names::{BusName, OwnedWellKnownName, WellKnownName},
7    object_server::{Interface, SignalEmitter},
8    zvariant::{DynamicType, ObjectPath, Value},
9};
10
11use crate::{
12    LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Playlist, PlaylistId,
13    PlaylistOrdering, PlaylistsInterface, PlaylistsProperty, PlaylistsSignal, Property,
14    RootInterface, Signal, Time, TrackId, TrackListInterface, TrackListProperty, TrackListSignal,
15    Uri, Volume, playlist::MaybePlaylist,
16};
17
18const BUS_NAME_PREFIX: &str = "org.mpris.MediaPlayer2.";
19
20const OBJECT_PATH: ObjectPath<'static> =
21    ObjectPath::from_static_str_unchecked("/org/mpris/MediaPlayer2");
22
23struct RawRootInterface<T> {
24    imp: Arc<T>,
25}
26
27#[zbus::interface(name = "org.mpris.MediaPlayer2")]
28impl<T> RawRootInterface<T>
29where
30    T: RootInterface + 'static,
31{
32    async fn raise(&self) -> fdo::Result<()> {
33        self.imp.raise().await
34    }
35
36    async fn quit(&self) -> fdo::Result<()> {
37        self.imp.quit().await
38    }
39
40    #[zbus(property)]
41    async fn can_quit(&self) -> fdo::Result<bool> {
42        self.imp.can_quit().await
43    }
44
45    #[zbus(property)]
46    async fn fullscreen(&self) -> fdo::Result<bool> {
47        self.imp.fullscreen().await
48    }
49
50    #[zbus(property)]
51    async fn set_fullscreen(&self, fullscreen: bool) -> Result<()> {
52        self.imp.set_fullscreen(fullscreen).await
53    }
54
55    #[zbus(property)]
56    async fn can_set_fullscreen(&self) -> fdo::Result<bool> {
57        self.imp.can_set_fullscreen().await
58    }
59
60    #[zbus(property)]
61    async fn can_raise(&self) -> fdo::Result<bool> {
62        self.imp.can_raise().await
63    }
64
65    #[zbus(property)]
66    async fn has_track_list(&self) -> fdo::Result<bool> {
67        self.imp.has_track_list().await
68    }
69
70    #[zbus(property)]
71    async fn identity(&self) -> fdo::Result<String> {
72        self.imp.identity().await
73    }
74
75    #[zbus(property)]
76    async fn desktop_entry(&self) -> fdo::Result<String> {
77        self.imp.desktop_entry().await
78    }
79
80    #[zbus(property)]
81    async fn supported_uri_schemes(&self) -> fdo::Result<Vec<String>> {
82        self.imp.supported_uri_schemes().await
83    }
84
85    #[zbus(property)]
86    async fn supported_mime_types(&self) -> fdo::Result<Vec<String>> {
87        self.imp.supported_mime_types().await
88    }
89}
90
91struct RawPlayerInterface<T> {
92    imp: Arc<T>,
93}
94
95#[zbus::interface(name = "org.mpris.MediaPlayer2.Player")]
96impl<T> RawPlayerInterface<T>
97where
98    T: PlayerInterface + 'static,
99{
100    async fn next(&self) -> fdo::Result<()> {
101        self.imp.next().await
102    }
103
104    async fn previous(&self) -> fdo::Result<()> {
105        self.imp.previous().await
106    }
107
108    async fn pause(&self) -> fdo::Result<()> {
109        self.imp.pause().await
110    }
111
112    async fn play_pause(&self) -> fdo::Result<()> {
113        self.imp.play_pause().await
114    }
115
116    async fn stop(&self) -> fdo::Result<()> {
117        self.imp.stop().await
118    }
119
120    async fn play(&self) -> fdo::Result<()> {
121        self.imp.play().await
122    }
123
124    async fn seek(&self, offset: Time) -> fdo::Result<()> {
125        self.imp.seek(offset).await
126    }
127
128    async fn set_position(&self, track_id: TrackId, position: Time) -> fdo::Result<()> {
129        self.imp.set_position(track_id, position).await
130    }
131
132    async fn open_uri(&self, uri: String) -> fdo::Result<()> {
133        self.imp.open_uri(uri).await
134    }
135
136    #[zbus(signal)]
137    async fn seeked(ctxt: &SignalEmitter<'_>, position: Time) -> Result<()>;
138
139    #[zbus(property)]
140    async fn playback_status(&self) -> fdo::Result<PlaybackStatus> {
141        self.imp.playback_status().await
142    }
143
144    #[zbus(property)]
145    async fn loop_status(&self) -> fdo::Result<LoopStatus> {
146        self.imp.loop_status().await
147    }
148
149    #[zbus(property)]
150    async fn set_loop_status(&self, loop_status: LoopStatus) -> Result<()> {
151        self.imp.set_loop_status(loop_status).await
152    }
153
154    #[zbus(property)]
155    async fn rate(&self) -> fdo::Result<PlaybackRate> {
156        self.imp.rate().await
157    }
158
159    #[zbus(property)]
160    async fn set_rate(&self, rate: PlaybackRate) -> Result<()> {
161        self.imp.set_rate(rate).await
162    }
163
164    #[zbus(property)]
165    async fn shuffle(&self) -> fdo::Result<bool> {
166        self.imp.shuffle().await
167    }
168
169    #[zbus(property)]
170    async fn set_shuffle(&self, shuffle: bool) -> Result<()> {
171        self.imp.set_shuffle(shuffle).await
172    }
173
174    #[zbus(property)]
175    async fn metadata(&self) -> fdo::Result<Metadata> {
176        self.imp.metadata().await
177    }
178
179    #[zbus(property)]
180    async fn volume(&self) -> fdo::Result<Volume> {
181        self.imp.volume().await
182    }
183
184    #[zbus(property)]
185    async fn set_volume(&self, volume: Volume) -> Result<()> {
186        self.imp.set_volume(volume).await
187    }
188
189    #[zbus(property(emits_changed_signal = "false"))]
190    async fn position(&self) -> fdo::Result<Time> {
191        self.imp.position().await
192    }
193
194    #[zbus(property)]
195    async fn minimum_rate(&self) -> fdo::Result<PlaybackRate> {
196        self.imp.minimum_rate().await
197    }
198
199    #[zbus(property)]
200    async fn maximum_rate(&self) -> fdo::Result<PlaybackRate> {
201        self.imp.maximum_rate().await
202    }
203
204    #[zbus(property)]
205    async fn can_go_next(&self) -> fdo::Result<bool> {
206        self.imp.can_go_next().await
207    }
208
209    #[zbus(property)]
210    async fn can_go_previous(&self) -> fdo::Result<bool> {
211        self.imp.can_go_previous().await
212    }
213
214    #[zbus(property)]
215    async fn can_play(&self) -> fdo::Result<bool> {
216        self.imp.can_play().await
217    }
218
219    #[zbus(property)]
220    async fn can_pause(&self) -> fdo::Result<bool> {
221        self.imp.can_pause().await
222    }
223
224    #[zbus(property)]
225    async fn can_seek(&self) -> fdo::Result<bool> {
226        self.imp.can_seek().await
227    }
228
229    // FIXME This should have been "const", but it is "false" in the spec.
230    #[zbus(property(emits_changed_signal = "false"))]
231    async fn can_control(&self) -> fdo::Result<bool> {
232        self.imp.can_control().await
233    }
234}
235
236struct RawTrackListInterface<T> {
237    imp: Arc<T>,
238}
239
240#[zbus::interface(name = "org.mpris.MediaPlayer2.TrackList")]
241impl<T> RawTrackListInterface<T>
242where
243    T: TrackListInterface + 'static,
244{
245    async fn get_tracks_metadata(&self, track_ids: Vec<TrackId>) -> fdo::Result<Vec<Metadata>> {
246        self.imp.get_tracks_metadata(track_ids).await
247    }
248
249    async fn add_track(
250        &self,
251        uri: Uri,
252        after_track: TrackId,
253        set_as_current: bool,
254    ) -> fdo::Result<()> {
255        self.imp.add_track(uri, after_track, set_as_current).await
256    }
257
258    async fn remove_track(&self, track_id: TrackId) -> fdo::Result<()> {
259        self.imp.remove_track(track_id).await
260    }
261
262    async fn go_to(&self, track_id: TrackId) -> fdo::Result<()> {
263        self.imp.go_to(track_id).await
264    }
265
266    #[zbus(signal)]
267    async fn track_list_replaced(
268        ctxt: &SignalEmitter<'_>,
269        tracks: Vec<TrackId>,
270        current_track: TrackId,
271    ) -> Result<()>;
272
273    #[zbus(signal)]
274    async fn track_added(
275        ctxt: &SignalEmitter<'_>,
276        metadata: Metadata,
277        after_track: TrackId,
278    ) -> Result<()>;
279
280    #[zbus(signal)]
281    async fn track_removed(ctxt: &SignalEmitter<'_>, track_id: TrackId) -> Result<()>;
282
283    #[zbus(signal)]
284    async fn track_metadata_changed(
285        ctxt: &SignalEmitter<'_>,
286        track_id: TrackId,
287        metadata: Metadata,
288    ) -> Result<()>;
289
290    #[zbus(property(emits_changed_signal = "invalidates"))]
291    async fn tracks(&self) -> fdo::Result<Vec<TrackId>> {
292        self.imp.tracks().await
293    }
294
295    #[zbus(property)]
296    async fn can_edit_tracks(&self) -> fdo::Result<bool> {
297        self.imp.can_edit_tracks().await
298    }
299}
300
301struct RawPlaylistsInterface<T> {
302    imp: Arc<T>,
303}
304
305#[zbus::interface(name = "org.mpris.MediaPlayer2.Playlists")]
306impl<T> RawPlaylistsInterface<T>
307where
308    T: PlaylistsInterface + 'static,
309{
310    async fn activate_playlist(&self, playlist_id: PlaylistId) -> fdo::Result<()> {
311        self.imp.activate_playlist(playlist_id).await
312    }
313
314    async fn get_playlists(
315        &self,
316        index: u32,
317        max_count: u32,
318        order: PlaylistOrdering,
319        reverse_order: bool,
320    ) -> fdo::Result<Vec<Playlist>> {
321        self.imp
322            .get_playlists(index, max_count, order, reverse_order)
323            .await
324    }
325
326    #[zbus(signal)]
327    async fn playlist_changed(ctxt: &SignalEmitter<'_>, playlist: Playlist) -> Result<()>;
328
329    #[zbus(property)]
330    async fn playlist_count(&self) -> fdo::Result<u32> {
331        self.imp.playlist_count().await
332    }
333
334    #[zbus(property)]
335    async fn orderings(&self) -> fdo::Result<Vec<PlaylistOrdering>> {
336        self.imp.orderings().await
337    }
338
339    #[zbus(property)]
340    async fn active_playlist(&self) -> fdo::Result<MaybePlaylist> {
341        self.imp.active_playlist().await.map(MaybePlaylist::from)
342    }
343}
344
345/// Thin wrapper around [`zbus::Connection`] that calls to `T`'s implementation
346/// of [`RootInterface`], [`PlayerInterface`], [`TrackListInterface`], and
347/// [`PlaylistsInterface`] to implement `org.mpris.MediaPlayer2` and its
348/// sub-interfaces.
349///
350/// When implementing using [`Server`], it is important to note that properties
351/// changed signals are *not* emitted automatically; they must be emitted
352/// manually using [`Server::properties_changed`],
353/// [`Server::track_list_properties_changed`], or
354/// [`Server::playlists_properties_changed`], when they changed internally.
355pub struct Server<T> {
356    connection: Connection,
357    imp: Arc<T>,
358    bus_name: OwnedWellKnownName,
359}
360
361impl<T> fmt::Debug for Server<T> {
362    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363        f.debug_struct("Server").finish()
364    }
365}
366
367macro_rules! insert_property {
368    ($item:ident, $property_type:ident => $($map:ident, $property:ident);*) => {
369        match $item {
370            $(
371                $property_type::$property(val) => {
372                    $map.insert(stringify!($property), Value::new(val));
373                }
374            )*
375        }
376    };
377}
378
379impl<T> Server<T>
380where
381    T: PlayerInterface + 'static,
382{
383    /// Creates a new [`Server`] with the given bus name suffix and
384    /// implementation, `imp`, which must implement [`RootInterface`] and
385    /// [`PlayerInterface`].
386    ///
387    /// The resulting bus name will be
388    /// `org.mpris.MediaPlayer2.<bus_name_suffix>`, where
389    /// `<bus_name_suffix>`must be a unique identifier, such as one based on a
390    /// UNIX process id. For example, this could be:
391    ///
392    /// * `org.mpris.MediaPlayer2.vlc.instance7389`
393    ///
394    /// **Note:** According to the [D-Bus specification], the unique
395    /// identifier "must only contain  the ASCII characters
396    /// `[A-Z][a-z][0-9]_-`" and "must not begin with a digit".
397    ///
398    /// [D-Bus specification]: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-bus
399    pub async fn new(bus_name_suffix: &str, imp: T) -> Result<Self> {
400        Self::new_inner(bus_name_suffix, imp, |builder, _| Ok(builder)).await
401    }
402
403    /// Returns a reference to the underlying implementation.
404    #[inline]
405    pub fn imp(&self) -> &T {
406        &self.imp
407    }
408
409    /// Returns a reference to the inner [`Connection`].
410    ///
411    /// If you needed to call this, consider filing an issue.
412    #[cfg(feature = "unstable")]
413    #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
414    #[inline]
415    pub fn connection(&self) -> &Connection {
416        &self.connection
417    }
418
419    /// Returns the bus name of the server.
420    pub fn bus_name(&self) -> &WellKnownName<'_> {
421        self.bus_name.inner()
422    }
423
424    /// Releases the bus name of the server.
425    ///
426    /// The bus name is automatically released when the server is dropped. But
427    /// if you want to release it manually, you can call this method.
428    ///
429    /// Unless an error is encountered, returns `Ok(true)` if name was
430    /// previously registered with the bus and it has now been successfully
431    /// deregistered, `Ok(false)` if name was not previously registered or
432    /// already deregistered.
433    pub async fn release_bus_name(&self) -> Result<bool> {
434        self.connection.release_name(self.bus_name()).await
435    }
436
437    /// Emits the given signal.
438    pub async fn emit(&self, signal: Signal) -> Result<()> {
439        match signal {
440            Signal::Seeked { position } => {
441                self.emit_inner::<RawPlayerInterface<T>>("Seeked", &(position,))
442                    .await?;
443            }
444        }
445
446        Ok(())
447    }
448
449    /// Emits the `PropertiesChanged` signal for the given properties.
450    ///
451    /// This categorizes the property in the `changed` or `invalidated`
452    /// properties as defined by the spec.
453    ///
454    /// [`Server::track_list_properties_changed`] or
455    /// [`Server::playlists_properties_changed`] are used
456    /// to emit `PropertiesChanged` for the `TrackList` or `Playlists`
457    /// interfaces respectively.
458    pub async fn properties_changed(
459        &self,
460        properties: impl IntoIterator<Item = Property>,
461    ) -> Result<()> {
462        let mut root_changed = HashMap::new();
463        let mut player_changed = HashMap::new();
464
465        for property in properties.into_iter() {
466            insert_property!(
467                property, Property =>
468                root_changed, CanQuit;
469                root_changed, Fullscreen;
470                root_changed, CanSetFullscreen;
471                root_changed, CanRaise;
472                root_changed, HasTrackList;
473                root_changed, Identity;
474                root_changed, DesktopEntry;
475                root_changed, SupportedUriSchemes;
476                root_changed, SupportedMimeTypes;
477                player_changed, PlaybackStatus;
478                player_changed, LoopStatus;
479                player_changed, Rate;
480                player_changed, Shuffle;
481                player_changed, Metadata;
482                player_changed, Volume;
483                player_changed, MinimumRate;
484                player_changed, MaximumRate;
485                player_changed, CanGoNext;
486                player_changed, CanGoPrevious;
487                player_changed, CanPlay;
488                player_changed, CanPause;
489                player_changed, CanSeek
490            );
491        }
492
493        if !root_changed.is_empty() {
494            self.properties_changed_inner::<RawRootInterface<T>>(root_changed, &[])
495                .await?;
496        }
497
498        if !player_changed.is_empty() {
499            self.properties_changed_inner::<RawPlayerInterface<T>>(player_changed, &[])
500                .await?;
501        }
502
503        Ok(())
504    }
505
506    async fn new_inner(
507        bus_name_suffix: &str,
508        imp: T,
509        builder_ext_func: impl FnOnce(conn::Builder<'_>, Arc<T>) -> Result<conn::Builder<'_>>
510        + Send
511        + Sync
512        + 'static,
513    ) -> Result<Self> {
514        let imp = Arc::new(imp);
515
516        let bus_name = OwnedWellKnownName::try_from(format!("{BUS_NAME_PREFIX}{bus_name_suffix}"))?;
517
518        let connection_builder = conn::Builder::session()?
519            .name(&bus_name)?
520            .serve_at(
521                OBJECT_PATH,
522                RawRootInterface {
523                    imp: Arc::clone(&imp),
524                },
525            )?
526            .serve_at(
527                OBJECT_PATH,
528                RawPlayerInterface {
529                    imp: Arc::clone(&imp),
530                },
531            )?;
532        let connection = builder_ext_func(connection_builder, Arc::clone(&imp))?
533            .build()
534            .await?;
535
536        Ok(Self {
537            connection,
538            imp,
539            bus_name,
540        })
541    }
542
543    async fn properties_changed_inner<I>(
544        &self,
545        changed_properties: HashMap<&str, Value<'_>>,
546        invalidated_properties: &[&str],
547    ) -> Result<()>
548    where
549        I: Interface,
550    {
551        self.emit_inner::<fdo::Properties>(
552            "PropertiesChanged",
553            &(I::name(), changed_properties, invalidated_properties),
554        )
555        .await
556    }
557
558    async fn emit_inner<I>(
559        &self,
560        signal_name: &str,
561        body: &(impl Serialize + DynamicType),
562    ) -> Result<()>
563    where
564        I: Interface,
565    {
566        self.connection
567            .emit_signal(
568                None::<BusName<'_>>,
569                OBJECT_PATH,
570                I::name(),
571                signal_name,
572                body,
573            )
574            .await
575    }
576}
577
578impl<T> Server<T>
579where
580    T: TrackListInterface + 'static,
581{
582    /// Creates a new [`Server`] with the given bus name suffix and
583    /// implementation, which must implement [`TrackListInterface`] in addition
584    /// to [`RootInterface`] and [`PlayerInterface`].
585    ///
586    /// See also [`Server::new`].
587    pub async fn new_with_track_list(bus_name_suffix: &str, imp: T) -> Result<Self> {
588        Self::new_inner(bus_name_suffix, imp, |builder, imp| {
589            builder.serve_at(OBJECT_PATH, RawTrackListInterface { imp })
590        })
591        .await
592    }
593
594    /// Emits the given signal on the `TrackList` interface.
595    pub async fn track_list_emit(&self, signal: TrackListSignal) -> Result<()> {
596        match signal {
597            TrackListSignal::TrackListReplaced {
598                tracks,
599                current_track,
600            } => {
601                self.emit_inner::<RawTrackListInterface<T>>(
602                    "TrackListReplaced",
603                    &(tracks, current_track),
604                )
605                .await?;
606            }
607            TrackListSignal::TrackAdded {
608                metadata,
609                after_track,
610            } => {
611                self.emit_inner::<RawTrackListInterface<T>>("TrackAdded", &(metadata, after_track))
612                    .await?;
613            }
614            TrackListSignal::TrackRemoved { track_id } => {
615                self.emit_inner::<RawTrackListInterface<T>>("TrackRemoved", &(track_id,))
616                    .await?;
617            }
618            TrackListSignal::TrackMetadataChanged { track_id, metadata } => {
619                self.emit_inner::<RawTrackListInterface<T>>(
620                    "TrackMetadataChanged",
621                    &(track_id, metadata),
622                )
623                .await?;
624            }
625        }
626
627        Ok(())
628    }
629
630    /// Emits the `PropertiesChanged` signal for the given properties.
631    ///
632    /// This categorizes the property in the `changed` or `invalidated`
633    /// properties as defined by the spec.
634    pub async fn track_list_properties_changed(
635        &self,
636        properties: impl IntoIterator<Item = TrackListProperty>,
637    ) -> Result<()> {
638        let mut changed = HashMap::new();
639        let mut invalidated = Vec::new();
640
641        for property in properties.into_iter() {
642            match property {
643                TrackListProperty::Tracks => {
644                    invalidated.push("Tracks");
645                }
646                TrackListProperty::CanEditTracks(can_edit_tracks) => {
647                    changed.insert("CanEditTracks", Value::new(can_edit_tracks));
648                }
649            }
650        }
651
652        if !changed.is_empty() || !invalidated.is_empty() {
653            self.properties_changed_inner::<RawTrackListInterface<T>>(changed, &invalidated)
654                .await?;
655        }
656
657        Ok(())
658    }
659}
660
661impl<T> Server<T>
662where
663    T: PlaylistsInterface + 'static,
664{
665    /// Creates a new [`Server`] with the given bus name suffix and
666    /// implementation, which must implement [`PlaylistsInterface`] in addition
667    /// to [`RootInterface`] and [`PlayerInterface`].
668    ///
669    /// See also [`Server::new`].
670    pub async fn new_with_playlists(bus_name_suffix: &str, imp: T) -> Result<Self> {
671        Self::new_inner(bus_name_suffix, imp, |builder, imp| {
672            builder.serve_at(OBJECT_PATH, RawPlaylistsInterface { imp })
673        })
674        .await
675    }
676
677    /// Emits the given signal on the `Playlists` interface.
678    pub async fn playlists_emit(&self, signal: PlaylistsSignal) -> Result<()> {
679        match signal {
680            PlaylistsSignal::PlaylistChanged { playlist } => {
681                self.emit_inner::<RawPlaylistsInterface<T>>("PlaylistChanged", &(playlist,))
682                    .await?;
683            }
684        }
685
686        Ok(())
687    }
688
689    /// Emits the `PropertiesChanged` signal for the given properties.
690    ///
691    /// This categorizes the property in the `changed` or `invalidated`
692    /// properties as defined by the spec.
693    pub async fn playlists_properties_changed(
694        &self,
695        properties: impl IntoIterator<Item = PlaylistsProperty>,
696    ) -> Result<()> {
697        let mut changed = HashMap::new();
698
699        for property in properties.into_iter() {
700            match property {
701                PlaylistsProperty::PlaylistCount(playlist_count) => {
702                    changed.insert("PlaylistCount", Value::new(playlist_count));
703                }
704                PlaylistsProperty::Orderings(orderings) => {
705                    changed.insert("Orderings", Value::new(orderings));
706                }
707                PlaylistsProperty::ActivePlaylist(active_playlist) => {
708                    changed.insert(
709                        "ActivePlaylist",
710                        Value::new(MaybePlaylist::from(active_playlist)),
711                    );
712                }
713            }
714        }
715
716        if !changed.is_empty() {
717            self.properties_changed_inner::<RawPlaylistsInterface<T>>(changed, &[])
718                .await?;
719        }
720
721        Ok(())
722    }
723}
724
725impl<T> Server<T>
726where
727    T: TrackListInterface + PlaylistsInterface + 'static,
728{
729    /// Creates a new [`Server`] with the given bus name suffix and
730    /// implementation, which must implement [`TrackListInterface`] and
731    /// [`PlaylistsInterface`] in addition to [`RootInterface`] and
732    /// [`PlayerInterface`].
733    ///
734    /// See also [`Server::new`].
735    pub async fn new_with_all(bus_name_suffix: &str, imp: T) -> Result<Self> {
736        Self::new_inner(bus_name_suffix, imp, |builder, imp| {
737            builder
738                .serve_at(
739                    OBJECT_PATH,
740                    RawTrackListInterface {
741                        imp: Arc::clone(&imp),
742                    },
743                )?
744                .serve_at(OBJECT_PATH, RawPlaylistsInterface { imp })
745        })
746        .await
747    }
748}
749
750#[cfg(test)]
751mod tests {
752    use static_assertions::assert_impl_all;
753
754    use super::Server;
755
756    #[allow(dead_code)]
757    pub struct TestPlayer;
758
759    assert_impl_all!(Server<TestPlayer>: Send, Sync, Unpin);
760}