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