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 #[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
345pub 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 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 #[inline]
405 pub fn imp(&self) -> &T {
406 &self.imp
407 }
408
409 #[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 pub fn bus_name(&self) -> &WellKnownName<'_> {
421 self.bus_name.inner()
422 }
423
424 pub async fn release_bus_name(&self) -> Result<bool> {
434 self.connection.release_name(self.bus_name()).await
435 }
436
437 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 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 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 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 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 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 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 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 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}