Skip to main content

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}