mecomp_mpris/interfaces/root.rs
1//! Implements the root interface of the MPRIS specification.
2//!
3//! [org.mpris.MediaPlayer2](https://specifications.freedesktop.org/mpris-spec/latest/Media_Player.html)
4
5use futures::executor::block_on;
6use mpris_server::{
7 RootInterface,
8 zbus::{Error as ZbusError, fdo},
9};
10
11use crate::Mpris;
12
13pub const SUPPORTED_MIME_TYPES: [&str; 4] = ["audio/mp3", "audio/wav", "audio/ogg", "audio/flac"];
14
15impl RootInterface for Mpris {
16 async fn raise(&self) -> fdo::Result<()> {
17 Ok(())
18 }
19
20 async fn quit(&self) -> fdo::Result<()> {
21 block_on(self.daemon.clone().daemon_shutdown(()))
22 .map_err(|e| fdo::Error::Failed(e.to_string()))?;
23 Ok(())
24 }
25
26 async fn can_quit(&self) -> fdo::Result<bool> {
27 Ok(true)
28 }
29
30 async fn fullscreen(&self) -> fdo::Result<bool> {
31 Ok(false)
32 }
33
34 async fn set_fullscreen(&self, _: bool) -> Result<(), ZbusError> {
35 Err(ZbusError::Unsupported)
36 }
37
38 async fn can_set_fullscreen(&self) -> fdo::Result<bool> {
39 Ok(false)
40 }
41
42 async fn can_raise(&self) -> fdo::Result<bool> {
43 // TODO: Maybe in the future we can implement raising the player by starting the GUI.
44 Ok(false)
45 }
46
47 async fn has_track_list(&self) -> fdo::Result<bool> {
48 // TODO: when we implement the track list interface, we should return true here.
49 Ok(false)
50 }
51
52 async fn identity(&self) -> fdo::Result<String> {
53 Ok("MECOMP Music Player".to_string())
54 }
55
56 async fn desktop_entry(&self) -> fdo::Result<String> {
57 // TODO: bundle a desktop entry with the application so we can support this
58 Err(fdo::Error::Failed("Desktop entry not found".to_string()))
59 }
60
61 async fn supported_uri_schemes(&self) -> fdo::Result<Vec<String>> {
62 Ok(vec!["file".to_string()])
63 }
64
65 async fn supported_mime_types(&self) -> fdo::Result<Vec<String>> {
66 Ok(SUPPORTED_MIME_TYPES
67 .iter()
68 .map(ToString::to_string)
69 .collect())
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 use crate::test_utils::fixtures;
78 use pretty_assertions::assert_eq;
79
80 #[tokio::test]
81 /// """
82 /// The media player may be unable to control how its user interface is displayed, or it may not have a graphical user interface at all.
83 /// In this case, the [CanRaise] property is false and this method does nothing.
84 /// """
85 ///
86 /// Mecomp does not have a graphical user interface, so it does not support raising.
87 async fn raise() {
88 let (mpris, _, _, _) = fixtures().await;
89 let result = mpris.can_raise().await;
90 assert_eq!(result, Ok(false));
91 let result = mpris.raise().await;
92 assert_eq!(result, Ok(()));
93 }
94
95 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
96 /// """
97 /// The media player may refuse to allow clients to shut it down. In this case, the [CanQuit] property is false and this method does nothing.
98 /// Note: Media players which can be D-Bus activated, or for which there is no sensibly easy way to terminate a running instance
99 /// (via the main interface or a notification area icon for example) should allow clients to use this method. Otherwise, it should not be needed.
100 /// """
101 ///
102 /// Mecomp allows clients to shut it down.
103 async fn quit() {
104 let (mpris, _, _, _) = fixtures().await;
105 let result = mpris.can_quit().await;
106 assert_eq!(result, Ok(true));
107 // this is safe to do since there is no daemon running.
108 let result = mpris.quit().await;
109 assert_eq!(result, Ok(()));
110 }
111
112 #[tokio::test]
113 /// """
114 /// If false, attempting to set [Fullscreen] will have no effect, and may raise an error.
115 /// If true, attempting to set [Fullscreen] will not raise an error, and (if it is different from the current value)
116 /// will cause the media player to attempt to enter or exit fullscreen mode.
117 ///
118 /// Note that the media player may be unable to fulfil the request. In this case, the value will not change.
119 /// If the media player knows in advance that it will not be able to fulfil the request, however, this property should be false.
120 /// """
121 /// Mecomp does not support fullscreen mode.
122 async fn fullscreen() {
123 let (mpris, _, _, _) = fixtures().await;
124 let result = mpris.fullscreen().await;
125 assert_eq!(result, Ok(false));
126 let result = mpris.can_set_fullscreen().await;
127 assert_eq!(result, Ok(false));
128 let result = mpris.set_fullscreen(true).await;
129 assert_eq!(result, Err(ZbusError::Unsupported));
130 }
131
132 #[tokio::test]
133 /// """
134 /// Indicates whether the /org/mpris/MediaPlayer2 object implements the [TrackList interface].
135 /// """
136 ///
137 /// Mecomp currently does not implement the TrackList interface.
138 async fn has_track_list() {
139 let (mpris, _, _, _) = fixtures().await;
140 let result = mpris.has_track_list().await;
141 assert_eq!(result, Ok(false));
142 }
143
144 #[tokio::test]
145 /// """
146 /// A friendly name to identify the media player to users (eg: "VLC media player").
147 /// """
148 ///
149 /// Mecomp identifies itself as "MECOMP Music Player".
150 async fn identity() {
151 let (mpris, _, _, _) = fixtures().await;
152 let result = mpris.identity().await;
153 assert_eq!(result, Ok("MECOMP Music Player".to_string()));
154 }
155
156 #[tokio::test]
157 /// """
158 /// The desktop entry file as described in the [Desktop Entry Specification](https://specifications.freedesktop.org/desktop-entry-spec/latest/).
159 /// """
160 ///
161 /// Mecomp currently doesn't have a desktop app, so it does not make sense to return a desktop entry.
162 /// TODO: Once I've implemented the GUI, it should ship with a desktop entry that can be returned here.
163 async fn desktop_entry() {
164 let (mpris, _, _, _) = fixtures().await;
165 let result = mpris.desktop_entry().await;
166 assert_eq!(
167 result,
168 Err(fdo::Error::Failed("Desktop entry not found".to_string()))
169 );
170 }
171
172 #[tokio::test]
173 /// """
174 /// The URI schemes supported by the media player.
175 /// """
176 ///
177 /// Mecomp can only play files from the local filesystem, so it supports the "file" URI scheme.
178 async fn supported_uri_schemes() {
179 let (mpris, _, _, _) = fixtures().await;
180 let result = mpris.supported_uri_schemes().await;
181 assert_eq!(result, Ok(vec!["file".to_string()]));
182 }
183
184 #[tokio::test]
185 /// """
186 /// The mime-types supported by the media player.
187 /// """
188 ///
189 /// Mecomp can play anything that it can decode, so mime-types supported by both the [lofty-rs](https://crates.io/crates/lofty) and [rodio](https://0crates.io/crates/rodio) crates are supported.
190 /// So, mp3, wav, ogg (vorbis), and flac
191 async fn supported_mime_types() {
192 let (mpris, _, _, _) = fixtures().await;
193 let result = mpris.supported_mime_types().await;
194 assert_eq!(
195 result,
196 Ok(SUPPORTED_MIME_TYPES
197 .iter()
198 .map(ToString::to_string)
199 .collect())
200 );
201 }
202}