diff --git a/cosmic-applet-audio/src/lib.rs b/cosmic-applet-audio/src/lib.rs index 1f21666d..f3b7da89 100644 --- a/cosmic-applet-audio/src/lib.rs +++ b/cosmic-applet-audio/src/lib.rs @@ -66,10 +66,6 @@ pub struct Audio { current_input: Option, outputs: Vec, inputs: Vec, - sink_mute: bool, - sink_volume: u32, - source_mute: bool, - source_volume: u32, pulse_state: PulseState, popup: Option, timeline: Timeline, @@ -84,13 +80,15 @@ impl Audio { } fn output_icon_name(&self) -> &'static str { - if self.sink_mute || self.sink_volume == 0 { + let volume = self.current_output_volume_percent(); + let mute = self.current_output_mute(); + if mute || volume == 0. { "audio-volume-muted-symbolic" - } else if self.sink_volume < 33 { + } else if volume < 33. { "audio-volume-low-symbolic" - } else if self.sink_volume < 66 { + } else if volume < 66. { "audio-volume-medium-symbolic" - } else if self.sink_volume <= 100 { + } else if volume <= 100. { "audio-volume-high-symbolic" } else { "audio-volume-overamplified-symbolic" @@ -102,11 +100,13 @@ impl Audio { } fn input_icon_name(&self) -> &'static str { - if self.source_mute || self.source_volume == 0 { + let volume = self.current_input_volume_percent(); + let mute = self.current_input_mute(); + if mute || volume == 0. { "microphone-sensitivity-muted-symbolic" - } else if self.source_volume < 33 { + } else if volume < 33. { "microphone-sensitivity-low-symbolic" - } else if self.source_volume < 66 { + } else if volume < 66. { "microphone-sensitivity-medium-symbolic" } else { "microphone-sensitivity-high-symbolic" @@ -126,6 +126,8 @@ pub enum Message { Ignore, SetOutputVolume(f64), SetInputVolume(f64), + SetOutputMute(bool), + SetInputMute(bool), OutputToggle, InputToggle, OutputChanged(String), @@ -253,6 +255,38 @@ impl Audio { } }) } + + fn current_output_volume_percent(&self) -> f64 { + volume_to_percent( + self.current_output + .as_ref() + .map(|o| o.volume.avg()) + .unwrap_or_default(), + ) + } + + fn current_input_volume_percent(&self) -> f64 { + volume_to_percent( + self.current_input + .as_ref() + .map(|o| o.volume.avg()) + .unwrap_or_default(), + ) + } + + fn current_output_mute(&self) -> bool { + self.current_output + .as_ref() + .map(|o| o.mute) + .unwrap_or_default() + } + + fn current_input_mute(&self) -> bool { + self.current_input + .as_ref() + .map(|o| o.mute) + .unwrap_or_default() + } } impl cosmic::Application for Audio { @@ -358,6 +392,34 @@ impl cosmic::Application for Audio { } } } + Message::SetOutputMute(mute) => { + if let Some(output) = self.current_output.as_mut() { + output.mute = mute; + } + if let PulseState::Connected(connection) = &mut self.pulse_state { + if let Some(device) = &self.current_output { + if let Some(name) = &device.name { + connection + .send(pulse::Message::SetSinkMuteByName(name.clone(), device.mute)) + } + } + } + } + Message::SetInputMute(mute) => { + if let Some(input) = self.current_input.as_mut() { + input.mute = mute; + } + if let PulseState::Connected(connection) = &mut self.pulse_state { + if let Some(device) = &self.current_input { + if let Some(name) = &device.name { + connection.send(pulse::Message::SetSourceMuteByName( + name.clone(), + device.mute, + )) + } + } + } + } Message::OutputChanged(val) => { if let Some(conn) = self.pulse_state.connection() { if let Some(val) = self.outputs.iter().find(|o| o.name.as_ref() == Some(&val)) { @@ -539,16 +601,28 @@ impl cosmic::Application for Audio { }, Message::PulseSub(event) => match event { sub_pulse::Event::SinkVolume(value) => { - self.sink_volume = value; + self.current_output.as_mut().map(|output| { + output + .volume + .set(output.volume.len(), percent_to_volume(value as f64)) + }); } sub_pulse::Event::SinkMute(value) => { - self.sink_mute = value; + if let Some(output) = self.current_output.as_mut() { + output.mute = value; + } } sub_pulse::Event::SourceVolume(value) => { - self.source_volume = value; + self.current_input.as_mut().map(|input| { + input + .volume + .set(input.volume.len(), percent_to_volume(value as f64)) + }); } sub_pulse::Event::SourceMute(value) => { - self.source_mute = value; + if let Some(input) = self.current_input.as_mut() { + input.mute = value; + } } }, }; @@ -615,18 +689,10 @@ impl cosmic::Application for Audio { fn view_window(&self, _id: window::Id) -> Element { let audio_disabled = matches!(self.pulse_state, PulseState::Disconnected(_)); - let out_f64 = volume_to_percent( - self.current_output - .as_ref() - .map(|o| o.volume.avg()) - .unwrap_or_default(), - ); - let in_f64 = volume_to_percent( - self.current_input - .as_ref() - .map(|o| o.volume.avg()) - .unwrap_or_default(), - ); + let out_f64 = self.current_output_volume_percent(); + let in_f64 = self.current_input_volume_percent(); + let out_mute = self.current_output_mute(); + let in_mute = self.current_input_mute(); let mut audio_content = if audio_disabled { column![padded_control( @@ -639,9 +705,15 @@ impl cosmic::Application for Audio { column![ padded_control( row![ - icon::from_name(self.output_icon_name()) - .size(24) - .symbolic(true), + button::icon( + icon::from_name(self.output_icon_name()) + .size(24) + .symbolic(true), + ) + .style(cosmic::theme::Button::Icon) + .icon_size(24) + .line_height(24) + .on_press(Message::SetOutputMute(!out_mute)), slider(0.0..=100.0, out_f64, Message::SetOutputVolume) .width(Length::FillPortion(5)), text(format!("{}%", out_f64.round())) @@ -654,9 +726,15 @@ impl cosmic::Application for Audio { ), padded_control( row![ - icon::from_name(self.input_icon_name()) - .size(24) - .symbolic(true), + button::icon( + icon::from_name(self.input_icon_name()) + .size(24) + .symbolic(true), + ) + .style(cosmic::theme::Button::Icon) + .icon_size(24) + .line_height(24) + .on_press(Message::SetInputMute(!in_mute)), slider(0.0..=100.0, in_f64, Message::SetInputVolume) .width(Length::FillPortion(5)), text(format!("{}%", in_f64.round())) diff --git a/cosmic-applet-audio/src/pulse.rs b/cosmic-applet-audio/src/pulse.rs index c3c254c2..efe47cdf 100644 --- a/cosmic-applet-audio/src/pulse.rs +++ b/cosmic-applet-audio/src/pulse.rs @@ -173,6 +173,8 @@ pub enum Message { SetDefaultSource(DeviceInfo), SetSinkVolumeByName(String, ChannelVolumes), SetSourceVolumeByName(String, ChannelVolumes), + SetSinkMuteByName(String, bool), + SetSourceMuteByName(String, bool), } struct PulseHandle { @@ -290,6 +292,27 @@ impl PulseHandle { }; server.set_source_volume_by_name(&name, &channel_volumes) } + Message::SetSinkMuteByName(name, mute) => { + let server = match server.as_mut() { + Some(s) => s, + None => continue, + }; + + let op = + server.introspector.set_sink_mute_by_name(&name, mute, None); + server.wait_for_result(op).ok(); + } + Message::SetSourceMuteByName(name, mute) => { + let server = match server.as_mut() { + Some(s) => s, + None => continue, + }; + + let op = server + .introspector + .set_source_mute_by_name(&name, mute, None); + server.wait_for_result(op).ok(); + } Message::UpdateConnection => { tracing::info!( "Updating Connection, server exists: {:?}",