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    RootInterface,
7    zbus::{Error as ZbusError, fdo},
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}