266 lines
8.5 KiB
Rust
266 lines
8.5 KiB
Rust
// Copyright (c) 2022 Marceline Cramer
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
use std::path::Path;
|
|
|
|
use canary_magpie::protocol::{
|
|
ClientMessenger, CreatePanel, MagpieClientMsg, MagpieServerMsg, RecvMessage, MAGPIE_SOCK,
|
|
};
|
|
use canary_music_player::*;
|
|
use smol::net::unix::UnixStream;
|
|
|
|
pub type MagpieClient = ClientMessenger<UnixStream>;
|
|
|
|
pub mod mpris;
|
|
|
|
use mpris::*;
|
|
|
|
#[derive(Debug)]
|
|
pub struct Metadata {
|
|
pub album: AlbumInfo,
|
|
pub track: TrackInfo,
|
|
}
|
|
|
|
impl<'a> From<MetadataMap<'a>> for Metadata {
|
|
fn from(map: MetadataMap<'a>) -> Self {
|
|
let album = AlbumInfo {
|
|
title: map
|
|
.get("xesam:album")
|
|
.and_then(|v| TryFrom::try_from(v).ok()),
|
|
artists: map
|
|
.get("xesam:albumArtist")
|
|
.cloned()
|
|
.and_then(|v| TryFrom::try_from(v).ok())
|
|
.unwrap_or(Vec::new()),
|
|
};
|
|
|
|
let track = TrackInfo {
|
|
title: map
|
|
.get("xesam:title")
|
|
.and_then(|v| TryFrom::try_from(v).ok()),
|
|
artists: map
|
|
.get("xesam:artist")
|
|
.cloned()
|
|
.and_then(|v| TryFrom::try_from(v).ok())
|
|
.unwrap_or(Vec::new()),
|
|
track_number: map
|
|
.get("xesam:trackNumber")
|
|
.and_then(|v| TryFrom::try_from(v).ok()),
|
|
length: map
|
|
.get("mpris:length")
|
|
.and_then(|v| i64::try_from(v).ok())
|
|
.map(|us| us as f32 / 1_000_000.0), // 1,000,000 microseconds in a second
|
|
};
|
|
|
|
Self { album, track }
|
|
}
|
|
}
|
|
|
|
impl Metadata {
|
|
pub async fn update_new(magpie: &mut MagpieClient, metadata: MetadataMap<'_>) -> Self {
|
|
let new: Self = metadata.into();
|
|
let msg = InMsg::AlbumChanged(new.album.clone());
|
|
magpie.send_panel_json_async(0, &msg).await;
|
|
let msg = InMsg::TrackChanged(new.track.clone());
|
|
magpie.send_panel_json_async(0, &msg).await;
|
|
new
|
|
}
|
|
|
|
pub async fn update_diff(&mut self, messenger: &mut MagpieClient, metadata: MetadataMap<'_>) {
|
|
let new: Self = metadata.into();
|
|
|
|
if self.album != new.album {
|
|
let msg = InMsg::AlbumChanged(new.album.clone());
|
|
messenger.send_panel_json_async(0, &msg).await;
|
|
}
|
|
|
|
if self.track != new.track {
|
|
let msg = InMsg::TrackChanged(new.track.clone());
|
|
messenger.send_panel_json_async(0, &msg).await;
|
|
let progress = ProgressChanged { position: 0.0 };
|
|
let msg = InMsg::ProgressChanged(progress);
|
|
messenger.send_panel_json_async(0, &msg).await;
|
|
}
|
|
|
|
*self = new;
|
|
}
|
|
}
|
|
|
|
async fn on_message(
|
|
player: &PlayerProxy<'_>,
|
|
magpie: &mut MagpieClient,
|
|
message: MagpieClientMsg,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
let message = match message {
|
|
MagpieClientMsg::RecvMessage(RecvMessage { id: 0, msg }) => msg,
|
|
_ => return Ok(()),
|
|
};
|
|
|
|
let message: OutMsg = match serde_json::from_slice(&message) {
|
|
Ok(v) => v,
|
|
Err(err) => {
|
|
eprintln!("Panel message parse error: {:?}", err);
|
|
return Ok(());
|
|
}
|
|
};
|
|
|
|
match message {
|
|
OutMsg::Pause => player.pause().await?,
|
|
OutMsg::Play => player.play().await?,
|
|
OutMsg::PlayPause => player.play_pause().await?,
|
|
OutMsg::Stop => player.stop().await?,
|
|
OutMsg::Previous => player.previous().await?,
|
|
OutMsg::Next => player.next().await?,
|
|
OutMsg::Seek { offset } => {
|
|
let offset = (offset * 1_000_000.0) as i64; // Seconds to microseconds
|
|
player.seek(offset).await?;
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn player_main(
|
|
player: &PlayerProxy<'_>,
|
|
magpie: &mut MagpieClient,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
use futures_util::StreamExt;
|
|
let mut playback_status = player.receive_playback_status_changed().await.fuse();
|
|
let mut metadata_tracker = player.receive_metadata_changed().await.fuse();
|
|
let mut position_tracker = player.receive_position_changed().await.fuse();
|
|
|
|
let mut metadata = Metadata::update_new(magpie, player.metadata().await?).await;
|
|
use futures_util::FutureExt;
|
|
|
|
loop {
|
|
futures_util::select! {
|
|
msg = magpie.recv().fuse() => {
|
|
match msg {
|
|
Ok(msg) => on_message(player, magpie, msg).await?,
|
|
Err(err) => eprintln!("Magpie recv error: {:?}", err),
|
|
}
|
|
}
|
|
// TODO also update volume, shuffle status, and loop status
|
|
status = playback_status.next() => {
|
|
let status = match status {
|
|
Some(v) => v,
|
|
None => break,
|
|
};
|
|
|
|
let status = status.get().await?;
|
|
let status = match status.as_str() {
|
|
"Playing" => Some(PlaybackStatus::Playing),
|
|
"Paused" => Some(PlaybackStatus::Paused),
|
|
"Stopped" => Some(PlaybackStatus::Stopped),
|
|
_ => None,
|
|
};
|
|
|
|
if let Some(status) = status {
|
|
let msg = InMsg::PlaybackStatusChanged(status);
|
|
magpie.send_panel_json_async(0, &msg).await;
|
|
}
|
|
}
|
|
position = position_tracker.next() => {
|
|
let position = match position {
|
|
Some(v) => v,
|
|
None => break,
|
|
};
|
|
|
|
let position = position.get().await?;
|
|
let position = position as f32 / 1_000_000.0; // Microseconds to seconds
|
|
let progress = ProgressChanged { position };
|
|
let msg = InMsg::ProgressChanged(progress);
|
|
magpie.send_panel_json_async(0, &msg).await;
|
|
}
|
|
new_metadata = metadata_tracker.next() => {
|
|
let new_metadata = match new_metadata {
|
|
Some(v) => v,
|
|
None => break,
|
|
};
|
|
|
|
let new_metadata = new_metadata.get().await?;
|
|
metadata.update_diff(magpie, new_metadata).await;
|
|
}
|
|
};
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn main() {
|
|
let args: Vec<String> = std::env::args().collect();
|
|
let module_path = args
|
|
.get(1)
|
|
.expect("Please pass a path to a Canary script!")
|
|
.to_owned();
|
|
|
|
smol::block_on(async {
|
|
let sock_dir = std::env::var("XDG_RUNTIME_DIR").expect("XDG_RUNTIME_DIR not set");
|
|
let sock_dir = Path::new(&sock_dir);
|
|
let sock_path = sock_dir.join(MAGPIE_SOCK);
|
|
let socket = UnixStream::connect(sock_path).await.unwrap();
|
|
let mut magpie = MagpieClient::new(socket);
|
|
let protocol = "tebibyte-media.desktop.music-player-controller".to_string();
|
|
let script = std::path::PathBuf::from(&module_path);
|
|
let msg = CreatePanel {
|
|
id: 0,
|
|
protocol,
|
|
script,
|
|
};
|
|
|
|
let msg = MagpieServerMsg::CreatePanel(msg);
|
|
magpie.send_async(&msg).await.unwrap();
|
|
|
|
let dbus = zbus::Connection::session().await.unwrap();
|
|
|
|
let mut first_loop = true;
|
|
let mut connected = false;
|
|
|
|
loop {
|
|
if !first_loop {
|
|
let wait = std::time::Duration::from_secs(1);
|
|
std::thread::sleep(wait);
|
|
}
|
|
|
|
first_loop = false;
|
|
|
|
if connected {
|
|
println!("Disconnected from MPRIS");
|
|
let msg = InMsg::Disconnected;
|
|
magpie.send_panel_json_async(0, &msg).await;
|
|
connected = false;
|
|
}
|
|
|
|
println!("Connecting to MPRIS...");
|
|
|
|
let player = match find_player(&dbus).await {
|
|
Ok(Some(player)) => player,
|
|
Ok(None) => {
|
|
eprintln!("Couldn't find player");
|
|
continue;
|
|
}
|
|
Err(err) => {
|
|
eprintln!("D-Bus error while finding player: {:?}", err);
|
|
return;
|
|
}
|
|
};
|
|
|
|
println!(
|
|
"Connected to \"{}\" ({})",
|
|
player.path().as_str(),
|
|
player.destination().as_str()
|
|
);
|
|
connected = true;
|
|
magpie.send_panel_json_async(0, &InMsg::Connected).await;
|
|
|
|
match player_main(&player, &mut magpie).await {
|
|
Ok(()) => {}
|
|
Err(err) => {
|
|
eprintln!("D-Bus error while connected to player: {:?}", err);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|