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