diff --git a/applets/cosmic-applet-audio/src/main.rs b/applets/cosmic-applet-audio/src/main.rs index 8249b47f..ebb0c722 100644 --- a/applets/cosmic-applet-audio/src/main.rs +++ b/applets/cosmic-applet-audio/src/main.rs @@ -11,6 +11,7 @@ mod pa; use pa::PA; mod task; mod volume; +mod volume_scale; use futures::{channel::mpsc, stream::StreamExt}; use gtk4::{ diff --git a/applets/cosmic-applet-audio/src/pa/mod.rs b/applets/cosmic-applet-audio/src/pa/mod.rs index ab80e0cd..32d9dca5 100644 --- a/applets/cosmic-applet-audio/src/pa/mod.rs +++ b/applets/cosmic-applet-audio/src/pa/mod.rs @@ -197,7 +197,11 @@ impl PA { .await } - pub async fn set_sink_volume_by_name(&self, name: &str, volume: &ChannelVolumes) -> bool { + pub fn set_sink_volume_by_name( + &self, + name: &str, + volume: &ChannelVolumes, + ) -> PAFut { PAFut::new(|waker| { self.introspect().set_sink_volume_by_name( name, @@ -205,10 +209,13 @@ impl PA { Some(Box::new(move |success| waker.wake(success))), ) }) - .await } - pub async fn set_source_volume_by_name(&self, name: &str, volume: &ChannelVolumes) -> bool { + pub fn set_source_volume_by_name( + &self, + name: &str, + volume: &ChannelVolumes, + ) -> PAFut { PAFut::new(|waker| { self.introspect().set_source_volume_by_name( name, @@ -216,6 +223,5 @@ impl PA { Some(Box::new(move |success| waker.wake(success))), ) }) - .await } } diff --git a/applets/cosmic-applet-audio/src/volume_scale.rs b/applets/cosmic-applet-audio/src/volume_scale.rs new file mode 100644 index 00000000..822a8795 --- /dev/null +++ b/applets/cosmic-applet-audio/src/volume_scale.rs @@ -0,0 +1,93 @@ +use gtk4::{glib, prelude::*}; +use libpulse_binding::volume::{ChannelVolumes, Volume}; +use std::{ + cell::{Cell, RefCell}, + future::Future, + pin::Pin, + rc::Rc, +}; + +use crate::PA; + +// Component + +struct VolumeScale { + scale: gtk4::Scale, + name: Rc>>, +} + +impl VolumeScale { + fn new(pa: PA, sink: bool) { + let name: Rc>> = Rc::new(RefCell::new(None)); + let scale = gtk4::Scale::with_range(gtk4::Orientation::Horizontal, 0., 100., 1.); + let updater = Updater::new(move |value: f64| { + let name = name.clone(); + let pa = pa.clone(); + async move { + let mut volumes = ChannelVolumes::default(); + volumes.set(0, Volume((value * 100.) as _)); // XXX ? + + let name_ref = name.borrow(); + if let Some(name) = name_ref.as_deref() { + if sink { + let fut = pa.set_sink_volume_by_name(name, &volumes); + drop(name_ref); + fut.await; + } else { + let fut = pa.set_source_volume_by_name(name, &volumes); + drop(name_ref); + fut.await; + } + } + } + }); + scale.connect_change_value(move |_scale, _scroll, value| { + updater.update(value); + gtk4::Inhibit(false) + }); + } + + fn set_value(&self, value: f64) { + self.scale.set_value(value); + } + + fn set_name(&self, name: Option) { + *self.name.borrow_mut() = name; + } +} + +// Perform an asynchronous update operation without queuing more than one set. +struct Updater { + updating: Rc>, + value: Rc>>, + update_fn: Rc Pin + 'static>>>, +} + +impl Updater { + fn new + 'static, F: Fn(T) -> Fut + 'static>(f: F) -> Self { + let value = Rc::new(Cell::new(None)); + let updating = Rc::new(Cell::new(false)); + let update_fn = + Rc::new(move |value| Box::pin(f(value)) as Pin>>); + Self { + updating, + value, + update_fn, + } + } + + fn update(&self, value: T) { + self.value.set(Some(value)); + if self.updating.replace(true) == false { + let value = self.value.clone(); + let updating = self.updating.clone(); + let update_fn = self.update_fn.clone(); + glib::MainContext::default().spawn_local(async move { + while let Some(value) = value.take() { + update_fn(value).await; + } + updating.set(false); + }); + } + } +}