diff --git a/src/main.rs b/src/main.rs index a269ec4..3b87de8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,12 +16,6 @@ pub use playbin_song::Song as PlaybinSong; pub mod subsonic; -pub mod playbin; -pub use playbin::Playbin; - -mod signal; -pub use signal::{Signal, SignalEmitter, SignalHandler}; - pub mod event; pub use event::Event; diff --git a/src/mpris.rs b/src/mpris.rs index 3457edf..58d5f13 100644 --- a/src/mpris.rs +++ b/src/mpris.rs @@ -1,6 +1,3 @@ -mod player; -pub use player::Player; - use adw::prelude::*; use gtk::glib; use tracing::{event, Level}; diff --git a/src/mpris/player.rs b/src/mpris/player.rs deleted file mode 100644 index e0769eb..0000000 --- a/src/mpris/player.rs +++ /dev/null @@ -1,582 +0,0 @@ -use crate::{Playbin, PlaybinSong}; -use gtk::glib::spawn_future_local; -use std::collections::HashMap; -use std::rc::{Rc, Weak}; -use tracing::{event, Level}; -use zbus::zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Value}; - -const MICROSECONDS: f64 = 1e6; // in a second - -#[derive(Default)] -struct MetadataMap { - // mpris - track_id: Option, - length: Option, - art_url: Option, - // xesam - album: Option, - //album_artist: Option>, - artist: Option>, - //as_text: Option, - //audio_bpm: Option, - //auto_rating: Option, - //comment: Option>, - //composer: Option>, - content_created: Option, - //disc_number: Option, - //first_used: Option, - genre: Option>, - //last_used: Option, - //lyricist: Option>, - title: Option, - track_number: Option, - //url: Option, - //use_count: Option, - user_rating: Option, -} - -impl MetadataMap { - fn from_playbin_song(song: Option<&PlaybinSong>) -> Self { - song.map(|song| MetadataMap { - // use a unique growing counter to identify tracks - track_id: Some({ - format!("/eu/callcc/audrey/Track/{}", song.counter()) - .try_into() - .unwrap() - }), - length: Some(song.duration() * MICROSECONDS as i64), - //art_url: Some(song.cover_art_url()), // FIXME: this would leak credentials - album: Some(song.album()), - artist: Some(vec![song.artist()]), - //content_created: song.year().map(|year| chrono::NaiveDate::from_yo_opt(year, 1).unwrap()), // FIXME: replace this unwrap with Some(Err) -> None - //genre: Some(song.genre.iter().collect()), - title: Some(song.title()), - //track_number: song.track().map(|u| u as i32), - //user_rating: Some(if song.starred().is_none() { 0.0 } else { 1.0 }), - ..Default::default() - }) - .unwrap_or_default() - } - - fn as_hash_map(&self) -> HashMap<&'static str, OwnedValue> { - let mut map = HashMap::new(); - - if let Some(track_id) = &self.track_id { - map.insert( - "mpris:trackid", - Value::new(track_id.as_ref()).try_into().unwrap(), - ); - } - if let Some(art_url) = &self.art_url { - map.insert( - "mpris:artUrl", - Value::new(art_url.to_string()).try_into().unwrap(), - ); - } - if let Some(length) = &self.length { - map.insert("mpris:length", Value::new(length).try_into().unwrap()); - } - if let Some(album) = &self.album { - map.insert("xesam:album", Value::new(album).try_into().unwrap()); - } - if let Some(artist) = &self.artist { - map.insert("xesam:artist", Value::new(artist).try_into().unwrap()); - } - if let Some(content_created) = &self.content_created { - map.insert( - "xesam:contentCreated", - Value::new(content_created.format("%+").to_string()) - .try_into() - .unwrap(), - ); - } - if let Some(genre) = &self.genre { - map.insert("xesam:genre", Value::new(genre).try_into().unwrap()); - } - if let Some(track_number) = self.track_number { - map.insert( - "xesam:trackNumber", - Value::new(track_number).try_into().unwrap(), - ); - } - if let Some(title) = &self.title { - map.insert("xesam:title", Value::new(title).try_into().unwrap()); - } - if let Some(user_rating) = self.user_rating { - map.insert( - "xesam:userRating", - Value::new(user_rating).try_into().unwrap(), - ); - } - - map - } -} - -pub struct Player(async_channel::Sender) + Send>>); - -impl Player { - pub async fn setup( - object_server: &zbus::ObjectServer, - playbin: &Rc, - ) -> Result<(), zbus::Error> { - let local = LocalPlayer { - metadata: MetadataMap::from_playbin_song(None), - playbin: Rc::downgrade(playbin), - }; - - let (with_local_send, with_local_recv) = async_channel::unbounded(); - - let player = Self(with_local_send); - - spawn_future_local(async move { - let local = Rc::new(local); - while let Ok(f) = with_local_recv.recv().await { - f(Rc::clone(&local)); - } - }); - - object_server.at("/org/mpris/MediaPlayer2", player).await?; - - let _player_ref = object_server - .interface::<_, Self>("/org/mpris/MediaPlayer2") - .await?; - - /* - playbin.connect_new_track(glib::clone!( - #[strong] - player_ref, - move |_, song| { - let metadata = MetadataMap::from_playbin_song(Some(song)); - - let player_ref = player_ref.clone(); - glib::spawn_future_local(async move { - let mut player = player_ref.get_mut().await; - player.metadata = metadata; - player - .metadata_changed(player_ref.signal_emitter()) - .await - .unwrap(); - }); - } - )); - - playbin.connect_seeked(glib::clone!( - #[strong] - player_ref, - move |_, position| { - let player_ref = player_ref.clone(); - glib::spawn_future_local(async move { - player_ref - .seeked((position * MICROSECONDS) as i64) - .await - .unwrap(); - }); - } - )); - - playbin.connect_notify_future_local( - "play-queue-length", - glib::clone!( - #[strong] - player_ref, - move |_, _| { - let player_ref = player_ref.clone(); - async move { - let player = player_ref.get_mut().await; - // properties that depend on the play queue length - player - .can_go_next_changed(player_ref.signal_emitter()) - .await - .unwrap(); - player - .can_go_previous_changed(player_ref.signal_emitter()) - .await - .unwrap(); - player - .can_play_changed(player_ref.signal_emitter()) - .await - .unwrap(); - } - } - ), - ); - - playbin.connect_notify_future_local( - "state", - glib::clone!( - #[strong] - player_ref, - move |_, _| { - let player_ref = player_ref.clone(); - async move { - let player = player_ref.get_mut().await; - // properties that depend on the playbin state - player - .playback_status_changed(player_ref.signal_emitter()) - .await - .unwrap(); - } - } - ), - ); - - playbin.connect_notify_future_local( - "volume", - glib::clone!( - #[strong] - player_ref, - move |_, _| { - let player_ref = player_ref.clone(); - async move { - let player = player_ref.get_mut().await; - player - .volume_changed(player_ref.signal_emitter()) - .await - .unwrap(); - } - } - ), - ); - */ - - Ok(()) - } - - async fn with_local>( - &self, - f: impl FnOnce(Rc) -> F + Send + 'static, - ) -> T { - let (send, recv) = async_channel::bounded(1); - - self.0 - .send(Box::new(move |local| { - gtk::glib::spawn_future_local(async move { - send.send(f(local).await).await.unwrap(); - }); - })) - .await - .unwrap(); - recv.recv().await.unwrap() - } -} - -// because zbus insists in being Send+Sync all proper -struct LocalPlayer { - metadata: MetadataMap, - playbin: Weak, -} - -#[zbus::interface(name = "org.mpris.MediaPlayer2.Player")] -impl Player { - async fn next(&self) -> zbus::fdo::Result<()> { - self.with_local(move |local| async move { local.next() }) - .await - } - - async fn previous(&self) -> zbus::fdo::Result<()> { - self.with_local(move |local| async move { local.previous() }) - .await - } - - async fn pause(&self) -> zbus::fdo::Result<()> { - self.with_local(move |local| async move { local.pause() }) - .await - } - - async fn play_pause(&self) -> zbus::fdo::Result<()> { - self.with_local(move |local| async move { local.play_pause() }) - .await - } - - async fn stop(&self) -> zbus::fdo::Result<()> { - self.with_local(move |local| async move { local.stop() }) - .await - } - - async fn play(&self) -> zbus::fdo::Result<()> { - self.with_local(move |local| async move { local.play() }) - .await - } - - async fn seek(&self, offset: i64) -> zbus::fdo::Result<()> { - self.with_local(move |local| async move { local.seek(offset) }) - .await - } - - async fn set_position(&self, track_id: ObjectPath<'_>, position: i64) -> zbus::fdo::Result<()> { - let track_id = track_id.to_owned(); - self.with_local(move |local| async move { local.set_position(track_id, position) }) - .await - } - - async fn open_uri(&self, _s: String) -> zbus::fdo::Result<()> { - Err(zbus::fdo::Error::NotSupported("OpenUri".into())) - } - - #[zbus(property)] - async fn playback_status(&self) -> zbus::fdo::Result { - self.with_local(|local| async move { local.playback_status() }) - .await - } - - #[zbus(property)] - fn loop_status(&self) -> zbus::fdo::Result { - Ok("None".into()) // TODO - } - - #[zbus(property)] - fn set_loop_status(&self, _loop_status: &str) -> zbus::Result<()> { - Err(zbus::fdo::Error::NotSupported("setting LoopStatus".into()).into()) // TODO - } - - #[zbus(property)] - fn rate(&self) -> zbus::fdo::Result { - Ok(1.0) - } - - #[zbus(property)] - async fn set_rate(&self, rate: f64) -> zbus::Result<()> { - // A value of 0.0 should not be set by the client. If it is, the media player should act as though Pause was called. - if rate == 0.0 { - self.with_local(|local| async move { local.pause() }) - .await?; - } - - // just ignore anything else - Ok(()) - } - - #[zbus(property)] - // FIXME: https://github.com/dbus2/zbus/issues/992 - fn shuffle(&self) -> zbus::fdo::Result { - Ok(false) - } - - #[zbus(property)] - // FIXME: see above - fn set_shuffle(&self, _shuffle: bool) -> zbus::Result<()> { - Err(zbus::fdo::Error::NotSupported("setting Shuffle".into()).into()) - } - - #[zbus(property)] - async fn metadata(&self) -> zbus::fdo::Result> { - self.with_local(move |local| async move { Ok(local.metadata.as_hash_map()) }) - .await - } - - #[zbus(property)] - async fn volume(&self) -> zbus::fdo::Result { - self.with_local(|local| async move { local.volume() }).await - } - - #[zbus(property)] - async fn set_volume(&self, volume: f64) -> zbus::Result<()> { - self.with_local(move |local| async move { local.set_volume(volume) }) - .await?; - Ok(()) - } - - #[zbus(property(emits_changed_signal = "false"))] - async fn position(&self) -> zbus::fdo::Result { - self.with_local(|local| async move { local.position() }) - .await - } - - #[zbus(property)] - fn minimum_rate(&self) -> f64 { - 1.0 - } - - #[zbus(property)] - fn maximum_rate(&self) -> f64 { - 1.0 - } - - #[zbus(property)] - async fn can_go_next(&self) -> zbus::fdo::Result { - self.with_local(|local| async move { local.can_go_next() }) - .await - } - - #[zbus(property)] - async fn can_go_previous(&self) -> zbus::fdo::Result { - self.with_local(|local| async move { local.can_go_previous() }) - .await - } - - #[zbus(property)] - async fn can_play(&self) -> zbus::fdo::Result { - self.with_local(|local| async move { local.can_play() }) - .await - } - - #[zbus(property)] - async fn can_pause(&self) -> bool { - self.with_local(|local| async move { local.can_pause() }) - .await - } - - #[zbus(property)] - async fn can_seek(&self) -> bool { - self.with_local(|local| async move { local.can_seek() }) - .await - } - - #[zbus(property(emits_changed_signal = "const"))] - async fn can_control(&self) -> bool { - true - } -} - -impl LocalPlayer { - fn playbin(&self) -> zbus::fdo::Result> { - match self.playbin.upgrade() { - None => Err(zbus::fdo::Error::Failed("playbin was discarded".into())), - Some(playbin) => Ok(playbin), - } - } - - fn next(&self) -> zbus::fdo::Result<()> { - // If CanGoNext is false, attempting to call this method should have no effect. - if !self.can_go_next()? { - return Ok(()); - } - - let playbin = self.playbin()?; - if playbin.current_entry().is_none() - || (playbin.current_entry().unwrap() + 1 > playbin.entries().len()) - { - // If there is no next track (and endless playback and track repeat are both off), stop playback. - // (interpret this as something else than what Stop does) - todo!(); - } else { - playbin.next_entry(); - } - - Ok(()) - } - - fn previous(&self) -> zbus::fdo::Result<()> { - let playbin = self.playbin()?; - - // If CanGoPrevious is false, attempting to call this method should have no effect. - if !self.can_go_previous()? { - return Ok(()); - } - - match playbin.current_entry() { - None | Some(0) => { - // If there is no previous track (and endless playback and track repeat are both off), stop playback. - // (interpret this as something else than what Stop does) - playbin.stop(); - } - _ => { - playbin.prev_entry(); - } - } - - Ok(()) - } - - fn pause(&self) -> zbus::fdo::Result<()> { - let playbin = self.playbin()?; - - // If CanPause is false, attempting to call this method should have no effect. - if !self.can_pause() { - return Ok(()); - } - - // If playback is already paused, this has no effect. - if playbin.paused() { - return Ok(()); - } - - playbin.set_paused(true); - Ok(()) - } - - fn play_pause(&self) -> zbus::fdo::Result<()> { - let playbin = self.playbin()?; - playbin.set_paused(!playbin.paused()); - Ok(()) - } - - fn stop(&self) -> zbus::fdo::Result<()> { - todo!() - } - - fn play(&self) -> zbus::fdo::Result<()> { - todo!() - } - - fn seek(&self, _offset: i64) -> zbus::fdo::Result<()> { - todo!() - } - - fn set_position( - &self, - _track_id: ObjectPath<'static>, - _position: i64, - ) -> zbus::fdo::Result<()> { - todo!() - } - - fn playback_status(&self) -> zbus::fdo::Result { - match self.playbin()?.paused() { - //crate::playbin::State::Stopped => Ok("Stopped".into()), - false => Ok("Playing".into()), - true => Ok("Paused".into()), - } - } - - fn volume(&self) -> zbus::fdo::Result { - Ok(self.playbin()?.volume() as f64 / 100.0) - } - - fn set_volume(&self, mut volume: f64) -> zbus::fdo::Result<()> { - // When setting, if a negative value is passed, the volume should be set to 0.0. - if volume < 0.0 { - volume = 0.0; - } - let playbin = self.playbin()?; - // FIXME: check if this is set by the notify callback: self.volume = volume; - playbin.set_volume((volume * 100.0) as i64); - Ok(()) - } - - fn position(&self) -> zbus::fdo::Result { - Ok(0) // TODO - } - - fn can_go_next(&self) -> zbus::fdo::Result { - // same as can_play - Ok(self.playbin()?.entries().len() > 0) - } - - fn can_go_previous(&self) -> zbus::fdo::Result { - // same as can_play - Ok(self.playbin()?.entries().len() > 0) - } - - fn can_play(&self) -> zbus::fdo::Result { - // it only makes sense to disallow "play" when the play queue is empty - Ok(self.playbin()?.entries().len() > 0) - } - - fn can_pause(&self) -> bool { - // we don't play anything that can't be paused - true - } - - fn can_seek(&self) -> bool { - // we don't play anything that can't be seeked - true - } -} - -impl Drop for Player { - fn drop(&mut self) { - event!(Level::DEBUG, "dropping MprisPlayer"); - } -} diff --git a/src/playbin.rs b/src/playbin.rs deleted file mode 100644 index 3ae122e..0000000 --- a/src/playbin.rs +++ /dev/null @@ -1,267 +0,0 @@ -use crate::mpv; -use crate::signal::{Signal, SignalEmitter}; -use crate::Event; -use crate::PlaybinSong as Song; -use event_listener::EventListener; -use std::cell::{Ref, RefCell}; -use tracing::{event, span, Level}; - -pub struct Playbin { - mpv: mpv::Handle, - entries: RefCell>, - - sender: async_broadcast::Sender, - - stopped: SignalEmitter, - entry_removed: SignalEmitter, - - file_started: SignalEmitter, -} - -impl Playbin { - pub fn new(sender: async_broadcast::Sender) -> Self { - let mpv = mpv::Handle::new(); - mpv.set_property("audio-client-name", "audrey").unwrap(); - mpv.set_property("user-agent", crate::USER_AGENT).unwrap(); - mpv.set_property("video", false).unwrap(); - mpv.set_property("prefetch-playlist", true).unwrap(); - mpv.set_property("gapless-audio", true).unwrap(); - - mpv.observe_property(0, "volume").unwrap(); - mpv.observe_property(1, "mute").unwrap(); - mpv.observe_property(2, "pause").unwrap(); - mpv.observe_property(3, "playlist-pos").unwrap(); - - // "Useful to drain property changes before a new file is loaded." - mpv.add_hook(0, "on_before_start_file", 0).unwrap(); - - Self { - mpv, - entries: RefCell::new(vec![]), - - sender, - - stopped: Default::default(), - entry_removed: Default::default(), - - file_started: Default::default(), - } - } - - pub fn volume(&self) -> i64 { - self.mpv.get_property("volume").unwrap() - } - - pub fn set_volume(&self, volume: i64) { - self.mpv.set_property("volume", volume).unwrap(); - } - - pub fn muted(&self) -> bool { - self.mpv.get_property("mute").unwrap() - } - - pub fn set_muted(&self, muted: bool) { - self.mpv.set_property("mute", muted).unwrap(); - } - - pub fn paused(&self) -> bool { - self.mpv.get_property("pause").unwrap() - } - - pub fn set_paused(&self, paused: bool) { - self.mpv.set_property("pause", paused).unwrap(); - } - - pub fn position(&self) -> Option { - self.mpv.get_property("time-pos").unwrap() - } - - pub fn current_entry(&self) -> Option { - self.mpv - .get_property::("playlist-pos") - .unwrap() - .try_into() - .ok() - } - - pub fn seek(&self, _position: f64) { - todo!() - } - - pub fn next_entry(&self) { - self.mpv.command(["playlist-next"]).unwrap(); - } - - pub fn prev_entry(&self) { - self.mpv.command(["playlist-prev"]).unwrap(); - } - - pub fn play_entry(&self, index: usize) { - self.mpv - .command(["playlist-play-index", &index.to_string()]) - .unwrap(); - } - - pub fn entries(&self) -> Ref<'_, [Song]> { - Ref::map(self.entries.borrow(), Vec::as_ref) - } - - pub fn push_entry(&self, entry: Song) { - let mut entries = self.entries.borrow_mut(); - self.mpv - .command(["loadfile", &entry.stream_url(), "append-play"]) - .unwrap(); - let index = entries.len(); - entries.push(entry); - - drop(entries); - self.sender - .try_broadcast(Event::PlaybinEntryInserted(index)) - .unwrap(); - } - - pub fn insert_entry(&self, index: usize, entry: Song) { - let mut entries = self.entries.borrow_mut(); - self.mpv - .command([ - "loadfile", - &entry.stream_url(), - "insert-at-play", - &index.to_string(), - ]) - .unwrap(); - entries.insert(index, entry); - - drop(entries); - self.sender - .try_broadcast(Event::PlaybinEntryInserted(index)) - .unwrap(); - } - - // stop playback and clear playlist - pub fn stop(&self) { - let mut entries = self.entries.borrow_mut(); - self.mpv.command(["stop"]).unwrap(); - entries.clear(); - - drop(entries); - self.sender.try_broadcast(Event::PlaybinStopped).unwrap(); - } - - pub fn remove_entry(&self, index: usize) { - let mut entries = self.entries.borrow_mut(); - self.mpv - .command(["playlist-remove", &index.to_string()]) - .unwrap(); - entries.remove(index); - - drop(entries); - self.sender - .try_broadcast(Event::PlaybinEntryRemoved(index)) - .unwrap(); - } - - pub fn move_entry(&self, _from: usize, _to: usize) { - todo!() - } - - pub fn tick(&self) -> EventListener { - let listener = self.mpv.wakeup_listener(); - while let Some(event) = self.mpv.wait_event(0.0) { - self.handle_event(event); - } - listener - } - - fn handle_event(&self, event: mpv::Event) { - let span = span!(Level::DEBUG, "mpv_handle_event"); - let _guart = span.enter(); - match event { - mpv::Event::PropertyChange(event) => match event.reply_userdata { - 0 => { - assert_eq!(&event.name, "volume"); - self.sender - .try_broadcast(Event::PlaybinVolumeChanged) - .unwrap(); - event!(Level::DEBUG, "volume change {}", self.volume()); - } - - 1 => { - assert_eq!(&event.name, "mute"); - self.sender - .try_broadcast(Event::PlaybinMutedChanged) - .unwrap(); - event!(Level::DEBUG, "mute state change to {}", self.muted()); - } - - 2 => { - assert_eq!(&event.name, "pause"); - self.sender - .try_broadcast(Event::PlaybinPausedChanged) - .unwrap(); - event!(Level::DEBUG, "pause state change to {}", self.paused()); - } - - 3 => { - assert_eq!(&event.name, "playlist-pos"); - self.sender - .try_broadcast(Event::PlaybinCurrentEntryChanged) - .unwrap(); - event!( - Level::DEBUG, - "playlist-pos change {:?}", - self.current_entry() - ); - } - - _ => unreachable!(), - }, - - mpv::Event::Hook(event) => match event.reply_userdata { - 0 => { - assert_eq!(&event.name, "on_before_start_file"); - event!(Level::DEBUG, "on_before_start_file triggered"); - // just use this as a barrier - self.mpv.continue_hook(event.id).unwrap(); - } - - _ => unreachable!(), - }, - - mpv::Event::StartFile(_) => { - // since we set up the hook before, the current song is guaranteed not to change - // under our feet - self.sender - .try_broadcast(Event::PlaybinFileStarted) - .unwrap(); - - // sanity check - assert_eq!( - self.entries()[self.current_entry().unwrap()].stream_url(), - self.mpv.get_property::("path").unwrap() - ); - } - - _ => event!(Level::DEBUG, "mpv event {:?}", event), - } - } - - pub fn stopped(&self) -> Signal<'_, Self, ()> { - self.stopped.signal() - } - - pub fn entry_removed(&self) -> Signal<'_, Self, usize> { - self.entry_removed.signal() - } - - pub fn file_started(&self) -> Signal<'_, Self, ()> { - self.file_started.signal() - } -} - -impl Drop for Playbin { - fn drop(&mut self) { - event!(Level::DEBUG, "dropping Playbin2"); - self.mpv.command(["quit"]).unwrap(); - } -} diff --git a/src/signal.rs b/src/signal.rs deleted file mode 100644 index 1602a2c..0000000 --- a/src/signal.rs +++ /dev/null @@ -1,123 +0,0 @@ -use gtk::{ - glib::Object, - prelude::{IsA, ObjectExt}, -}; -use std::cell::{Cell, RefCell}; -use std::rc::{Rc, Weak}; - -type SignalHandlerBox = Box bool>; - -pub struct SignalEmitter { - handlers: RefCell>>, - just_connected: RefCell>>, -} - -impl Default for SignalEmitter { - fn default() -> Self { - Self { - handlers: RefCell::new(vec![]), - just_connected: RefCell::new(vec![]), - } - } -} - -pub struct Signal<'a, E, T> { - just_connected: &'a RefCell>>, -} - -#[derive(Clone, Default)] -pub struct SignalHandler(Weak>); - -impl SignalHandler { - pub fn disconnect(self) -> bool { - match self.0.upgrade() { - None => false, - Some(cell) => { - cell.set(true); - true - } - } - } -} - -impl Signal<'_, E, T> { - fn connect_impl(&self, f: impl FnMut(&E, T) -> bool + 'static) { - self.just_connected.borrow_mut().push(Box::new(f)); - } - - pub fn connect(&self, mut f: impl FnMut(&E, T) -> bool + 'static) -> SignalHandler { - let disconnect = Rc::new(Cell::new(false)); - let disconnect_weak = Rc::downgrade(&disconnect); - - self.connect_impl(move |e, t| match disconnect.get() { - true => false, - false => { - f(e, t); - true - } - }); - - SignalHandler(disconnect_weak) - } - - pub fn connect_rc( - &self, - listener: &Rc, - mut f: impl FnMut(&E, Rc, T) -> bool + 'static, - ) -> SignalHandler { - let listener = Rc::downgrade(listener); - - self.connect(move |e, t| match listener.upgrade() { - None => false, - Some(listener) => f(e, listener, t), - }) - } - - pub fn connect_object>( - &self, - listener: &L, - mut f: impl FnMut(&E, L, T) -> bool + 'static, - ) -> SignalHandler { - let listener = listener.downgrade(); - - self.connect(move |e, t| match listener.upgrade() { - None => false, - Some(listener) => f(e, listener, t), - }) - } -} - -impl SignalEmitter { - pub fn signal(&self) -> Signal<'_, E, T> { - Signal { - just_connected: &self.just_connected, - } - } - - pub fn emit_with(&self, emitter: &E, mut f: impl FnMut() -> T) { - let mut handlers = self - .handlers - .try_borrow_mut() - .expect("tried to re-emit signal during emission"); - handlers.append(self.just_connected.borrow_mut().as_mut()); - - let mut i = 0; - // FIXME: does not preserve ordering - while i < handlers.len() { - if handlers[i](emitter, f()) { - i += 1; - } else { - drop(handlers.swap_remove(i)); - } - } - } -} - -impl SignalEmitter -where - T: Clone, -{ - pub fn emit(&self, emitter: &E, t: T) { - self.emit_with(emitter, || t.clone()); - } -} diff --git a/src/ui/setup.rs b/src/ui/setup.rs index 190c208..ee80ab4 100644 --- a/src/ui/setup.rs +++ b/src/ui/setup.rs @@ -1,9 +1,8 @@ mod imp { - use crate::signal::SignalEmitter; use adw::{glib, prelude::*, subclass::prelude::*}; use glib::subclass::InitializingObject; + use glib::WeakRef; use std::cell::{Cell, RefCell}; - use std::rc::Rc; use tracing::{event, Level}; #[derive(gtk::CompositeTemplate, glib::Properties, Default)] @@ -25,7 +24,8 @@ mod imp { #[property(get, set)] password: RefCell, - pub(super) connected: SignalEmitter>, + #[property(get, set)] + window: WeakRef, } #[glib::object_subclass] @@ -112,7 +112,7 @@ mod imp { .await .unwrap(); - self.connected.emit(self.obj().as_ref(), Rc::new(api)); + self.window.upgrade().unwrap().setup_connected(api); } } @@ -138,10 +138,6 @@ impl Default for Setup { } } -use crate::signal::Signal; -use crate::subsonic; -use std::rc::Rc; - impl Setup { pub fn load(&self) { glib::spawn_future_local(glib::clone!( @@ -181,8 +177,4 @@ impl Setup { } )); } - - pub fn connected(&self) -> Signal<'_, super::Setup, Rc> { - self.imp().connected.signal() - } } diff --git a/src/ui/window.rs b/src/ui/window.rs index a376aff..99c78f8 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -325,6 +325,7 @@ mod imp { use adw::prelude::*; use adw::subclass::prelude::*; use gtk::{gio, glib}; +use std::rc::Rc; glib::wrapper! { pub struct Window(ObjectSubclass) @@ -336,16 +337,7 @@ impl Window { pub fn new(app: &impl IsA) -> Self { let window: Self = glib::Object::builder().property("application", app).build(); - window - .imp() - .setup - .connected() - .connect_object(&window, |_setup, window, api| { - window.imp().api.replace(Some(api)); - //window.imp().playbin.stop(); - window.set_can_click_shuffle_all(true); - true - }); + window.imp().setup.set_window(&window); window.imp().setup.load(); window @@ -369,4 +361,9 @@ impl Window { .unwrap(); self.playlist_model().remove(index as u32); } + + pub fn setup_connected(&self, api: crate::subsonic::Client) { + self.imp().api.replace(Some(Rc::new(api))); + self.set_can_click_shuffle_all(true); + } }