From 0e7baf704b6ff7a4c213690a7c87ae0641a9287c Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 22 Jul 2022 14:44:12 -0700 Subject: [PATCH] audio: Set icon based on volume and mute status of output --- applets/cosmic-applet-audio/src/main.rs | 22 ++++++-- applets/cosmic-applet-audio/src/pa/mod.rs | 55 ++++++++++--------- .../cosmic-applet-audio/src/volume_scale.rs | 3 + 3 files changed, 51 insertions(+), 29 deletions(-) diff --git a/applets/cosmic-applet-audio/src/main.rs b/applets/cosmic-applet-audio/src/main.rs index d36ea700..42850307 100644 --- a/applets/cosmic-applet-audio/src/main.rs +++ b/applets/cosmic-applet-audio/src/main.rs @@ -82,9 +82,8 @@ fn app(application: &Application) { set_application: Some(application), set_title: Some("COSMIC Network Applet"), #[wrap(Some)] - set_child = &libcosmic_applet::AppletButton { - // TODO: adjust based on volume, mute - set_button_icon_name: "multimedia-volume-control-symbolic", + set_child: button = &libcosmic_applet::AppletButton { + set_button_icon_name: "audio-volume-medium-symbolic", #[wrap(Some)] set_popover_child: window_box = &GtkBox { set_orientation: Orientation::Vertical, @@ -180,11 +179,26 @@ fn app(application: &Application) { }), ); glib::MainContext::default().spawn_local( - clone!(@weak outputs, @weak current_output, @weak output_volume, @strong pa => async move { + clone!(@weak outputs, @weak current_output, @weak output_volume, @strong pa, @strong button, => async move { while let Some(()) = refresh_output_rx.next().await { output::refresh_output_widgets(&pa, &outputs); let default_output = output::refresh_default_output(&pa, ¤t_output).await; volume::update_volume(&default_output, &output_volume); + button.set_button_icon_name({ + let volume = default_output.volume.avg().0 as f64 / Volume::NORMAL.0 as f64; + // XXX correct cutoffs? + if default_output.mute { + "audio-volume-muted" + } else if volume > 1.0 { + "audio-volume-overamplified-symbolic" + } else if volume > 0.66 { + "audio-volume-high-symbolic" + } else if volume > 0.33 { + "audio-volume-medium-symbolic" + } else { + "audio-volume-low-symbolic" + } + }); } }), ); diff --git a/applets/cosmic-applet-audio/src/pa/mod.rs b/applets/cosmic-applet-audio/src/pa/mod.rs index 32d9dca5..6872d907 100644 --- a/applets/cosmic-applet-audio/src/pa/mod.rs +++ b/applets/cosmic-applet-audio/src/pa/mod.rs @@ -2,7 +2,7 @@ use gtk4::glib; use libpulse_binding::{ callbacks::ListResult, context::{ - introspect::{Introspector, SinkInfo}, + introspect::{Introspector, SinkInfo, SourceInfo}, subscribe::{Facility, InterestMaskSet, Operation}, Context, FlagSet, State, }, @@ -19,9 +19,34 @@ pub struct DeviceInfo { pub name: Option, pub description: Option, pub volume: ChannelVolumes, + pub mute: bool, pub index: u32, } +impl<'a> From<&SinkInfo<'a>> for DeviceInfo { + fn from(info: &SinkInfo<'a>) -> Self { + Self { + name: info.name.clone().map(|x| x.into_owned()), + description: info.description.clone().map(|x| x.into_owned()), + volume: info.volume, + mute: info.mute, + index: info.index, + } + } +} + +impl<'a> From<&SourceInfo<'a>> for DeviceInfo { + fn from(info: &SourceInfo<'a>) -> Self { + Self { + name: info.name.clone().map(|x| x.into_owned()), + description: info.description.clone().map(|x| x.into_owned()), + volume: info.volume, + mute: info.mute, + index: info.index, + } + } +} + pub struct ServerInfo { pub default_sink_name: Option, pub default_source_name: Option, @@ -105,12 +130,7 @@ impl PA { PAFut::new(|waker| { self.introspect() .get_sink_info_list(move |result| match result { - ListResult::Item(item) => items.as_mut().unwrap().push(DeviceInfo { - name: item.name.clone().map(|x| x.into_owned()), - description: item.description.clone().map(|x| x.into_owned()), - volume: item.volume, - index: item.index, - }), + ListResult::Item(item) => items.as_mut().unwrap().push(DeviceInfo::from(item)), ListResult::End => waker.wake(Ok(items.take().unwrap())), ListResult::Error => waker.wake(Err(())), }) @@ -130,12 +150,7 @@ impl PA { self.introspect() .get_sink_info_by_name(&name, move |result| match result { ListResult::Item(item) => { - sink = Some(DeviceInfo { - name: item.name.clone().map(|x| x.into_owned()), - description: item.description.clone().map(|x| x.into_owned()), - volume: item.volume, - index: item.index, - }); + sink = Some(DeviceInfo::from(item)); } ListResult::End => waker.wake(sink.take().ok_or(())), ListResult::Error => waker.wake(Err(())), @@ -158,12 +173,7 @@ impl PA { PAFut::new(|waker| { self.introspect() .get_source_info_list(move |result| match result { - ListResult::Item(item) => items.as_mut().unwrap().push(DeviceInfo { - name: item.name.clone().map(|x| x.into_owned()), - description: item.description.clone().map(|x| x.into_owned()), - volume: item.volume, - index: item.index, - }), + ListResult::Item(item) => items.as_mut().unwrap().push(DeviceInfo::from(item)), ListResult::End => waker.wake(Ok(items.take().unwrap())), ListResult::Error => waker.wake(Err(())), }) @@ -183,12 +193,7 @@ impl PA { self.introspect() .get_source_info_by_name(&name, move |result| match result { ListResult::Item(item) => { - source = Some(DeviceInfo { - name: item.name.clone().map(|x| x.into_owned()), - description: item.description.clone().map(|x| x.into_owned()), - volume: item.volume, - index: item.index, - }); + source = Some(DeviceInfo::from(item)); } ListResult::End => waker.wake(source.take().ok_or(())), ListResult::Error => waker.wake(Err(())), diff --git a/applets/cosmic-applet-audio/src/volume_scale.rs b/applets/cosmic-applet-audio/src/volume_scale.rs index e1ea9fa1..56a54ce9 100644 --- a/applets/cosmic-applet-audio/src/volume_scale.rs +++ b/applets/cosmic-applet-audio/src/volume_scale.rs @@ -1,3 +1,6 @@ +// TODO: Use `Volume::ui_max()`? +// * Make sure volumes greater than this are handled properly. + use gtk4::{glib, prelude::*, subclass::prelude::*}; use libpulse_binding::volume::{ChannelVolumes, Volume}; use std::{