diff --git a/src/mpris.rs b/src/mpris.rs index a7c32e2d..93754b50 100644 --- a/src/mpris.rs +++ b/src/mpris.rs @@ -5,7 +5,8 @@ use gtk4::{ prelude::*, subclass::prelude::*, }; -use std::cell::RefCell; +use once_cell::unsync::OnceCell; +use std::{cell::RefCell, collections::HashMap}; use crate::deref_cell::DerefCell; @@ -15,6 +16,8 @@ pub struct MprisControlsInner { backward_button: DerefCell, play_pause_button: DerefCell, forward_button: DerefCell, + dbus: OnceCell, + players: RefCell>, } #[glib::object_subclass] @@ -32,17 +35,17 @@ impl ObjectImpl for MprisControlsInner { fn constructed(&self, obj: &MprisControls) { let backward_button = cascade! { gtk4::Button::from_icon_name(Some("media-skip-backward-symbolic")); - ..connect_clicked(|_| {}); + ..connect_clicked(clone!(@strong obj => move |_| obj.call("Previous"))); }; let play_pause_button = cascade! { gtk4::Button::from_icon_name(Some("media-playback-start-symbolic")); - ..connect_clicked(|_| {}); + ..connect_clicked(clone!(@strong obj => move |_| obj.call("PlayPause"))); }; let forward_button = cascade! { gtk4::Button::from_icon_name(Some("media-skip-forward-symbolic")); - ..connect_clicked(|_| {}); + ..connect_clicked(clone!(@strong obj => move |_| obj.call("Next"))); }; let box_ = cascade! { @@ -53,31 +56,41 @@ impl ObjectImpl for MprisControlsInner { ..append(&forward_button); }; - /* glib::MainContext::default().spawn_local(clone!(@strong obj => async move { - // XXX unwrap - let dbus = DBus::new().await.unwrap(); - dbus.connect_name_owner_changed(|a, b, c| { - println!("{:?}", (a, b, c)); - }); - for name in dbus.list_names().await.unwrap() { - if !name.starts_with("org.mpris.MediaPlayer2.") { - continue; + let dbus = match DBus::new().await { + Ok(dbus) => dbus, + Err(err) => { + eprintln!("Failed to connect to 'org.freedesktop.DBus': {}", err); + return; } - let player = Player::new(&name).await.unwrap(); + }; - println!("{}", name); - let status = player.playback_status(); - let metadata = player.metadata().unwrap(); - let title = metadata.title(); - let album = metadata.album(); - let artist = metadata.artist(); - let art = metadata.arturl(); - println!("{:?}", (title, art)); + dbus.connect_name_owner_changed(clone!(@strong obj => move |name, old, new| { + if name.starts_with("org.mpris.MediaPlayer2.") { + if !old.is_empty() { + obj.player_removed(&name); + } + if !new.is_empty() { + glib::MainContext::default().spawn_local(clone!(@strong obj => async move { + obj.player_added(&name).await; + })); + } + } + })); + + match dbus.list_names().await { + Ok(names) => for name in names { + if name.starts_with("org.mpris.MediaPlayer2.") { + glib::MainContext::default().spawn_local(clone!(@strong obj => async move { + obj.player_added(&name).await; + })); + } + } + Err(err) => eprintln!("Failed to call 'ListNames: {}'", err) } - std::mem::forget(dbus); + + let _ = obj.inner().dbus.set(dbus); })); - */ self.box_.set(box_); self.backward_button.set(backward_button); @@ -85,7 +98,7 @@ impl ObjectImpl for MprisControlsInner { self.forward_button.set(forward_button); } - fn dispose(&self, obj: &MprisControls) { + fn dispose(&self, _obj: &MprisControls) { self.box_.unparent(); } } @@ -101,6 +114,52 @@ impl MprisControls { pub fn new() -> Self { glib::Object::new(&[]).unwrap() } + + fn inner(&self) -> &MprisControlsInner { + MprisControlsInner::from_instance(self) + } + + fn player(&self) -> Option { + // XXX correctly choose which player to show + self.inner().players.borrow().values().next().cloned() + } + + async fn player_added(&self, name: &str) { + let player = match Player::new(&name).await { + Ok(player) => player, + Err(err) => { + eprintln!("Failed to connect to '{}': {}", name, err); + return; + } + }; + + let status = player.playback_status(); + let metadata = player.metadata().unwrap(); // XXX unwrap + let title = metadata.title(); + let album = metadata.album(); + let artist = metadata.artist(); + let arturl = metadata.arturl(); + println!("{:?}", (status, title, album, artist, arturl)); + + self.inner() + .players + .borrow_mut() + .insert(name.to_owned(), player); + } + + fn player_removed(&self, name: &str) { + self.inner().players.borrow_mut().remove(name); + } + + fn call(&self, method: &'static str) { + glib::MainContext::default().spawn_local(clone!(@strong self as self_ => async move { + if let Some(player) = self_.player() { + if let Err(err) = player.call(method).await { + eprintln!("Failed to call '{}': {}", method, err); + } + } + })); + } } struct Metadata(glib::VariantDict); @@ -127,6 +186,7 @@ impl Metadata { } } +#[derive(Clone)] struct Player(gio::DBusProxy); impl Player { @@ -143,6 +203,13 @@ impl Player { Ok(Self(proxy)) } + async fn call(&self, method: &str) -> Result<(), glib::Error> { + self.0 + .call_future(method, None, gio::DBusCallFlags::NONE, 1000) + .await?; + Ok(()) + } + fn property(&self, prop: &str) -> Option { self.0.cached_property(prop)?.get() } diff --git a/src/time_button.rs b/src/time_button.rs index 31a06419..28ef3eb2 100644 --- a/src/time_button.rs +++ b/src/time_button.rs @@ -55,7 +55,7 @@ impl ObjectImpl for TimeButtonInner { obj.update_time(); } - fn dispose(&self, obj: &TimeButton) { + fn dispose(&self, _obj: &TimeButton) { self.menu_button.unparent(); } }