Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add mpris-dbus module #366

Merged
merged 14 commits into from
Sep 12, 2023
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