Skip to content

Commit

Permalink
feat: add mpris-dbus module (#366)
Browse files Browse the repository at this point in the history
* feat: add spotify dbus wrapper

* fix: minor stuff

* fix: missing dep

* fix: no change signal

* feat: add dbus module

* refactor: rename module to `mpris_dbus`

* refactor: better layout for `mpris-dbus`

* refactor: transform macro into function

* refactor: make all metadata optional

* feat: add support for track number

* docs: add documentation on dbus

* fix: make `no_autostart` windows only

* chore: fix formatting
  • Loading branch information
Nerixyz authored Sep 12, 2023
1 parent 0f82bdc commit 856e237
Show file tree
Hide file tree
Showing 15 changed files with 1,274 additions and 14 deletions.
730 changes: 720 additions & 10 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ repository = "https://github.com/Nerixyz/current-song2"
categories = ["multimedia"] # TODO: find something more fitting

[workspace]
members = [".", "lib/win-gsmtc", "lib/win-wrapper"]
members = [".", "lib/win-gsmtc", "lib/win-wrapper", "lib/mpris-dbus"]

[features]
# When enabled, the overlay will be bundled into the executable.
Expand Down Expand Up @@ -58,5 +58,8 @@ win-gsmtc = { path = "lib/win-gsmtc" }
win-wrapper = { path = "lib/win-wrapper" }
windows = "0.51"

[target.'cfg(unix)'.dependencies]
mpris-dbus = { path = "lib/mpris-dbus" }

[build-dependencies]
actix-web-static-files = "4.0"
3 changes: 3 additions & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ markdown_extensions:
- attr_list
- footnotes
- md_in_html
- pymdownx.emoji:
emoji_index: !!python/name:materialx.emoji.twemoji
emoji_generator: !!python/name:materialx.emoji.to_svg
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.snippets
Expand Down
31 changes: 29 additions & 2 deletions docs/src/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ The configuration uses the [toml](https://toml.io) format. The default configura
```toml
no_autostart = true

# GSMTC is Windows only
[modules.gsmtc]
enabled = true

[modules.gsmtc.filter]
mode = "Exclude"
items = ["chrome.exe", "msedge.exe", "firefox.exe"]

# DBus is Unix only
[modules.dbus]
enabled = true
destinations = ["org.mpris.MediaPlayer2.spotify"]

[modules.file]
enabled = false

Expand All @@ -35,15 +41,15 @@ The server executable always searches for the configuration file next to itself:
╰─ config.toml
```

## `no_autostart`
## `no_autostart` :fontawesome-brands-windows:

This flag controls if the application will try to add itself to autostart.

- If it's `true`, then it won't add itself to autostart. _This doesn't mean it will be removed! See [Autostart](index.md#Autostart)._
- If it's `false` (default), then it **will** check the autostart and possibly add itself there. You can still disable
the entry on the _Task Manager_'s _Autostart_ tab since this is independent of the actual registry entry.

## GSMTC (Global System Media Transport Controls)
## GSMTC (Global System Media Transport Controls) :fontawesome-brands-windows:

GSMTC uses Windows' own media tracking to provide metadata. However, not every application emits metadata to this system
or only limited metadata (specifically browsers; that's why they're excluded by default).
Expand Down Expand Up @@ -89,6 +95,27 @@ three modes: `Disabled`,`Include`, and `Exclude`:

Controls whether the module should be enabled or not.

## D-Bus :fontawesome-brands-linux:

The D-Bus module and collect metadata from any player implementing the [Media Player Remote Interfacing Specification (MPRIS)](https://specifications.freedesktop.org/mpris-spec/latest/). Most players support MPRIS (see [_Supported Clients_](https://wiki.archlinux.org/title/MPRIS#Supported_clients)). To collect metadata from a specific client, add its destination to [`destinations`](#destinations).

The following [metadata](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata) is collected (when available):

- [`mpris:length`](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/#mpris:length)
- [`xesam:title`](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/#xesam:title)
- [`xesam:album`](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/#xesam:album)
- [`xesam:trackNumber`](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/#xesam:tracknumber)
- [`xesam:artist`](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/#xesam:artist) (joined by `, `)
- [`mpris:artUrl`](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/#mpris:arturl)

### `destinations`

A list of destinations to listen to. Often this is the player name in lower-case prefixed with `org.mpris.MediaPlayer2.`. Each destination becomes a source formatted as `dbus::{destination}`.

### `is_enabled`

Controls whether the module should be enabled or not.

## Server

### `custom_theme_path`
Expand Down
25 changes: 25 additions & 0 deletions lib/mpris-dbus/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "mpris-dbus"
version = "0.1.0"
edition = "2021"
description = "A wrapper around the org.mpris.MediaPlayer2.Player DBus interface"
license = "MIT OR Apache-2.0"
repository = "https://github.com/Nerixyz/current-song2"
keywords = ["unix", "spotify", "dbus"]
categories = ["multimedia"]
readme = "README.md"

[dependencies]
chrono = "0.4"
derive_more = "0.99.17"
futures = "0.3"
serde = { version = "1", features = ["derive"] }
tap = "1"
thiserror = "1"
tokio = { version = "1.32", features = ["sync", "rt", "macros"] }
tracing = "0.1"
zbus = { version = "3.14", default-features = false, features = ["tokio"] }

[dev-dependencies]
tokio = { version = "1.32", features = ["sync", "macros", "rt-multi-thread"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
16 changes: 16 additions & 0 deletions lib/mpris-dbus/examples/listen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use mpris_dbus::player;

#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();

let mut listener = player::listen("org.mpris.MediaPlayer2.spotify")
.await
.unwrap();
println!("Waiting for events...");
while let Some(state) = listener.recv().await {
println!("{state:#?}");
}
}
128 changes: 128 additions & 0 deletions lib/mpris-dbus/src/interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use std::collections::HashMap;

use serde::{Deserialize, Serialize};
use zbus::{dbus_proxy, fdo, zvariant};

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, zvariant::Type, Deserialize, Serialize)]
#[zvariant(signature = "s")]
pub enum PlaybackStatus {
Playing,
Paused,
#[default]
Stopped,
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, zvariant::Type, Deserialize, Serialize)]
#[zvariant(signature = "s")]
pub enum LoopStatus {
#[default]
None,
Track,
Playlist,
}

#[dbus_proxy(
interface = "org.mpris.MediaPlayer2.Player",
default_path = "/org/mpris/MediaPlayer2",
gen_blocking = false
)]
trait SpotifyPlayer {
// Methods
fn next(&self) -> fdo::Result<()>;
fn previous(&self) -> fdo::Result<()>;
fn pause(&self) -> fdo::Result<()>;
fn play_pause(&self) -> fdo::Result<()>;
fn stop(&self) -> fdo::Result<()>;
fn play(&self) -> fdo::Result<()>;
fn seek(&self, offset: i64) -> fdo::Result<()>;
fn set_position(&self, track_id: zvariant::ObjectPath<'_>, position: i64) -> fdo::Result<()>;
fn open_uri(&self, uri: zvariant::Str<'_>) -> fdo::Result<()>;

// Signals
#[dbus_proxy(signal)]
fn seeked(&self, position: i64) -> fdo::Result<()>;

// Properties
#[dbus_proxy(property(emits_changed_signal = "true"))]
fn playback_status(&self) -> fdo::Result<PlaybackStatus>;

#[dbus_proxy(property(emits_changed_signal = "true"))]
fn loop_status(&self) -> fdo::Result<LoopStatus>;
#[dbus_proxy(property(emits_changed_signal = "true"))]
fn set_loop_status(&self, status: LoopStatus) -> fdo::Result<()>;

#[dbus_proxy(property(emits_changed_signal = "true"))]
fn rate(&self) -> fdo::Result<f64>;
#[dbus_proxy(property(emits_changed_signal = "true"))]
fn set_rate(&self, rate: f64) -> fdo::Result<()>;

#[dbus_proxy(property(emits_changed_signal = "true"))]
fn shuffle(&self) -> fdo::Result<bool>;
#[dbus_proxy(property(emits_changed_signal = "true"))]
fn set_shuffle(&self, enabled: bool) -> fdo::Result<()>;

#[dbus_proxy(property(emits_changed_signal = "true"))]
fn volume(&self) -> fdo::Result<f64>;
#[dbus_proxy(property(emits_changed_signal = "true"))]
fn set_volume(&self, volume: f64) -> fdo::Result<()>;

#[dbus_proxy(property(emits_changed_signal = "true"))]
fn metadata(&self) -> fdo::Result<HashMap<zvariant::Str<'static>, zvariant::Value>>;

#[dbus_proxy(property(emits_changed_signal = "false"))]
fn position(&self) -> fdo::Result<i64>;
#[dbus_proxy(property)]
fn minimum_rate(&self) -> fdo::Result<i64>;
#[dbus_proxy(property)]
fn maximum_rate(&self) -> fdo::Result<f64>;
#[dbus_proxy(property)]
fn can_go_next(&self) -> fdo::Result<bool>;
#[dbus_proxy(property)]
fn can_go_previous(&self) -> fdo::Result<bool>;
#[dbus_proxy(property)]
fn can_play(&self) -> fdo::Result<bool>;
#[dbus_proxy(property)]
fn can_pause(&self) -> fdo::Result<bool>;
#[dbus_proxy(property)]
fn can_seek(&self) -> fdo::Result<bool>;
#[dbus_proxy(property)]
fn can_control(&self) -> fdo::Result<bool>;
}

macro_rules! string_enum_conversions {
($en:ty; $($val:ident => $str:literal),+) => {
impl<'a> From<$en> for zvariant::Value<'a> {
fn from(value: $en) -> Self {
zvariant::Value::Str(zvariant::Str::from_static(match value {
$(<$en>::$val => $str),+
}))
}
}

impl TryFrom<zvariant::OwnedValue> for $en {
type Error = zvariant::Error;

fn try_from(value: zvariant::OwnedValue) -> Result<Self, Self::Error> {
let Ok(s) = <&str>::try_from(&value) else {
return Err(Self::Error::IncorrectType);
};
match s {
$($str => Ok(Self::$val)),+,
_ => Err(Self::Error::IncorrectType),
}
}
}
};
}

string_enum_conversions! { LoopStatus;
None => "None",
Track => "Track",
Playlist => "Playlist"
}

string_enum_conversions! { PlaybackStatus;
Playing => "Playing",
Paused => "Paused",
Stopped => "Stopped"
}
4 changes: 4 additions & 0 deletions lib/mpris-dbus/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#![cfg(unix)]

pub mod interface;
pub mod player;
Loading

0 comments on commit 856e237

Please sign in to comment.