Skip to content

Commit

Permalink
Added now-playing command.
Browse files Browse the repository at this point in the history
  • Loading branch information
ray-kast committed Nov 14, 2021
1 parent c51f834 commit 06ed240
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 44 deletions.
47 changes: 46 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "empress"
version = "1.3.0"
version = "1.4.0"
authors = ["rookie1024 <rookie1286@gmail.com>"]
edition = "2018"
edition = "2021"
description = "A D-Bus MPRIS daemon for controlling media players."
documentation = "https://docs.rs/empress"
readme = "README.md"
Expand All @@ -22,5 +22,7 @@ futures = "0.3.13"
lazy_static = "1.4.0"
log = "0.4.14"
regex = "1.4.4"
serde = { version = "1.0.130", features = ["derive"] }
serde_json = "1.0.70"
structopt = "0.3.21"
tokio = { version = "1.2.0", features = ["macros", "rt", "signal", "sync"] }
53 changes: 50 additions & 3 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,54 @@
use std::{sync::Arc, time::Duration};
use std::{collections::HashMap, io, sync::Arc, time::Duration};

use anyhow::{Context, Error};
use dbus::{
arg::{AppendAll, ReadAll},
arg::{AppendAll, ReadAll, RefArg, Variant},
nonblock::{Proxy, SyncConnection},
};
use dbus_tokio::connection;
use log::{info, warn};
use serde::Serialize;
use tokio::{select, sync::oneshot, task};

use crate::{
ClientCommand, MethodId, PlayerOpts, Result, INTERFACE_NAME, SERVER_NAME, SERVER_PATH,
server::mpris, ClientCommand, MethodId, PlayerOpts, Result, INTERFACE_NAME, SERVER_NAME,
SERVER_PATH,
};

#[derive(Debug, Clone, Serialize)]
struct NowPlayingResult {
title: Option<String>,
artist: Option<Vec<String>>,
album: Option<String>,
}

impl TryFrom<HashMap<String, Variant<Box<dyn RefArg>>>> for NowPlayingResult {
type Error = Error;

fn try_from(mut map: HashMap<String, Variant<Box<dyn RefArg>>>) -> Result<Self> {
let title = map
.remove(mpris::track_list::ATTR_TITLE)
.and_then(|Variant(v)| v.as_str().map(ToOwned::to_owned));
let artist = map
.remove(mpris::track_list::ATTR_ARTIST)
.and_then(|Variant(v)| {
v.as_iter().map(|i| {
i.filter_map(|v| v.as_str().map(ToOwned::to_owned))
.collect::<Vec<_>>()
})
});
let album = map
.remove(mpris::track_list::ATTR_ALBUM)
.and_then(|Variant(v)| v.as_str().map(ToOwned::to_owned));

Ok(Self {
title,
artist,
album,
})
}
}

pub(super) async fn run(cmd: ClientCommand) -> Result {
let (res, conn) = connection::new_session_sync().context("failed to connect to D-Bus")?;
let (close_tx, close_rx) = oneshot::channel();
Expand Down Expand Up @@ -45,6 +81,17 @@ pub(super) async fn run(cmd: ClientCommand) -> Result {
println!("{}\t{}", player, status);
}
},
ClientCommand::NowPlaying(opts) => {
let PlayerOpts {} = opts;
let (map,): (HashMap<String, Variant<Box<dyn RefArg>>>,) =
try_send(&proxy, id, ()).await?;

serde_json::to_writer(io::stdout(), &NowPlayingResult::try_from(map)?)?;

if atty::is(atty::Stream::Stdout) {
println!();
}
},
ClientCommand::Seek {
player: PlayerOpts {},
to,
Expand Down
15 changes: 13 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#![warn(missing_docs, clippy::all, clippy::pedantic, clippy::cargo)]
#![deny(missing_debug_implementations)]
#![deny(
clippy::suspicious,
clippy::style,
missing_debug_implementations,
missing_copy_implementations
)]
#![warn(clippy::pedantic, clippy::cargo, missing_docs)]

//! Binary crate for `empress`. See [the
//! README](https://github.com/ray-kast/empress/blob/master/README.md) for more
Expand Down Expand Up @@ -46,6 +51,8 @@ enum MethodId {
ListPlayers,
/// Skip one track forwards
Next,
/// Print information about the current track
NowPlaying,
/// Skip one track backwards
Previous,
/// Pause a currently-playing player
Expand All @@ -70,6 +77,7 @@ impl Display for MethodId {
f.write_str(match self {
Self::ListPlayers => "ListPlayers",
Self::Next => "Next",
Self::NowPlaying => "NowPlaying",
Self::Previous => "Previous",
Self::Pause => "Pause",
Self::PlayPause => "PlayPause",
Expand Down Expand Up @@ -114,6 +122,8 @@ enum ClientCommand {
ListPlayers,
/// Skip one track forwards
Next(PlayerOpts),
/// Print information about the current track
NowPlaying(PlayerOpts),
/// Skip one track backwards
Previous(PlayerOpts),
/// Pause a currently-playing player
Expand Down Expand Up @@ -150,6 +160,7 @@ impl ClientCommand {
match self {
Self::ListPlayers => MethodId::ListPlayers,
Self::Next(..) => MethodId::Next,
Self::NowPlaying { .. } => MethodId::NowPlaying,
Self::Previous(..) => MethodId::Previous,
Self::Pause(..) => MethodId::Pause,
Self::PlayPause(..) => MethodId::PlayPause,
Expand Down
9 changes: 8 additions & 1 deletion src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use tokio::{

use crate::{MethodId, PlayerOpts, Result, INTERFACE_NAME, SERVER_NAME, SERVER_PATH};

mod mpris;
pub mod mpris;
mod player;
mod player_map;
#[allow(clippy::module_inception)] // I'm aware, but the struct is called Server
Expand Down Expand Up @@ -73,6 +73,13 @@ fn register_interface(b: &mut IfaceBuilder<Arc<Server>>) {
|ctx, cr, ()| handle(ctx, cr, |serv| async move { serv.list_players().await }),
);

b.method_with_cr_async(
MethodId::NowPlaying.to_string(),
(),
("info",),
|ctx, cr, ()| handle(ctx, cr, |serv| async move { serv.now_playing().await }),
);

b.method_with_cr_async(MethodId::Next.to_string(), (), (), |ctx, cr, ()| {
handle(
ctx,
Expand Down
20 changes: 16 additions & 4 deletions src/server/mpris.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ use dbus::{
use lazy_static::lazy_static;

lazy_static! {
pub static ref BUS_NAME: BusName<'static> = "org.mpris.MediaPlayer2".into();
pub static ref ENTRY_PATH: Path<'static> = "/org/mpris/MediaPlayer2".into();
pub static ref NAME_PREFIX: String = "org.mpris.MediaPlayer2".into();
pub static ref PATH_PREFIX: String = "/org/mpris/MediaPlayer2".into();
pub static ref BUS_NAME: BusName<'static> = (&*NAME_PREFIX).into();
pub static ref ENTRY_PATH: Path<'static> = (&*PATH_PREFIX).into();
}

pub mod player {
use super::{lazy_static, Interface, Member};
use super::{lazy_static, Interface, Member, NAME_PREFIX};
use crate::Result;

lazy_static! {
pub static ref INTERFACE: Interface<'static> = "org.mpris.MediaPlayer2.Player".into();
pub static ref INTERFACE: Interface<'static> = format!("{}.Player", *NAME_PREFIX).into();
pub static ref NEXT: Member<'static> = "Next".into();
pub static ref PREVIOUS: Member<'static> = "Previous".into();
pub static ref PAUSE: Member<'static> = "Pause".into();
Expand Down Expand Up @@ -64,5 +66,15 @@ pub mod player {
}

pub mod track_list {
use super::{lazy_static, Path};

pub const ATTR_TRACKID: &str = "mpris:trackid";
pub const ATTR_TITLE: &str = "xesam:title";
pub const ATTR_ARTIST: &str = "xesam:artist";
pub const ATTR_ALBUM: &str = "xesam:album";

lazy_static! {
pub static ref PATH_PREFIX: String = format!("{}/TrackList", *super::PATH_PREFIX);
pub static ref NO_TRACK: Path<'static> = format!("{}/NoTrack", *PATH_PREFIX).into();
}
}
50 changes: 24 additions & 26 deletions src/server/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,34 +294,32 @@ impl Player {
conn: &SyncConnection,
to: SeekPosition,
) -> Result<Option<(Self, f64)>> {
Ok(
if self.can_seek(conn).await? {
let meta = self.metadata(conn).await?;

let pos = match to {
SeekPosition::Relative(p) => self.position(conn).await? + p,
SeekPosition::Absolute(p) => p,
};

Some((
self.set_position(
conn,
Path::new(
meta.get(mpris::track_list::ATTR_TRACKID)
.ok_or_else(|| anyhow!("missing track ID in metadata"))?
.as_str()
.ok_or_else(|| anyhow!("track ID wasn't a string"))?,
)
.map_err(|s| anyhow!("track ID {:?} was not valid", s))?,
pos,
Ok(if self.can_seek(conn).await? {
let meta = self.metadata(conn).await?;

let pos = match to {
SeekPosition::Relative(p) => self.position(conn).await? + p,
SeekPosition::Absolute(p) => p,
};

Some((
self.set_position(
conn,
Path::new(
meta.get(mpris::track_list::ATTR_TRACKID)
.ok_or_else(|| anyhow!("missing track ID in metadata"))?
.as_str()
.ok_or_else(|| anyhow!("track ID wasn't a string"))?,
)
.await?,
.map_err(|s| anyhow!("track ID {:?} was not valid", s))?,
pos,
))
} else {
None
},
)
)
.await?,
pos,
))
} else {
None
})
}
}

Expand Down
Loading

0 comments on commit 06ed240

Please sign in to comment.