1use std::{collections::HashMap, fmt, sync::Arc};
2
3use serde::Serialize;
4use zbus::{
5 conn, fdo,
6 names::BusName,
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 OBJECT_PATH: ObjectPath<'static> =
20 ObjectPath::from_static_str_unchecked("/org/mpris/MediaPlayer2");
21
22struct RawRootInterface<T> {
23 imp: Arc<T>,
24}
25
26#[zbus::interface(name = "org.mpris.MediaPlayer2")]
27impl<T> RawRootInterface<T>
28where
29 T: RootInterface + 'static,
30{
31 async fn raise(&self) -> fdo::Result<()> {
32 self.imp.raise().await
33 }
34
35 async fn quit(&self) -> fdo::Result<()> {
36 self.imp.quit().await
37 }
38
39 #[zbus(property)]
40 async fn can_quit(&self) -> fdo::Result<bool> {
41 self.imp.can_quit().await
42 }
43
44 #[zbus(property)]
45 async fn fullscreen(&self) -> fdo::Result<bool> {
46 self.imp.fullscreen().await
47 }
48
49 #[zbus(property)]
50 async fn set_fullscreen(&self, fullscreen: bool) -> Result<()> {
51 self.imp.set_fullscreen(fullscreen).await
52 }
53
54 #[zbus(property)]
55 async fn can_set_fullscreen(&self) -> fdo::Result<bool> {
56 self.imp.can_set_fullscreen().await
57 }
58
59 #[zbus(property)]
60 async fn can_raise(&self) -> fdo::Result<bool> {
61 self.imp.can_raise().await
62 }
63
64 #[zbus(property)]
65 async fn has_track_list(&self) -> fdo::Result<bool> {
66 self.imp.has_track_list().await
67 }
68
69 #[zbus(property)]
70 async fn identity(&self) -> fdo::Result<String> {
71 self.imp.identity().await
72 }
73
74 #[zbus(property)]
75 async fn desktop_entry(&self) -> fdo::Result<String> {
76 self.imp.desktop_entry().await
77 }
78
79 #[zbus(property)]
80 async fn supported_uri_schemes(&self) -> fdo::Result<Vec<String>> {
81 self.imp.supported_uri_schemes().await
82 }
83
84 #[zbus(property)]
85 async fn supported_mime_types(&self) -> fdo::Result<Vec<String>> {
86 self.imp.supported_mime_types().await
87 }
88}
89
90struct RawPlayerInterface<T> {
91 imp: Arc<T>,
92}
93
94#[zbus::interface(name = "org.mpris.MediaPlayer2.Player")]
95impl<T> RawPlayerInterface<T>
96where
97 T: PlayerInterface + 'static,
98{
99 async fn next(&self) -> fdo::Result<()> {
100 self.imp.next().await
101 }
102
103 async fn previous(&self) -> fdo::Result<()> {
104 self.imp.previous().await
105 }
106
107 async fn pause(&self) -> fdo::Result<()> {
108 self.imp.pause().await
109 }
110
111 async fn play_pause(&self) -> fdo::Result<()> {
112 self.imp.play_pause().await
113 }
114
115 async fn stop(&self) -> fdo::Result<()> {
116 self.imp.stop().await
117 }
118
119 async fn play(&self) -> fdo::Result<()> {
120 self.imp.play().await
121 }
122
123 async fn seek(&self, offset: Time) -> fdo::Result<()> {
124 self.imp.seek(offset).await
125 }
126
127 async fn set_position(&self, track_id: TrackId, position: Time) -> fdo::Result<()> {
128 self.imp.set_position(track_id, position).await
129 }
130
131 async fn open_uri(&self, uri: String) -> fdo::Result<()> {
132 self.imp.open_uri(uri).await
133 }
134
135 #[zbus(signal)]
136 async fn seeked(ctxt: &SignalEmitter<'_>, position: Time) -> Result<()>;
137
138 #[zbus(property)]
139 async fn playback_status(&self) -> fdo::Result<PlaybackStatus> {
140 self.imp.playback_status().await
141 }
142
143 #[zbus(property)]
144 async fn loop_status(&self) -> fdo::Result<LoopStatus> {
145 self.imp.loop_status().await
146 }
147
148 #[zbus(property)]
149 async fn set_loop_status(&self, loop_status: LoopStatus) -> Result<()> {
150 self.imp.set_loop_status(loop_status).await
151 }
152
153 #[zbus(property)]
154 async fn rate(&self) -> fdo::Result<PlaybackRate> {
155 self.imp.rate().await
156 }
157
158 #[zbus(property)]
159 async fn set_rate(&self, rate: PlaybackRate) -> Result<()> {
160 self.imp.set_rate(rate).await
161 }
162
163 #[zbus(property)]
164 async fn shuffle(&self) -> fdo::Result<bool> {
165 self.imp.shuffle().await
166 }
167
168 #[zbus(property)]
169 async fn set_shuffle(&self, shuffle: bool) -> Result<()> {
170 self.imp.set_shuffle(shuffle).await
171 }
172
173 #[zbus(property)]
174 async fn metadata(&self) -> fdo::Result<Metadata> {
175 self.imp.metadata().await
176 }
177
178 #[zbus(property)]
179 async fn volume(&self) -> fdo::Result<Volume> {
180 self.imp.volume().await
181 }
182
183 #[zbus(property)]
184 async fn set_volume(&self, volume: Volume) -> Result<()> {
185 self.imp.set_volume(volume).await
186 }
187
188 #[zbus(property(emits_changed_signal = "false"))]
189 async fn position(&self) -> fdo::Result<Time> {
190 self.imp.position().await
191 }
192
193 #[zbus(property)]
194 async fn minimum_rate(&self) -> fdo::Result<PlaybackRate> {
195 self.imp.minimum_rate().await
196 }
197
198 #[zbus(property)]
199 async fn maximum_rate(&self) -> fdo::Result<PlaybackRate> {
200 self.imp.maximum_rate().await
201 }
202
203 #[zbus(property)]
204 async fn can_go_next(&self) -> fdo::Result<bool> {
205 self.imp.can_go_next().await
206 }
207
208 #[zbus(property)]
209 async fn can_go_previous(&self) -> fdo::Result<bool> {
210 self.imp.can_go_previous().await
211 }
212
213 #[zbus(property)]
214 async fn can_play(&self) -> fdo::Result<bool> {
215 self.imp.can_play().await
216 }
217
218 #[zbus(property)]
219 async fn can_pause(&self) -> fdo::Result<bool> {
220 self.imp.can_pause().await
221 }
222
223 #[zbus(property)]
224 async fn can_seek(&self) -> fdo::Result<bool> {
225 self.imp.can_seek().await
226 }
227
228 #[zbus(property(emits_changed_signal = "false"))]
230 async fn can_control(&self) -> fdo::Result<bool> {
231 self.imp.can_control().await
232 }
233}
234
235struct RawTrackListInterface<T> {
236 imp: Arc<T>,
237}
238
239#[zbus::interface(name = "org.mpris.MediaPlayer2.TrackList")]
240impl<T> RawTrackListInterface<T>
241where
242 T: TrackListInterface + 'static,
243{
244 async fn get_tracks_metadata(&self, track_ids: Vec<TrackId>) -> fdo::Result<Vec<Metadata>> {
245 self.imp.get_tracks_metadata(track_ids).await
246 }
247
248 async fn add_track(
249 &self,
250 uri: Uri,
251 after_track: TrackId,
252 set_as_current: bool,
253 ) -> fdo::Result<()> {
254 self.imp.add_track(uri, after_track, set_as_current).await
255 }
256
257 async fn remove_track(&self, track_id: TrackId) -> fdo::Result<()> {
258 self.imp.remove_track(track_id).await
259 }
260
261 async fn go_to(&self, track_id: TrackId) -> fdo::Result<()> {
262 self.imp.go_to(track_id).await
263 }
264
265 #[zbus(signal)]
266 async fn track_list_replaced(
267 ctxt: &SignalEmitter<'_>,
268 tracks: Vec<TrackId>,
269 current_track: TrackId,
270 ) -> Result<()>;
271
272 #[zbus(signal)]
273 async fn track_added(
274 ctxt: &SignalEmitter<'_>,
275 metadata: Metadata,
276 after_track: TrackId,
277 ) -> Result<()>;
278
279 #[zbus(signal)]
280 async fn track_removed(ctxt: &SignalEmitter<'_>, track_id: TrackId) -> Result<()>;
281
282 #[zbus(signal)]
283 async fn track_metadata_changed(
284 ctxt: &SignalEmitter<'_>,
285 track_id: TrackId,
286 metadata: Metadata,
287 ) -> Result<()>;
288
289 #[zbus(property(emits_changed_signal = "invalidates"))]
290 async fn tracks(&self) -> fdo::Result<Vec<TrackId>> {
291 self.imp.tracks().await
292 }
293
294 #[zbus(property)]
295 async fn can_edit_tracks(&self) -> fdo::Result<bool> {
296 self.imp.can_edit_tracks().await
297 }
298}
299
300struct RawPlaylistsInterface<T> {
301 imp: Arc<T>,
302}
303
304#[zbus::interface(name = "org.mpris.MediaPlayer2.Playlists")]
305impl<T> RawPlaylistsInterface<T>
306where
307 T: PlaylistsInterface + 'static,
308{
309 async fn activate_playlist(&self, playlist_id: PlaylistId) -> fdo::Result<()> {
310 self.imp.activate_playlist(playlist_id).await
311 }
312
313 async fn get_playlists(
314 &self,
315 index: u32,
316 max_count: u32,
317 order: PlaylistOrdering,
318 reverse_order: bool,
319 ) -> fdo::Result<Vec<Playlist>> {
320 self.imp
321 .get_playlists(index, max_count, order, reverse_order)
322 .await
323 }
324
325 #[zbus(signal)]
326 async fn playlist_changed(ctxt: &SignalEmitter<'_>, playlist: Playlist) -> Result<()>;
327
328 #[zbus(property)]
329 async fn playlist_count(&self) -> fdo::Result<u32> {
330 self.imp.playlist_count().await
331 }
332
333 #[zbus(property)]
334 async fn orderings(&self) -> fdo::Result<Vec<PlaylistOrdering>> {
335 self.imp.orderings().await
336 }
337
338 #[zbus(property)]
339 async fn active_playlist(&self) -> fdo::Result<MaybePlaylist> {
340 self.imp.active_playlist().await.map(MaybePlaylist::from)
341 }
342}
343
344pub struct Server<T> {
355 connection: Connection,
356 imp: Arc<T>,
357}
358
359impl<T> fmt::Debug for Server<T> {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 f.debug_struct("Server").finish()
362 }
363}
364
365macro_rules! insert_property {
366 ($item:ident, $property_type:ident => $($map:ident, $property:ident);*) => {
367 match $item {
368 $(
369 $property_type::$property(val) => {
370 $map.insert(stringify!($property), Value::new(val));
371 }
372 )*
373 }
374 };
375}
376
377impl<T> Server<T>
378where
379 T: PlayerInterface + 'static,
380{
381 pub async fn new(bus_name_suffix: &str, imp: T) -> Result<Self> {
398 Self::new_inner(bus_name_suffix, imp, |builder, _| Ok(builder)).await
399 }
400
401 #[inline]
403 pub fn imp(&self) -> &T {
404 &self.imp
405 }
406
407 #[cfg(feature = "unstable")]
411 #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
412 #[inline]
413 pub fn connection(&self) -> &Connection {
414 &self.connection
415 }
416
417 pub async fn emit(&self, signal: Signal) -> Result<()> {
419 match signal {
420 Signal::Seeked { position } => {
421 self.emit_inner::<RawPlayerInterface<T>>("Seeked", &(position,))
422 .await?;
423 }
424 }
425
426 Ok(())
427 }
428
429 pub async fn properties_changed(
439 &self,
440 properties: impl IntoIterator<Item = Property>,
441 ) -> Result<()> {
442 let mut root_changed = HashMap::new();
443 let mut player_changed = HashMap::new();
444
445 for property in properties.into_iter() {
446 insert_property!(
447 property, Property =>
448 root_changed, CanQuit;
449 root_changed, Fullscreen;
450 root_changed, CanSetFullscreen;
451 root_changed, CanRaise;
452 root_changed, HasTrackList;
453 root_changed, Identity;
454 root_changed, DesktopEntry;
455 root_changed, SupportedUriSchemes;
456 root_changed, SupportedMimeTypes;
457 player_changed, PlaybackStatus;
458 player_changed, LoopStatus;
459 player_changed, Rate;
460 player_changed, Shuffle;
461 player_changed, Metadata;
462 player_changed, Volume;
463 player_changed, MinimumRate;
464 player_changed, MaximumRate;
465 player_changed, CanGoNext;
466 player_changed, CanGoPrevious;
467 player_changed, CanPlay;
468 player_changed, CanPause;
469 player_changed, CanSeek
470 );
471 }
472
473 if !root_changed.is_empty() {
474 self.properties_changed_inner::<RawRootInterface<T>>(root_changed, &[])
475 .await?;
476 }
477
478 if !player_changed.is_empty() {
479 self.properties_changed_inner::<RawPlayerInterface<T>>(player_changed, &[])
480 .await?;
481 }
482
483 Ok(())
484 }
485
486 async fn new_inner(
487 bus_name_suffix: &str,
488 imp: T,
489 builder_ext_func: impl FnOnce(conn::Builder<'_>, Arc<T>) -> Result<conn::Builder<'_>>
490 + Send
491 + Sync
492 + 'static,
493 ) -> Result<Self> {
494 let imp = Arc::new(imp);
495
496 let connection_builder = conn::Builder::session()?
497 .name(format!("org.mpris.MediaPlayer2.{}", bus_name_suffix))?
498 .serve_at(
499 OBJECT_PATH,
500 RawRootInterface {
501 imp: Arc::clone(&imp),
502 },
503 )?
504 .serve_at(
505 OBJECT_PATH,
506 RawPlayerInterface {
507 imp: Arc::clone(&imp),
508 },
509 )?;
510 let connection = builder_ext_func(connection_builder, Arc::clone(&imp))?
511 .build()
512 .await?;
513
514 Ok(Self { connection, imp })
515 }
516
517 async fn properties_changed_inner<I>(
518 &self,
519 changed_properties: HashMap<&str, Value<'_>>,
520 invalidated_properties: &[&str],
521 ) -> Result<()>
522 where
523 I: Interface,
524 {
525 self.emit_inner::<fdo::Properties>(
526 "PropertiesChanged",
527 &(I::name(), changed_properties, invalidated_properties),
528 )
529 .await
530 }
531
532 async fn emit_inner<I>(
533 &self,
534 signal_name: &str,
535 body: &(impl Serialize + DynamicType),
536 ) -> Result<()>
537 where
538 I: Interface,
539 {
540 self.connection
541 .emit_signal(
542 None::<BusName<'_>>,
543 OBJECT_PATH,
544 I::name(),
545 signal_name,
546 body,
547 )
548 .await
549 }
550}
551
552impl<T> Server<T>
553where
554 T: TrackListInterface + 'static,
555{
556 pub async fn new_with_track_list(bus_name_suffix: &str, imp: T) -> Result<Self> {
562 Self::new_inner(bus_name_suffix, imp, |builder, imp| {
563 builder.serve_at(OBJECT_PATH, RawTrackListInterface { imp })
564 })
565 .await
566 }
567
568 pub async fn track_list_emit(&self, signal: TrackListSignal) -> Result<()> {
570 match signal {
571 TrackListSignal::TrackListReplaced {
572 tracks,
573 current_track,
574 } => {
575 self.emit_inner::<RawTrackListInterface<T>>(
576 "TrackListReplaced",
577 &(tracks, current_track),
578 )
579 .await?;
580 }
581 TrackListSignal::TrackAdded {
582 metadata,
583 after_track,
584 } => {
585 self.emit_inner::<RawTrackListInterface<T>>("TrackAdded", &(metadata, after_track))
586 .await?;
587 }
588 TrackListSignal::TrackRemoved { track_id } => {
589 self.emit_inner::<RawTrackListInterface<T>>("TrackRemoved", &(track_id,))
590 .await?;
591 }
592 TrackListSignal::TrackMetadataChanged { track_id, metadata } => {
593 self.emit_inner::<RawTrackListInterface<T>>(
594 "TrackMetadataChanged",
595 &(track_id, metadata),
596 )
597 .await?;
598 }
599 }
600
601 Ok(())
602 }
603
604 pub async fn track_list_properties_changed(
609 &self,
610 properties: impl IntoIterator<Item = TrackListProperty>,
611 ) -> Result<()> {
612 let mut changed = HashMap::new();
613 let mut invalidated = Vec::new();
614
615 for property in properties.into_iter() {
616 match property {
617 TrackListProperty::Tracks => {
618 invalidated.push("Tracks");
619 }
620 TrackListProperty::CanEditTracks(can_edit_tracks) => {
621 changed.insert("CanEditTracks", Value::new(can_edit_tracks));
622 }
623 }
624 }
625
626 if !changed.is_empty() || !invalidated.is_empty() {
627 self.properties_changed_inner::<RawTrackListInterface<T>>(changed, &invalidated)
628 .await?;
629 }
630
631 Ok(())
632 }
633}
634
635impl<T> Server<T>
636where
637 T: PlaylistsInterface + 'static,
638{
639 pub async fn new_with_playlists(bus_name_suffix: &str, imp: T) -> Result<Self> {
645 Self::new_inner(bus_name_suffix, imp, |builder, imp| {
646 builder.serve_at(OBJECT_PATH, RawPlaylistsInterface { imp })
647 })
648 .await
649 }
650
651 pub async fn playlists_emit(&self, signal: PlaylistsSignal) -> Result<()> {
653 match signal {
654 PlaylistsSignal::PlaylistChanged { playlist } => {
655 self.emit_inner::<RawPlaylistsInterface<T>>("PlaylistChanged", &(playlist,))
656 .await?;
657 }
658 }
659
660 Ok(())
661 }
662
663 pub async fn playlists_properties_changed(
668 &self,
669 properties: impl IntoIterator<Item = PlaylistsProperty>,
670 ) -> Result<()> {
671 let mut changed = HashMap::new();
672
673 for property in properties.into_iter() {
674 match property {
675 PlaylistsProperty::PlaylistCount(playlist_count) => {
676 changed.insert("PlaylistCount", Value::new(playlist_count));
677 }
678 PlaylistsProperty::Orderings(orderings) => {
679 changed.insert("Orderings", Value::new(orderings));
680 }
681 PlaylistsProperty::ActivePlaylist(active_playlist) => {
682 changed.insert(
683 "ActivePlaylist",
684 Value::new(MaybePlaylist::from(active_playlist)),
685 );
686 }
687 }
688 }
689
690 if !changed.is_empty() {
691 self.properties_changed_inner::<RawPlaylistsInterface<T>>(changed, &[])
692 .await?;
693 }
694
695 Ok(())
696 }
697}
698
699impl<T> Server<T>
700where
701 T: TrackListInterface + PlaylistsInterface + 'static,
702{
703 pub async fn new_with_all(bus_name_suffix: &str, imp: T) -> Result<Self> {
710 Self::new_inner(bus_name_suffix, imp, |builder, imp| {
711 builder
712 .serve_at(
713 OBJECT_PATH,
714 RawTrackListInterface {
715 imp: Arc::clone(&imp),
716 },
717 )?
718 .serve_at(OBJECT_PATH, RawPlaylistsInterface { imp })
719 })
720 .await
721 }
722}
723
724#[cfg(test)]
725mod tests {
726 use static_assertions::assert_impl_all;
727
728 use super::Server;
729
730 #[allow(dead_code)]
731 pub struct TestPlayer;
732
733 assert_impl_all!(Server<TestPlayer>: Send, Sync, Unpin);
734}