From 2ef67be5c6d020e08a8277353221edbccc0d6184 Mon Sep 17 00:00:00 2001 From: Kubat <mael.martin31@gmail.com> Date: Sat, 4 Feb 2023 16:52:43 +0100 Subject: [PATCH] MPRIS: Add the default implementations for the MPRIS server --- src/rust/mpris/src/error.rs | 12 ++ src/rust/mpris/src/lib.rs | 1 + src/rust/mpris/src/server.rs | 242 +++++++++++++++++++---------------- src/rust/mpris/src/types.rs | 81 ++++++++++-- 4 files changed, 219 insertions(+), 117 deletions(-) create mode 100644 src/rust/mpris/src/error.rs diff --git a/src/rust/mpris/src/error.rs b/src/rust/mpris/src/error.rs new file mode 100644 index 00000000..cb9f10ed --- /dev/null +++ b/src/rust/mpris/src/error.rs @@ -0,0 +1,12 @@ +use zbus::DBusError; + +#[derive(DBusError, Debug)] +#[dbus_error(prefix = "org.mpris.MediaPlayer2")] +pub enum Error { + #[dbus_error(zbus_error)] + ZBus(zbus::Error), + + NoFile(String), +} + +pub type MPRISResult<T> = std::result::Result<T, Error>; diff --git a/src/rust/mpris/src/lib.rs b/src/rust/mpris/src/lib.rs index e728031c..5449773c 100644 --- a/src/rust/mpris/src/lib.rs +++ b/src/rust/mpris/src/lib.rs @@ -1,2 +1,3 @@ +pub mod error; pub mod server; pub mod types; diff --git a/src/rust/mpris/src/server.rs b/src/rust/mpris/src/server.rs index 993d31ca..93be0933 100644 --- a/src/rust/mpris/src/server.rs +++ b/src/rust/mpris/src/server.rs @@ -1,18 +1,26 @@ //! # DBus interface for: `org.mpris.MediaPlayer2`, `org.mpris.MediaPlayer2.Player` // use crate::types::*; +use crate::types::*; +use commons::log; use zbus::dbus_interface; +/// Should be served at "/org/mpris/MediaPlayer2" pub struct MediaPlayer2 { identity: String, } +/// Should be served at "/org/mpris/MediaPlayer2" +pub struct MediaPlayer2Player {} + #[dbus_interface(name = "org.mpris.MediaPlayer2")] impl MediaPlayer2 { /// Quit method + #[dbus_interface(name = "Quit")] fn quit(&self) {} /// Raise method + #[dbus_interface(name = "Raise")] fn raise(&self) {} /// CanQuit property @@ -45,7 +53,7 @@ impl MediaPlayer2 { false } #[dbus_interface(property, name = "Fullscreen")] - fn set_fullscreen(&mut self, _value: bool) {} + fn set_fullscreen(&self, _value: bool) {} /// HasTrackList property #[dbus_interface(property, name = "HasTrackList")] @@ -75,110 +83,128 @@ impl MediaPlayer2 { } } -// #[dbus_interface( -// interface = "org.mpris.MediaPlayer2.Player", -// default_path = "/org/mpris/MediaPlayer2", -// assume_defaults = true -// )] -// trait Player { -// /// Next method -// fn next(&self) -> zbus::Result<()>; -// -// /// OpenUri method -// fn open_uri(&self, id_uri: &str) -> zbus::Result<()>; -// -// /// Pause method -// fn pause(&self) -> zbus::Result<()>; -// -// /// Play method -// fn play(&self) -> zbus::Result<()>; -// -// /// PlayPause method -// fn play_pause(&self) -> zbus::Result<()>; -// -// /// Previous method -// fn previous(&self) -> zbus::Result<()>; -// -// /// Seek method -// fn seek(&self, time: TimeMicroSec) -> zbus::Result<()>; -// -// /// SetPosition method -// fn set_position( -// &self, -// track_id: &MediaPlayerObjectPath, -// time: TimeMicroSec, -// ) -> zbus::Result<()>; -// -// /// Stop method -// fn stop(&self) -> zbus::Result<()>; -// -// /// CanControl property -// #[dbus_interface(property)] -// fn can_control(&self) -> zbus::Result<bool>; -// -// /// CanPause property -// #[dbus_interface(property)] -// fn can_pause(&self) -> zbus::Result<bool>; -// -// /// CanPlay property -// #[dbus_interface(property)] -// fn can_play(&self) -> zbus::Result<bool>; -// -// /// CanSeek property -// #[dbus_interface(property)] -// fn can_seek(&self) -> zbus::Result<bool>; -// -// /// LoopStatus property -// #[dbus_interface(property)] -// fn loop_status(&self) -> zbus::Result<MediaPlayerObjectPath>; -// fn set_loop_status(&self, value: &MediaPlayerObjectPath) -> zbus::Result<()>; -// -// /// MaximumRate property. We will only set it to `1.0` here... -// #[dbus_interface(property)] -// fn maximum_rate(&self) -> zbus::Result<f64> { -// Ok(1.0) -// } -// fn set_maximum_rate(&self, _: f64) -> zbus::Result<()> { -// Ok(()) -// } -// -// /// Metadata property. If there is a current kara playing, there must be at -// /// least a `mpris:trackid` value of type `o` which is the object path of -// /// the current kara id. -// #[dbus_interface(property)] -// fn metadata( -// &self, -// ) -> zbus::Result<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>; -// -// /// MinimumRate property. We will only set it to `1.0` here... -// #[dbus_interface(property)] -// fn minimum_rate(&self) -> zbus::Result<f64> { -// Ok(1.0) -// } -// fn set_minimum_rate(&self, _: f64) -> zbus::Result<()> { -// Ok(()) -// } -// -// /// PlaybackStatus property -// #[dbus_interface(property)] -// fn playback_status(&self) -> zbus::Result<MediaPlayerPlaybackStatus>; -// -// /// Position property -// #[dbus_interface(property)] -// fn position(&self) -> zbus::Result<TimeMicroSec>; -// -// /// Rate property -// #[dbus_interface(property)] -// fn rate(&self) -> zbus::Result<f64>; -// fn set_rate(&self, value: f64) -> zbus::Result<()>; -// -// /// Shuffle property -// #[dbus_interface(property)] -// fn shuffle(&self) -> zbus::Result<bool>; -// fn set_shuffle(&self, value: bool) -> zbus::Result<()>; -// -// /// Volume property -// #[dbus_interface(property)] -// fn volume(&self) -> zbus::Result<f64>; -// fn set_volume(&self, value: f64) -> zbus::Result<()>; -// } +#[dbus_interface(name = "org.mpris.MediaPlayer2.Player")] +impl MediaPlayer2Player { + /// Next method + fn next(&self) {} + + /// Pause method + fn pause(&self) { + todo!() + } + + /// Play method + fn play(&self) { + todo!() + } + + /// OpenUri method + fn open_uri(&self, uri: &str) { + todo!("handle open uri {uri}") + } + + fn play_pause(&self) { + todo!() + } + + /// Previous method + fn previous(&self) { + todo!() + } + + /// Seek method + fn seek(&self, offset: TimeMicroSec) { + todo!("handle seek offset {offset:?}") + } + + /// SetPosition method + fn set_position(&self, track_id: MediaPlayerObjectPath, position: TimeMicroSec) { + todo!("handle set position {track_id:?} {position:?}") + } + + /// Stop method + fn stop(&self) { + todo!() + } + + /// CanControl property + #[dbus_interface(property)] + fn can_control(&self) -> bool { + true + } + + /// CanPause property + #[dbus_interface(property)] + fn can_pause(&self) -> bool { + todo!() + } + + /// CanPlay property + #[dbus_interface(property)] + fn can_play(&self) -> bool { + todo!() + } + + /// CanSeek property + #[dbus_interface(property)] + fn can_seek(&self) -> bool { + false + } + + /// LoopStatus property + #[dbus_interface(property, name = "LoopStatus")] + fn loop_status(&self) -> MediaPlayerObjectPath { + todo!() + } + #[dbus_interface(property, name = "LoopStatus")] + fn set_loop_status(&self, loop_status: MediaPlayerObjectPath) { + todo!("handle loop status {loop_status:?}") + } + + #[rustfmt::skip] #[dbus_interface(property, name = "MaximumRate")] fn maximum_rate(&self) -> f64 { 1.0 } + #[rustfmt::skip] #[dbus_interface(property, name = "MinimumRate")] fn minimum_rate(&self) -> f64 { 1.0 } + #[rustfmt::skip] #[dbus_interface(property, name = "MaximumRate")] fn set_maximum_rate(&self, rate: f64) { log::warn!("ignore rate {rate}") } + #[rustfmt::skip] #[dbus_interface(property, name = "MinimumRate")] fn set_minimum_rate(&self, rate: f64) { log::warn!("ignore rate {rate}") } + #[rustfmt::skip] #[dbus_interface(property, name = "Rate" )] fn rate(&self) -> f64 { 1.0 } + #[rustfmt::skip] #[dbus_interface(property, name = "Rate" )] fn set_rate(&self, rate: f64) { log::warn!("ignore rate {rate}") } + + /// Shuffle property + #[dbus_interface(property, name = "Shuffle")] + fn shuffle(&self) -> bool { + todo!() + } + #[dbus_interface(property, name = "Shuffle")] + fn set_shuffle(&self, shuffle: bool) { + todo!("handle shuffle {shuffle}") + } + + /// Volume property + #[dbus_interface(property, name = "Volume")] + fn volume(&self) -> f64 { + todo!() + } + #[dbus_interface(property, name = "Volume")] + fn set_volume(&self, volume: f64) { + todo!("handle volume {volume}") + } + + /// PlaybackStatus property + #[dbus_interface(property, name = "PlaybackStatus")] + fn playback_status(&self) -> MediaPlayerPlaybackStatus { + todo!() + } + + /// Position property + #[dbus_interface(property, name = "Position")] + fn position(&self) -> TimeMicroSec { + todo!() + } + + /// Metadata property. If there is a current kara playing, there must be at + /// least a `mpris:trackid` value of type `o` which is the object path of + /// the current kara id. + #[dbus_interface(property, name = "Metadata")] + fn metadata(&self) -> MediaPlayerMetadata { + todo!() + } +} diff --git a/src/rust/mpris/src/types.rs b/src/rust/mpris/src/types.rs index 07af4a2b..7e017d11 100644 --- a/src/rust/mpris/src/types.rs +++ b/src/rust/mpris/src/types.rs @@ -1,5 +1,6 @@ +use hashbrown::HashMap; use serde::{Deserialize, Serialize}; -use zbus::zvariant::{ObjectPath as ZObjectPath, OwnedValue as ZOwnedValue, Type as ZType}; +use zbus::zvariant::{Dict as ZDict, ObjectPath as ZObjectPath, Type as ZType, Value as ZValue}; /// The loop status. #[derive(Debug, Deserialize, Serialize, ZType, Clone, Copy, PartialEq, Eq)] @@ -25,6 +26,10 @@ pub enum MediaPlayerPlaybackStatus { #[zvariant(signature = "x")] pub struct TimeMicroSec(i64); +#[derive(Debug, Serialize, Deserialize, ZType, Clone, PartialEq, Eq)] +#[zvariant(signature = "a{sv}")] +pub struct MediaPlayerMetadata(HashMap<String, String>); + /// A valid path must be prefiexed by `/org/mpris/MediaPlayer2`. After that we /// have the variant name, then a `/`, then the ascii only value. For example /// we can have: @@ -53,15 +58,15 @@ impl From<i64> for TimeMicroSec { } } -impl From<ZOwnedValue> for TimeMicroSec { - fn from(value: ZOwnedValue) -> Self { +impl From<ZValue<'_>> for TimeMicroSec { + fn from(value: ZValue<'_>) -> Self { let value: i64 = value.try_into().unwrap_or_default(); Self(value) } } -impl From<ZOwnedValue> for MediaPlayerLoopStatus { - fn from(value: ZOwnedValue) -> Self { +impl From<ZValue<'_>> for MediaPlayerLoopStatus { + fn from(value: ZValue<'_>) -> Self { let value: String = value.try_into().unwrap_or_default(); match &value[..] { "None" => Self::None, @@ -72,8 +77,8 @@ impl From<ZOwnedValue> for MediaPlayerLoopStatus { } } -impl From<ZOwnedValue> for MediaPlayerPlaybackStatus { - fn from(value: ZOwnedValue) -> Self { +impl From<ZValue<'_>> for MediaPlayerPlaybackStatus { + fn from(value: ZValue<'_>) -> Self { let value: String = value.try_into().unwrap_or_default(); match &value[..] { "Paused" => Self::Paused, @@ -84,8 +89,8 @@ impl From<ZOwnedValue> for MediaPlayerPlaybackStatus { } } -impl<'a> From<ZOwnedValue> for MediaPlayerObjectPath { - fn from(value: ZOwnedValue) -> Self { +impl From<ZValue<'_>> for MediaPlayerObjectPath { + fn from(value: ZValue<'_>) -> Self { let value: ZObjectPath = value.try_into().unwrap_or_default(); let value = value.trim_start_matches('/'); match value.split('/').collect::<Vec<_>>()[..] { @@ -98,3 +103,61 @@ impl<'a> From<ZOwnedValue> for MediaPlayerObjectPath { } } } + +impl From<MediaPlayerObjectPath> for ZValue<'_> { + fn from(value: MediaPlayerObjectPath) -> Self { + use MediaPlayerObjectPath::*; + ZValue::ObjectPath( + match value { + None => ZObjectPath::try_from("/org/mpris/MediaPlayer2/None"), + NoTrack => ZObjectPath::try_from("/org/mpris/MediaPlayer2/TrackList/NoTrack"), + Id(id) => ZObjectPath::try_from(format!("/org/mpris/MediaPlayer2/Id/{id}")), + } + .expect("incorrect serialization"), + ) + } +} + +impl From<MediaPlayerLoopStatus> for ZValue<'_> { + fn from(value: MediaPlayerLoopStatus) -> Self { + use MediaPlayerLoopStatus::*; + match value { + None => "None", + Track => "Track", + Playlist => "Playlist", + } + .into() + } +} + +impl From<MediaPlayerPlaybackStatus> for ZValue<'_> { + fn from(value: MediaPlayerPlaybackStatus) -> Self { + use MediaPlayerPlaybackStatus::*; + match value { + Paused => "Paused", + Playing => "Playing", + Stopped => "Stopped", + } + .into() + } +} + +impl From<TimeMicroSec> for ZValue<'_> { + fn from(TimeMicroSec(value): TimeMicroSec) -> Self { + value.into() + } +} + +impl From<MediaPlayerMetadata> for ZValue<'_> { + fn from(MediaPlayerMetadata(value): MediaPlayerMetadata) -> Self { + let mut dict = ZDict::new( + "s".try_into().expect("invalid signature"), + "v".try_into().expect("invalid signature"), + ); + for (key, value) in value { + dict.append(key.into(), value.into()) + .expect("invalid entry found") + } + todo!() + } +} -- GitLab