// 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; pub mod mpris; use mpris::*; #[derive(Debug)] pub struct Metadata { pub album: AlbumInfo, pub track: TrackInfo, } impl<'a> From> 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> { 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> { 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 = 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); } } } }); }