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