mod imp { use crate::{Playbin, PlaybinSong}; use adw::prelude::*; use adw::subclass::prelude::*; use glib::subclass::InitializingObject; use gtk::{gdk, glib}; use std::cell::{Cell, RefCell}; use std::rc::Rc; #[derive(gtk::CompositeTemplate, glib::Properties, Default)] #[template(resource = "/eu/callcc/audrey/window.ui")] #[properties(wrapper_type = super::Window)] pub struct Window { #[template_child] pub(super) playbar: TemplateChild, #[template_child] pub(super) play_queue: TemplateChild, #[property(get, set, default = false)] can_click_shuffle_all: Cell, #[property(get, set, nullable)] playing_cover_art: RefCell>, #[property(get, set, nullable)] song: RefCell>, pub(super) setup: crate::ui::Setup, pub(super) playbin2: Rc, pub(super) api2: RefCell>>, } #[glib::object_subclass] impl ObjectSubclass for Window { const NAME: &'static str = "AudreyUiWindow"; type Type = super::Window; type ParentType = adw::ApplicationWindow; fn class_init(klass: &mut Self::Class) { klass.bind_template(); klass.bind_template_callbacks(); } fn instance_init(obj: &InitializingObject) { obj.init_template(); } } #[glib::derived_properties] impl ObjectImpl for Window { fn constructed(&self) { self.parent_constructed(); let playbin = Rc::downgrade(&self.playbin2); glib::spawn_future_local(glib::clone!(async move { loop { match playbin.upgrade() { None => break, Some(playbin) => { let listener = playbin.tick(); drop(playbin); listener.await; } } } })); self.play_queue.set_playbin(&self.playbin2); // set up mpris let window = self.obj().clone(); glib::spawn_future_local(async move { let conn = zbus::connection::Builder::session() .expect("could not connect to the session bus") .internal_executor(false) .build() .await .expect("could not build connection to the session bus"); // run this in glib's main loop glib::spawn_future_local(glib::clone!( #[strong] conn, async move { loop { conn.executor().tick().await; } } )); crate::Mpris::setup(conn.object_server(), &window) .await .expect("could not serve mpris"); crate::mpris::Player::setup(conn.object_server(), &window.imp().playbin2) .await .expect("could not serve mpris player"); drop(window); // don't keep this alive // always set up handlers before requesting service name conn.request_name("org.mpris.MediaPlayer2.audrey") .await .expect("could not register name in session bus"); }); } } impl WidgetImpl for Window {} impl WindowImpl for Window {} impl ApplicationWindowImpl for Window {} impl AdwApplicationWindowImpl for Window {} #[gtk::template_callbacks] impl Window { #[template_callback] fn show_playbar_cover_art(&self, stack_child: Option<&str>) -> bool { stack_child != Some("play-queue") } #[template_callback] async fn shuffle_all(&self) { self.obj().set_can_click_shuffle_all(false); self.playbin2.stop(); let api = self.api2.borrow(); let api = api.as_ref().unwrap(); for song in api.get_random_songs(10).await.unwrap().into_iter() { self.playbin2 .push_entry(PlaybinSong::from_child(api, &song)); } self.obj().set_can_click_shuffle_all(true); } #[template_callback] fn show_setup_dialog(&self) { self.setup.present(Some(self.obj().as_ref())); } pub(super) fn now_playing(&self, _song: PlaybinSong) { /* this.song = song; // api.scrobble.begin (this.song.id); TODO if (this.cancel_loading_art != null) { this.cancel_loading_art.cancel (); } this.cancel_loading_art = new GLib.Cancellable (); this.playing_cover_art = null; // TODO: preload next art somehow this.cover_art_loading = true; string song_id = this.song.id; this.api.cover_art.begin (song_id, -1, Priority.DEFAULT, this.cancel_loading_art, (obj, res) => { try { this.playing_cover_art = Gdk.Texture.for_pixbuf (this.api.cover_art.end (res)); this.cover_art_loading = false; } catch (Error e) { if (!(e is IOError.CANCELLED)) { warning ("could not load cover for %s: %s", song_id, e.message); this.cover_art_loading = false; } } }); */ todo!() } } impl Drop for Window { fn drop(&mut self) { println!("dropping AudreyUiWindow"); } } } use crate::PlaybinSong; use adw::prelude::*; use adw::subclass::prelude::*; use gtk::{gdk, gio, glib}; use std::rc::Rc; glib::wrapper! { pub struct Window(ObjectSubclass) @extends adw::ApplicationWindow, gtk::ApplicationWindow, gtk::Window, gtk::Widget, @implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager; } impl Window { pub fn new(app: &impl IsA) -> Self { let window: Self = glib::Object::builder().property("application", app).build(); // manual bidirectional sync window .imp() .playbar .set_volume(window.imp().playbin2.volume() as i32); window.imp().playbin2.volume_changed().connect_object( &*window.imp().playbar, |playbin, playbar, ()| { playbar.set_volume(playbin.volume() as i32); true }, ); window.imp().playbar.connect_notify_local( Some("volume"), glib::clone!( #[weak(rename_to = playbin)] window.imp().playbin2, move |playbar, _| playbin.set_volume(playbar.volume() as i64) ), ); window.imp().playbar.set_mute(window.imp().playbin2.muted()); window.imp().playbin2.muted_changed().connect_object( &*window.imp().playbar, |playbin, playbar, ()| { playbar.set_mute(playbin.muted()); true }, ); window.imp().playbar.connect_notify_local( Some("mute"), glib::clone!( #[weak(rename_to = playbin)] window.imp().playbin2, move |playbar, _| playbin.set_muted(playbar.mute()) ), ); window.imp().playbin2.file_started().connect_object( &*window.imp().playbar, |playbin, playbar, ()| { let entry = &playbin.entries()[playbin.current_entry().unwrap()]; playbar.set_duration(entry.duration() as f64); true }, ); // update position every 100 ms glib::source::timeout_add_local(std::time::Duration::from_millis(100), { let playbar = window.imp().playbar.downgrade(); let playbin = Rc::downgrade(&window.imp().playbin2); move || { let playbar = match playbar.upgrade() { None => return glib::ControlFlow::Break, Some(playbar) => playbar, }; let playbin = match playbin.upgrade() { None => return glib::ControlFlow::Break, Some(playbin) => playbin, }; playbar.set_position(playbin.position().unwrap_or(0.0)); glib::ControlFlow::Continue } }); window .imp() .setup .connected() .connect_object(&window, |_setup, window, api| { window.imp().api2.replace(Some(api)); window.imp().playbin2.stop(); window.set_can_click_shuffle_all(true); true }); window.imp().setup.load(); window .imp() .playbin2 .file_started() .connect_object(&window, |_playbin, _window, ()| { // TODO window.imp().now_playing(song); true }); window } }