remove some signals

This commit is contained in:
Erica Z 2024-11-04 10:40:51 +01:00
parent 4413aaff4b
commit 9ff36afb15
9 changed files with 203 additions and 104 deletions

1
Cargo.lock generated
View file

@ -217,6 +217,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
name = "audrey"
version = "0.1.0"
dependencies = [
"async-broadcast",
"async-channel",
"base16ct",
"bindgen",

View file

@ -5,6 +5,7 @@ edition = "2021"
[dependencies]
adw = { version = "0.7.0", package = "libadwaita", features = ["v1_6"] }
async-broadcast = "0.7.1"
async-channel = "2.3.1"
base16ct = { version = "0.2.0", features = ["std"] }
chrono = { version = "0.4.38", features = ["serde"] }

34
src/broadcast.rs Normal file
View file

@ -0,0 +1,34 @@
// nice wrapper to have a Default impl for a (Sender, InactiveReceiver) pair
use async_broadcast::{broadcast, InactiveReceiver, Receiver, Sender, TrySendError};
pub struct Broadcast<const N: usize, T> {
pub sender: Sender<T>,
inactive_receiver: InactiveReceiver<T>,
}
impl<const N: usize, T> Default for Broadcast<N, T> {
fn default() -> Self {
let (sender, receiver) = broadcast(N);
Self {
sender,
inactive_receiver: receiver.deactivate(),
}
}
}
impl<const N: usize, T> Broadcast<N, T> where T: Clone {
pub fn receiver(&self) -> Receiver<T> {
self.inactive_receiver.activate_cloned()
}
// like sender.try_broadcast, but ignores if there aren't any active listeners
pub fn try_broadcast(&self, msg: T) -> Result<(), TrySendError<T>> {
match self.sender.try_broadcast(msg) {
Err(TrySendError::Inactive(_)) => Ok(()), // ignore
Err(err) => Err(err),
Ok(Some(_)) => unreachable!("we do not enable overflow mode"),
Ok(None) => Ok(()),
}
}
}

29
src/event.rs Normal file
View file

@ -0,0 +1,29 @@
#[derive(Clone, Debug)]
pub enum Event {
PlaybinVolumeChanged,
PlaybinMutedChanged,
PlaybinPausedChanged,
PlaybinCurrentEntryChanged,
PlaybinEntryInserted(usize),
PlaybinStopped,
PlaybinEntryRemoved(usize),
PlaybinFileStarted,
}
use adw::prelude::*;
use gtk::glib;
pub fn spawn_object_listener<O: IsA<glib::Object>>(
mut receiver: async_broadcast::Receiver<Event>,
obj: &O,
mut f: impl FnMut(O, Event) + 'static,
) {
let weak = obj.downgrade();
glib::spawn_future_local(async move {
while let Some(obj) = weak.upgrade() {
f(obj, receiver.recv_direct().await.unwrap());
}
});
}

View file

@ -22,6 +22,9 @@ pub type Playbin = playbin::Playbin<PlaybinSong>;
mod signal;
pub use signal::{Signal, SignalEmitter, SignalHandler};
pub mod event;
pub use event::Event;
use gettextrs::{bind_textdomain_codeset, bindtextdomain, setlocale, textdomain, LocaleCategory};
use gtk::prelude::*;
use gtk::{gio, glib};

View file

@ -1,5 +1,6 @@
use crate::mpv;
use crate::signal::{Signal, SignalEmitter};
use crate::Event;
use event_listener::EventListener;
use std::cell::{Ref, RefCell};
use tracing::{event, span, Level};
@ -20,20 +21,19 @@ pub struct Playbin<E> {
mpv: mpv::Handle,
entries: RefCell<Vec<E>>,
volume_changed: SignalEmitter<Self, ()>,
muted_changed: SignalEmitter<Self, ()>,
paused_changed: SignalEmitter<Self, ()>,
current_entry_changed: SignalEmitter<Self, ()>,
sender: async_broadcast::Sender<Event>,
entry_inserted: SignalEmitter<Self, usize>,
stopped: SignalEmitter<Self, ()>,
entry_removed: SignalEmitter<Self, usize>,
file_started: SignalEmitter<Self, ()>,
}
impl<E> Default for Playbin<E> {
fn default() -> Self {
impl<E> Playbin<E>
where
E: PlaybinEntry,
{
pub fn new(sender: async_broadcast::Sender<Event>) -> Self {
let mpv = mpv::Handle::new();
mpv.set_property("audio-client-name", "audrey").unwrap();
mpv.set_property("user-agent", crate::USER_AGENT).unwrap();
@ -53,24 +53,15 @@ impl<E> Default for Playbin<E> {
mpv,
entries: RefCell::new(vec![]),
volume_changed: Default::default(),
muted_changed: Default::default(),
paused_changed: Default::default(),
current_entry_changed: Default::default(),
sender,
entry_inserted: Default::default(),
stopped: Default::default(),
entry_removed: Default::default(),
file_started: Default::default(),
}
}
}
impl<E> Playbin<E>
where
E: PlaybinEntry,
{
pub fn volume(&self) -> i64 {
self.mpv.get_property("volume").unwrap()
}
@ -138,7 +129,9 @@ where
entries.push(entry);
drop(entries);
self.entry_inserted.emit(self, index);
self.sender
.try_broadcast(Event::PlaybinEntryInserted(index))
.unwrap();
}
pub fn insert_entry(&self, index: usize, entry: E) {
@ -149,7 +142,9 @@ where
entries.insert(index, entry);
drop(entries);
self.entry_inserted.emit(self, index);
self.sender
.try_broadcast(Event::PlaybinEntryInserted(index))
.unwrap();
}
// stop playback and clear playlist
@ -159,7 +154,7 @@ where
entries.clear();
drop(entries);
self.stopped.emit(self, ());
self.sender.try_broadcast(Event::PlaybinStopped).unwrap();
}
pub fn remove_entry(&self, index: usize) {
@ -170,7 +165,9 @@ where
entries.remove(index);
drop(entries);
self.entry_removed.emit(self, index);
self.sender
.try_broadcast(Event::PlaybinEntryRemoved(index))
.unwrap();
}
pub fn move_entry(&self, _from: usize, _to: usize) {
@ -192,25 +189,33 @@ where
mpv::Event::PropertyChange(event) => match event.reply_userdata {
0 => {
assert_eq!(&event.name, "volume");
self.volume_changed.emit(self, ());
self.sender
.try_broadcast(Event::PlaybinVolumeChanged)
.unwrap();
event!(Level::DEBUG, "volume change {}", self.volume());
}
1 => {
assert_eq!(&event.name, "mute");
self.muted_changed.emit(self, ());
self.sender
.try_broadcast(Event::PlaybinMutedChanged)
.unwrap();
event!(Level::DEBUG, "mute state change to {}", self.muted());
}
2 => {
assert_eq!(&event.name, "pause");
self.paused_changed.emit(self, ());
self.sender
.try_broadcast(Event::PlaybinPausedChanged)
.unwrap();
event!(Level::DEBUG, "pause state change to {}", self.paused());
}
3 => {
assert_eq!(&event.name, "playlist-pos");
self.current_entry_changed.emit(self, ());
self.sender
.try_broadcast(Event::PlaybinCurrentEntryChanged)
.unwrap();
event!(
Level::DEBUG,
"playlist-pos change {:?}",
@ -235,7 +240,9 @@ where
mpv::Event::StartFile(_) => {
// since we set up the hook before, the current song is guaranteed not to change
// under our feet
self.file_started.emit(self, ());
self.sender
.try_broadcast(Event::PlaybinFileStarted)
.unwrap();
// sanity check
assert_eq!(
@ -248,22 +255,6 @@ where
}
}
pub fn volume_changed(&self) -> Signal<'_, Self, ()> {
self.volume_changed.signal()
}
pub fn muted_changed(&self) -> Signal<'_, Self, ()> {
self.muted_changed.signal()
}
pub fn current_entry_changed(&self) -> Signal<'_, Self, ()> {
self.current_entry_changed.signal()
}
pub fn entry_inserted(&self) -> Signal<'_, Self, usize> {
self.entry_inserted.signal()
}
pub fn stopped(&self) -> Signal<'_, Self, ()> {
self.stopped.signal()
}

View file

@ -61,7 +61,7 @@ mod imp {
#[template_callback]
fn on_song_list_setup(&self, item: &gtk::ListItem, _factory: &gtk::SignalListItemFactory) {
let child = super::Song::new(self.playbin.borrow().as_ref().unwrap());
let child = super::Song::new(Some(&self.obj().window()));
child.set_draggable(true);
child.set_show_position(true);
@ -75,10 +75,7 @@ mod imp {
fn on_song_list_bind(&self, item: &gtk::ListItem, _factory: &gtk::SignalListItemFactory) {
let child = item.child().and_downcast::<super::Song>().unwrap();
child.bind(
item.position(),
item.item().unwrap().downcast_ref::<PlaybinSong>().unwrap(),
);
child.bind(item.position(), &self.obj().window());
}
#[template_callback]
@ -106,6 +103,7 @@ mod imp {
}
use crate::Playbin;
use adw::prelude::*;
use adw::subclass::prelude::*;
use gtk::glib;
use std::rc::Rc;
@ -124,15 +122,6 @@ impl PlayQueue {
.replace(Some(Rc::clone(playbin)))
.is_none()); // only set once
playbin
.entry_inserted()
.connect_object(self, |playbin, play_queue, index| {
play_queue
.model()
.unwrap()
.insert(index as u32, &playbin.entries()[index]);
true
});
playbin
.stopped()
.connect_object(self, |_playbin, play_queue, ()| {
@ -146,4 +135,8 @@ impl PlayQueue {
true
});
}
fn window(&self) -> crate::ui::Window {
self.root().unwrap().dynamic_cast().unwrap()
}
}

View file

@ -1,10 +1,8 @@
mod imp {
use crate::signal::SignalHandler;
use crate::{Playbin, PlaybinSong};
use crate::PlaybinSong;
use glib::subclass::InitializingObject;
use gtk::{gdk, gio, glib, prelude::*, subclass::prelude::*};
use std::cell::{Cell, RefCell};
use std::rc::Rc;
#[derive(gtk::CompositeTemplate, glib::Properties, Default)]
#[template(resource = "/eu/callcc/audrey/play_queue_song.ui")]
@ -21,6 +19,8 @@ mod imp {
#[property(set = Self::set_current, get)]
current: Cell<bool>,
#[property(set, get)]
position: Cell<u32>,
#[property(set, get)]
displayed_position: Cell<u32>,
@ -29,9 +29,6 @@ mod imp {
drag_pos: Cell<(i32, i32)>,
drag_widget: Cell<Option<gtk::ListBox>>,
pub(super) playbin: RefCell<Option<Rc<Playbin>>>,
pub(super) connection: Cell<SignalHandler>,
}
#[glib::object_subclass]
@ -62,6 +59,7 @@ mod imp {
move |_, _, _| {
self_
.obj()
.window()
.playbin()
.remove_entry(self_.obj().displayed_position() as usize - 1)
}
@ -123,7 +121,7 @@ mod imp {
fn on_drag_begin(&self, drag: &gdk::Drag) {
let drag_widget = gtk::ListBox::new();
let drag_row = super::Song::new(&self.obj().playbin());
let drag_row = super::Song::new(None);
drag_row.set_draggable(false);
drag_row.set_show_position(self.obj().show_position());
drag_row.set_show_artist(self.obj().show_artist());
@ -165,11 +163,10 @@ mod imp {
}
}
use crate::{Playbin, PlaybinSong};
use adw::subclass::prelude::*;
use crate::PlaybinSong;
use adw::prelude::*;
use glib::Object;
use gtk::glib;
use std::rc::Rc;
glib::wrapper! {
pub struct Song(ObjectSubclass<imp::Song>)
@ -178,38 +175,41 @@ glib::wrapper! {
}
impl Song {
pub fn new(playbin: &Rc<Playbin>) -> Self {
pub fn new(window: Option<&crate::ui::Window>) -> Self {
let song: Self = Object::new();
assert!(song
.imp()
.playbin
.replace(Some(Rc::clone(playbin)))
.is_none()); // only set once
if let Some(window) = window {
use crate::Event;
crate::event::spawn_object_listener(
window.receiver(),
&song,
|song, event| match event {
Event::PlaybinCurrentEntryChanged => {
song.set_current(
song.window().playbin().current_entry()
== Some(song.position() as usize),
);
}
_ => {}
},
);
}
song
}
fn playbin(&self) -> Rc<Playbin> {
Rc::clone(self.imp().playbin.borrow().as_ref().unwrap())
fn window(&self) -> crate::ui::Window {
self.root().unwrap().dynamic_cast().unwrap()
}
pub fn bind(&self, position: u32, song: &PlaybinSong) {
pub fn bind(&self, position: u32, window: &crate::ui::Window) {
self.set_displayed_position(position + 1);
self.set_song(song);
self.set_current(self.playbin().current_entry() == Some(position as usize));
self.imp()
.connection
.replace(self.playbin().current_entry_changed().connect_object(
self,
move |playbin, song, ()| {
song.set_current(playbin.current_entry() == Some(position as usize));
true
},
));
self.set_song(&window.playbin().entries()[position as usize]);
self.set_current(window.playbin().current_entry() == Some(position as usize));
self.set_position(position);
}
pub fn unbind(&self) {
self.imp().connection.take().disconnect();
}
pub fn unbind(&self) {}
}

View file

@ -8,7 +8,7 @@ mod imp {
use std::rc::Rc;
use tracing::{event, Level};
#[derive(gtk::CompositeTemplate, glib::Properties, Default)]
#[derive(gtk::CompositeTemplate, glib::Properties)]
#[template(resource = "/eu/callcc/audrey/window.ui")]
#[properties(wrapper_type = super::Window)]
pub struct Window {
@ -31,6 +31,28 @@ mod imp {
pub(super) playbin: Rc<Playbin>,
pub(super) api: RefCell<Option<Rc<crate::subsonic::Client>>>,
pub(super) sender: async_broadcast::Sender<crate::Event>,
pub(super) inactive_receiver: async_broadcast::InactiveReceiver<crate::Event>,
}
impl Default for Window {
fn default() -> Self {
let (sender, receiver) = async_broadcast::broadcast(100); // TODO: constantize
Self {
playbar: Default::default(),
play_queue: Default::default(),
can_click_shuffle_all: Cell::new(false),
playing_cover_art: Default::default(),
song: Default::default(),
setup: Default::default(),
playbin: Rc::new(Playbin::new(sender.clone())),
api: Default::default(),
sender,
inactive_receiver: receiver.deactivate(),
}
}
}
#[glib::object_subclass]
@ -54,6 +76,37 @@ mod imp {
fn constructed(&self) {
self.parent_constructed();
crate::event::spawn_object_listener(
self.inactive_receiver.activate_cloned(),
self.obj().as_ref(),
|window, event| {
use crate::Event;
match dbg!(event) {
Event::PlaybinVolumeChanged => {
window
.imp()
.playbar
.set_volume(window.playbin().volume() as i32);
}
Event::PlaybinMutedChanged => {
window.imp().playbar.set_mute(window.playbin().muted());
}
Event::PlaybinEntryInserted(index) => {
window
.imp()
.play_queue
.model()
.unwrap()
.insert(index as u32, &window.playbin().entries()[index]);
}
_ => {}
}
},
);
let playbin = Rc::downgrade(&self.playbin);
glib::spawn_future_local(glib::clone!(async move {
loop {
@ -197,13 +250,6 @@ impl Window {
.imp()
.playbar
.set_volume(window.imp().playbin.volume() as i32);
window.imp().playbin.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!(
@ -214,13 +260,6 @@ impl Window {
);
window.imp().playbar.set_mute(window.imp().playbin.muted());
window.imp().playbin.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!(
@ -282,4 +321,12 @@ impl Window {
window
}
pub fn playbin(&self) -> &crate::Playbin {
&self.imp().playbin
}
pub fn receiver(&self) -> async_broadcast::Receiver<crate::Event> {
self.imp().inactive_receiver.activate_cloned()
}
}