mpris_server/
server.rs

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