diff --git a/cosmic-applet-audio/src/main.rs b/cosmic-applet-audio/src/main.rs index d67469b1..92adb27d 100644 --- a/cosmic-applet-audio/src/main.rs +++ b/cosmic-applet-audio/src/main.rs @@ -133,6 +133,14 @@ impl Application for Audio { .min_width(1) .max_width(400) .max_height(1080); + + if let Some(conn) = self.pulse_state.connection() { + conn.send(pulse::Message::GetDefaultSink); + conn.send(pulse::Message::GetDefaultSource); + conn.send(pulse::Message::GetSinks); + conn.send(pulse::Message::GetSources); + } + return get_popup(popup_settings); } } @@ -169,12 +177,27 @@ impl Application for Audio { } } } - Message::OutputChanged(val) => log::info!("changed output {}", val), - Message::InputChanged(val) => log::info!("changed input {}", val), + 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)) { + conn.send(pulse::Message::SetDefaultSink(val.clone())); + } + } + } + Message::InputChanged(val) => { + if let Some(conn) = self.pulse_state.connection() { + if let Some(val) = self.inputs.iter().find(|i| i.name.as_ref() == Some(&val)) { + conn.send(pulse::Message::SetDefaultSource(val.clone())); + } + } + } Message::OutputToggle => { self.is_open = if self.is_open == IsOpen::Output { IsOpen::None } else { + if let Some(conn) = self.pulse_state.connection() { + conn.send(pulse::Message::GetSinks); + } IsOpen::Output } } @@ -182,6 +205,9 @@ impl Application for Audio { self.is_open = if self.is_open == IsOpen::Input { IsOpen::None } else { + if let Some(conn) = self.pulse_state.connection() { + conn.send(pulse::Message::GetSources); + } IsOpen::Input } } @@ -315,10 +341,13 @@ impl Application for Audio { self.outputs .clone() .into_iter() - .map(|output| pretty_name(output.description)) + .map(|output| ( + output.name.clone().unwrap_or_default(), + pretty_name(output.description) + )) .collect(), Message::OutputToggle, - Message::OutputChanged(String::from("test")), + Message::OutputChanged, ), revealer( self.is_open == IsOpen::Input, @@ -330,10 +359,13 @@ impl Application for Audio { self.inputs .clone() .into_iter() - .map(|input| pretty_name(input.description)) + .map(|input| ( + input.name.clone().unwrap_or_default(), + pretty_name(input.description) + )) .collect(), Message::InputToggle, - Message::InputChanged(String::from("test")), + Message::InputChanged, ) ] .align_items(Alignment::Start) @@ -372,14 +404,22 @@ fn revealer( open: bool, title: &str, selected: String, - options: Vec, + options: Vec<(String, String)>, toggle: Message, - _change: Message, + mut change: impl FnMut(String) -> Message + 'static, ) -> widget::Column { if open { options.iter().fold( column![revealer_head(open, title, selected, toggle)].width(Length::Fill), - |col, device| col.push(container(text(device)).padding([8, 48])), + |col, (id, name)| { + col.push( + button(APPLET_BUTTON_THEME) + .custom(vec![text(name).into()]) + .on_press(change(id.clone())) + .width(Length::Fill) + .padding([8, 48]), + ) + }, ) } else { column![revealer_head(open, title, selected, toggle)] diff --git a/cosmic-applet-audio/src/pulse.rs b/cosmic-applet-audio/src/pulse.rs index 25c541d5..a0f3307b 100644 --- a/cosmic-applet-audio/src/pulse.rs +++ b/cosmic-applet-audio/src/pulse.rs @@ -251,6 +251,44 @@ impl PulseHandle { server = Some(new_server); } } + Message::SetDefaultSink(device) => { + let server = match server.as_mut() { + Some(s) => s, + None => continue, + }; + let default_sink = match server.get_default_sink() { + Ok(sink) => sink, + Err(_) => continue, + }; + let to_move = server.get_sink_inputs(default_sink.index); + if let Some(name) = device.name.as_ref() { + if server.set_default_sink(name, to_move) { + from_pulse_send + .send(Message::SetDefaultSink(device)) + .await + .unwrap(); + } + } + } + Message::SetDefaultSource(device) => { + let server = match server.as_mut() { + Some(s) => s, + None => continue, + }; + let default_source = match server.get_default_source() { + Ok(source) => source, + Err(_) => continue, + }; + let to_move = server.get_source_outputs(default_source.index); + if let Some(name) = device.name.as_ref() { + if server.set_default_source(name, to_move) { + from_pulse_send + .send(Message::SetDefaultSource(device)) + .await + .unwrap(); + } + } + } _ => { log::warn!("message doesn't match") } @@ -408,6 +446,67 @@ impl PulseServer { .ok_or(PulseServerError::Misc("get_server_info(): failed")) } + fn set_default_sink(&mut self, sink: &str, to_move: Vec) -> bool { + let set_default_success = Rc::new(RefCell::new(false)); + let set_default_success_ref = set_default_success.clone(); + let op = self + .context + .borrow_mut() + .set_default_sink(sink, move |ret| { + *set_default_success.borrow_mut() = ret; + }); + self.wait_for_result(op).ok(); + if !set_default_success_ref.replace(true) { + return false; + } + + for index in to_move { + let move_success = Rc::new(RefCell::new(false)); + let op = self.introspector.move_sink_input_by_name( + index, + sink, + Some(Box::new(move |ret| { + *move_success.borrow_mut() = ret; + })), + ); + + self.wait_for_result(op).ok(); + } + // TODO handle errors + true + } + + fn set_default_source(&mut self, sink: &str, to_move: Vec) -> bool { + let set_default_success = Rc::new(RefCell::new(false)); + let set_default_success_ref = set_default_success.clone(); + let op = self + .context + .borrow_mut() + .set_default_source(sink, move |ret| { + *set_default_success.borrow_mut() = ret; + }); + self.wait_for_result(op).ok(); + + if !set_default_success_ref.replace(true) { + return false; + } + + for index in to_move { + let move_success = Rc::new(RefCell::new(false)); + let op = self.introspector.move_source_output_by_name( + index, + sink, + Some(Box::new(move |ret| { + *move_success.borrow_mut() = ret; + })), + ); + + self.wait_for_result(op).ok(); + } + + true + } + fn get_default_sink(&mut self) -> Result { let server_info = self.get_server_info(); match server_info { @@ -472,6 +571,34 @@ impl PulseServer { self.wait_for_result(op).ok(); } + fn get_source_outputs(&mut self, source: u32) -> Vec { + let result = Rc::new(RefCell::new(Vec::new())); + let result_ref = Rc::new(RefCell::new(Vec::new())); + let op = self.introspector.get_source_output_info_list(move |list| { + if let ListResult::Item(item) = list { + if source == item.source { + result.borrow_mut().push(item.index); + } + } + }); + self.wait_for_result(op).ok(); + result_ref.replace(Vec::new()) + } + + fn get_sink_inputs(&mut self, sink: u32) -> Vec { + let result = Rc::new(RefCell::new(Vec::new())); + let result_ref = Rc::new(RefCell::new(Vec::new())); + let op = self.introspector.get_sink_input_info_list(move |list| { + if let ListResult::Item(item) = list { + if sink == item.sink { + result.borrow_mut().push(item.index); + } + } + }); + self.wait_for_result(op).ok(); + result_ref.replace(Vec::new()) + } + // after building an operation such as get_devices() we need to keep polling // the pulse audio server to "wait" for the operation to complete fn wait_for_result(