diff --git a/Cargo.toml b/Cargo.toml index 85d6655..3ad810d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,9 @@ repository = "https://github.com/Xetibo/ReSet" license = "GPL-3.0-or-later" [dependencies] -#reset_daemon = "1.1.0" -re_set-lib = { git = "https://github.com/Xetibo/ReSet-Lib" } +# reset_daemon = "1.1.0" +re_set-lib = { git = "https://github.com/Xetibo/ReSet-Lib" , branch = "audioobject"} +# re_set-lib = "3.1.7" reset_daemon = { git = "https://github.com/Xetibo/ReSet-Daemon", branch = "dashie" } adw = { version = "0.6.0", package = "libadwaita", features = ["v1_4"] } dbus = "0.9.7" diff --git a/src/components/audio/audio_box_handlers.rs b/src/components/audio/audio_box_handlers.rs new file mode 100644 index 0000000..43f5855 --- /dev/null +++ b/src/components/audio/audio_box_handlers.rs @@ -0,0 +1,590 @@ +use std::{ + sync::Arc, + time::{Duration, SystemTime}, +}; + +use adw::prelude::{ComboRowExt, PreferencesRowExt}; +use dbus::arg::{Arg, Get}; +use glib::{ + object::{Cast, IsA}, + ControlFlow, Propagation, +}; +use gtk::{ + gio, + prelude::{BoxExt, ButtonExt, CheckButtonExt, ListBoxRowExt, RangeExt}, + StringObject, +}; +use re_set_lib::{ + audio::audio_structures::{TAudioObject, TAudioStreamObject}, + signals::{TAudioEventRemoved, TAudioObjectEvent, TAudioStreamEvent}, +}; + +use crate::components::base::{error_impl::ReSetErrorImpl, list_entry::ListEntry}; + +use super::{ + audio_box_utils::{ + populate_audio_object_information, populate_cards, populate_streams, + refresh_default_audio_object, + }, + audio_entry::{ + new_entry, DBusFunction, TAudioBox, TAudioBoxImpl, TAudioEntry, TAudioEntryImpl, + TAudioStream, TAudioStreamImpl, + }, + audio_functions::new_stream_entry, + audio_utils::audio_dbus_call, +}; + +pub fn mute_clicked_handler< + AudioObject: TAudioObject, + StreamObject: TAudioStreamObject, + AudioEntry: TAudioEntry, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox + ReSetErrorImpl + 'static, + AudioBoxImpl: TAudioBoxImpl, +>( + audio_box: Arc, + function: &'static DBusFunction, +) { + let imp = audio_box.box_imp(); + let source = imp.default_audio_object(); + let mut source = source.borrow_mut(); + source.toggle_muted(); + let icons = imp.icons(); + let mute_button = imp.audio_object_mute(); + if source.muted() { + mute_button.set_icon_name(icons.muted); + } else { + mute_button.set_icon_name(icons.active); + } + audio_dbus_call::( + audio_box.clone(), + (source.index(), source.muted()), + function, + ); +} + +pub fn volume_slider_handler< + AudioObject: TAudioObject, + StreamObject: TAudioStreamObject, + AudioEntry: TAudioEntry, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox + ReSetErrorImpl + 'static, + AudioBoxImpl: TAudioBoxImpl, +>( + audio_box: Arc, + value: f64, + function: &'static DBusFunction, +) -> Propagation { + let imp = audio_box.box_imp(); + let fraction = (value / 655.36).round(); + let percentage = (fraction).to_string() + "%"; + imp.volume_percentage().set_text(&percentage); + let source = imp.default_audio_object(); + let source = source.borrow(); + let index = source.index(); + let channels = source.channels(); + { + let mut time = imp.volume_time_stamp().borrow_mut(); + if time.is_some() && time.unwrap().elapsed().unwrap() < Duration::from_millis(50) { + return Propagation::Proceed; + } + *time = Some(SystemTime::now()); + } + audio_dbus_call::( + audio_box.clone(), + (index, channels, value as u32), + function, + ); + Propagation::Proceed +} + +pub fn dropdown_handler< + AudioObject: TAudioObject + Send + Sync, + StreamObject: TAudioStreamObject + Send + Sync, + AudioEntry: TAudioEntry, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream + IsA, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox + ReSetErrorImpl + 'static, + AudioBoxImpl: TAudioBoxImpl, +>( + audio_box: Arc, + dropdown: &adw::ComboRow, + function: &'static DBusFunction, +) -> ControlFlow { + let source_box_imp = audio_box.box_imp(); + let source_box_ref = audio_box.clone(); + let selected = dropdown.selected_item(); + if selected.is_none() { + return ControlFlow::Break; + } + let selected = selected.unwrap(); + let selected = selected.downcast_ref::().unwrap(); + let selected = selected.string().to_string(); + let source_map = source_box_imp.source_map(); + let source_map = source_map.read().unwrap(); + let source = source_map.get(&selected); + if source.is_none() { + return ControlFlow::Break; + } + let source = Arc::new(source.unwrap().1.clone()); + gio::spawn_blocking(move || { + let result = audio_dbus_call::( + source_box_ref.clone(), + (&source,), + function, + ); + if result.is_none() { + return ControlFlow::Break; + } + refresh_default_audio_object::< + AudioObject, + StreamObject, + AudioEntry, + AudioEntryImpl, + AudioStream, + AudioStreamImpl, + AudioBox, + AudioBoxImpl, + >(result.unwrap().0, source_box_ref.clone(), false); + ControlFlow::Continue + }); + ControlFlow::Continue +} + +pub fn populate_audio_objects< + AudioObject: TAudioObject + Arg + for<'z> Get<'z> + Send + Sync + 'static, + StreamObject: TAudioStreamObject + Send + Sync + for<'z> Get<'z> + Arg + 'static, + AudioEntry: TAudioEntry + IsA, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream + IsA, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox + ReSetErrorImpl + 'static, + AudioBoxImpl: TAudioBoxImpl, +>( + audio_box: Arc, + audio_objects_function: &'static DBusFunction, + default_audio_object_function: &'static DBusFunction, + set_default_audio_object_function: &'static DBusFunction, + get_audio_streams_function: &'static DBusFunction, + set_audio_object_mute_function: &'static DBusFunction, + set_audio_object_volume_function: &'static DBusFunction, +) { + gio::spawn_blocking(move || { + let sources = audio_dbus_call::,), ()>( + audio_box.clone(), + (), + audio_objects_function, + ); + if sources.is_none() { + return; + } + let audio_objects = sources.unwrap().0; + { + let imp = audio_box.box_imp(); + let list = imp.model_list(); + let list = list.write().unwrap(); + let map = imp.source_map(); + let mut map = map.write().unwrap(); + let model_index = imp.model_index(); + let mut model_index = model_index.write().unwrap(); + + let audio_object = audio_dbus_call::( + audio_box.clone(), + (), + default_audio_object_function, + ); + if let Some(audio_object) = audio_object { + imp.default_audio_object().replace(audio_object.0); + } + + for audio_object in audio_objects.iter() { + let alias = audio_object.alias(); + list.append(&alias); + map.insert( + alias.clone(), + (audio_object.index(), audio_object.name().clone()), + ); + *model_index += 1; + } + } + populate_streams::< + AudioObject, + StreamObject, + AudioEntry, + AudioEntryImpl, + AudioStream, + AudioStreamImpl, + AudioBox, + AudioBoxImpl, + >(audio_box.clone(), get_audio_streams_function); + populate_cards::< + AudioObject, + StreamObject, + AudioEntry, + AudioEntryImpl, + AudioStream, + AudioStreamImpl, + AudioBox, + AudioBoxImpl, + >(audio_box.clone()); + populate_audio_object_information::< + AudioObject, + StreamObject, + AudioEntry, + AudioEntryImpl, + AudioStream, + AudioStreamImpl, + AudioBox, + AudioBoxImpl, + >( + audio_box, + audio_objects, + set_default_audio_object_function, + set_audio_object_volume_function, + set_audio_object_mute_function, + ); + }); +} + +pub fn object_added_handler< + AudioObject: TAudioObject, + StreamObject: TAudioStreamObject, + AudioEntry: TAudioEntry + IsA, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream + IsA, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox + ReSetErrorImpl + 'static, + AudioBoxImpl: TAudioBoxImpl, + Event: TAudioObjectEvent, +>( + audio_box: Arc, + ir: Event, +) -> bool { + glib::spawn_future(async move { + glib::idle_add_once(move || { + let audio_box = audio_box.clone(); + let source_box_imp = audio_box.box_imp(); + let object = ir.object_ref(); + let object_index = object.index(); + let alias = object.alias().clone(); + let name = object.name().clone(); + let mut is_default = false; + if source_box_imp.default_audio_object().borrow().name() == object.name() { + is_default = true; + } + let source_entry = new_entry::< + AudioObject, + StreamObject, + AudioEntry, + AudioEntryImpl, + AudioStream, + AudioStreamImpl, + AudioBox, + AudioBoxImpl, + >( + is_default, + source_box_imp.default_check_button().clone(), + ir.object(), + audio_box.clone(), + ); + let source_clone = source_entry.clone(); + let entry = Arc::new(ListEntry::new(&*source_entry)); + entry.set_activatable(false); + let list = source_box_imp.audio_object_list(); + let mut list = list.write().unwrap(); + list.insert(object_index, (entry.clone(), source_clone, alias.clone())); + source_box_imp.audio_objects().append(&*entry); + let map = source_box_imp.source_map(); + let mut map = map.write().unwrap(); + let index = source_box_imp.model_index(); + let mut index = index.write().unwrap(); + let model_list = source_box_imp.model_list(); + let model_list = model_list.write().unwrap(); + // TODO: make this work generic! + if model_list.string(*index - 1) == Some("Monitor of Dummy Output".into()) { + model_list.append(&alias); + model_list.remove(*index - 1); + map.insert(alias, (object_index, name)); + source_box_imp.audio_object_dropdown().set_selected(0); + } else { + model_list.append(&alias); + map.insert(alias.clone(), (object_index, name)); + // TODO: make this work generic! + if alias == "Monitor of Dummy Output" { + source_box_imp.audio_object_dropdown().set_selected(0); + } + *index += 1; + } + }); + }); + true +} + +pub fn object_changed_handler< + AudioObject: TAudioObject, + StreamObject: TAudioStreamObject, + AudioEntry: TAudioEntry + IsA, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream + IsA, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox + ReSetErrorImpl + 'static, + AudioBoxImpl: TAudioBoxImpl, + Event: TAudioObjectEvent, +>( + audio_box: Arc, + ir: Event, + function: &'static DBusFunction, +) -> bool { + let source = audio_dbus_call::(audio_box.clone(), (), function); + if source.is_none() { + return false; + } + let default_source = source.unwrap().0; + glib::spawn_future(async move { + glib::idle_add_once(move || { + let audio_box = audio_box.clone(); + let box_imp = audio_box.box_imp(); + let object = ir.object_ref(); + let is_default = object.name() == default_source; + let volume = object.volume(); + let volume = volume.first().unwrap_or(&0_u32); + let fraction = (*volume as f64 / 655.36).round(); + let percentage = (fraction).to_string() + "%"; + + let list = box_imp.audio_object_list(); + let list = list.read().unwrap(); + let entry = list.get(&object.index()); + if entry.is_none() { + return; + } + let imp = entry.unwrap().1.entry_imp(); + if is_default { + box_imp.volume_percentage().set_text(&percentage); + box_imp.volume_slider().set_value(*volume as f64); + box_imp.default_audio_object().replace(ir.object()); + let icons = imp.icons(); + let mute_button = imp.mute(); + if object.muted() { + mute_button.set_icon_name(icons.muted); + } else { + mute_button.set_icon_name(icons.active); + } + imp.selected_audio_object().set_active(true); + } else { + imp.selected_audio_object().set_active(false); + } + imp.name().set_title(object.alias().as_str()); + imp.volume_percentage().set_text(&percentage); + imp.volume_slider().set_value(*volume as f64); + let mute_button = imp.mute(); + let icons = imp.icons(); + if object.muted() { + mute_button.set_icon_name(icons.muted); + } else { + mute_button.set_icon_name(icons.active); + } + }); + }); + true +} + +pub fn object_removed_handler< + AudioObject: TAudioObject, + StreamObject: TAudioStreamObject, + AudioEntry: TAudioEntry + IsA, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream + IsA, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox + ReSetErrorImpl + 'static, + AudioBoxImpl: TAudioBoxImpl, + Event: TAudioEventRemoved, +>( + audio_box: Arc, + ir: Event, +) -> bool { + glib::spawn_future(async move { + glib::idle_add_once(move || { + let audio_box = audio_box.clone(); + let box_imp = audio_box.box_imp(); + let entry: Option<(Arc, Arc, String)>; + { + let list = box_imp.audio_object_list(); + let mut list = list.write().unwrap(); + entry = list.remove(&ir.index()); + if entry.is_none() { + return; + } + } + box_imp.audio_objects().remove(&*entry.clone().unwrap().0); + let map = box_imp.source_map(); + let mut map = map.write().unwrap(); + let alias = entry.unwrap().2; + map.remove(&alias); + let index = box_imp.model_index(); + let mut index = index.write().unwrap(); + let model_list = box_imp.model_list(); + let model_list = model_list.write().unwrap(); + + if *index == 1 { + // TODO: ensure dummy output and input are mentioned + model_list.append("Dummy"); + } + for entry in 0..*index { + if model_list.string(entry) == Some(alias.clone().into()) { + model_list.splice(entry, 1, &[]); + break; + } + } + if *index > 1 { + *index -= 1; + } + }); + }); + true +} + +pub fn audio_stream_added_handler< + AudioObject: TAudioObject, + StreamObject: TAudioStreamObject, + AudioEntry: TAudioEntry + IsA, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream + IsA, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox + ReSetErrorImpl + 'static, + AudioBoxImpl: TAudioBoxImpl, + Event: TAudioStreamEvent, +>( + audio_box: Arc, + ir: Event, +) -> bool { + glib::spawn_future(async move { + glib::idle_add_once(move || { + let audio_box = audio_box.clone(); + let imp = audio_box.box_imp(); + let list = imp.audio_object_stream_list(); + let mut list = list.write().unwrap(); + let index = ir.stream_ref().index(); + let stream = new_stream_entry::< + AudioObject, + StreamObject, + AudioEntry, + AudioEntryImpl, + AudioStream, + AudioStreamImpl, + AudioBox, + AudioBoxImpl, + >(audio_box.clone(), ir.stream()); + let entry = Arc::new(ListEntry::new(&*stream)); + entry.set_activatable(false); + list.insert(index, (entry.clone(), stream.clone())); + imp.audio_object_streams().append(&*entry); + }); + }); + true +} + +pub fn audio_stream_changed_handler< + AudioObject: TAudioObject, + StreamObject: TAudioStreamObject, + AudioEntry: TAudioEntry + IsA, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream + IsA, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox + ReSetErrorImpl + 'static, + AudioBoxImpl: TAudioBoxImpl, + Event: TAudioStreamEvent, +>( + audio_box: Arc, + ir: Event, +) -> bool { + let imp = audio_box.box_imp(); + let alias: String; + { + let stream = ir.stream_ref(); + let object_list = imp.audio_object_list(); + let object_list = object_list.read().unwrap(); + if let Some(alias_opt) = object_list.get(&stream.audio_object_index()) { + alias = alias_opt.2.clone(); + } else { + alias = String::from(""); + } + } + glib::spawn_future(async move { + glib::idle_add_once(move || { + let audio_box = audio_box.clone(); + let box_imp = audio_box.box_imp(); + let entry: Arc; + let stream = ir.stream_ref(); + { + let list = box_imp.audio_object_stream_list(); + let list = list.read().unwrap(); + let entry_opt = list.get(&stream.index()); + if entry_opt.is_none() { + return; + } + entry = entry_opt.unwrap().1.clone(); + } + let imp = entry.entry_imp(); + let mute_button = imp.audio_object_mute(); + let icons = imp.icons(); + if stream.muted() { + mute_button.set_icon_name(icons.muted); + } else { + mute_button.set_icon_name(icons.active); + } + let name = stream.application_name().clone() + ": " + stream.name().as_str(); + imp.audio_object_selection().set_title(name.as_str()); + let volume = stream.volume(); + let volume = volume.first().unwrap_or(&0_u32); + let fraction = (*volume as f64 / 655.36).round(); + let percentage = (fraction).to_string() + "%"; + imp.volume_percentage().set_text(&percentage); + imp.volume_slider().set_value(*volume as f64); + let index = box_imp.model_index(); + let index = index.read().unwrap(); + let model_list = box_imp.model_list(); + let model_list = model_list.read().unwrap(); + for entry in 0..*index { + if model_list.string(entry) == Some(alias.clone().into()) { + imp.audio_object_selection().set_selected(entry); + break; + } + } + }); + }); + true +} + +pub fn audio_stream_removed_handler< + AudioObject: TAudioObject, + StreamObject: TAudioStreamObject, + AudioEntry: TAudioEntry + IsA, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream + IsA, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox + ReSetErrorImpl + 'static, + AudioBoxImpl: TAudioBoxImpl, + Event: TAudioEventRemoved, +>( + audio_box: Arc, + ir: Event, +) -> bool { + glib::spawn_future(async move { + glib::idle_add_once(move || { + let imp = audio_box.box_imp(); + let list = imp.audio_object_stream_list(); + let mut list = list.write().unwrap(); + let entry = list.remove(&ir.index()); + if entry.is_none() { + return; + } + imp.audio_object_streams().remove(&*entry.unwrap().0); + }); + }); + true +} diff --git a/src/components/audio/audio_box_utils.rs b/src/components/audio/audio_box_utils.rs new file mode 100644 index 0000000..fffa095 --- /dev/null +++ b/src/components/audio/audio_box_utils.rs @@ -0,0 +1,465 @@ +use std::sync::Arc; + +use adw::{prelude::ComboRowExt, prelude::PreferencesGroupExt}; +use dbus::{ + arg::{Arg, Get, ReadAll}, + blocking::Connection, + message::SignalArgs, + Path, +}; +use glib::{object::IsA, Variant}; +use gtk::{ + gio, + prelude::{ActionableExt, BoxExt, ButtonExt, CheckButtonExt, ListBoxRowExt, RangeExt}, +}; +use re_set_lib::{ + audio::audio_structures::{Card, TAudioObject, TAudioStreamObject}, + signals::{TAudioEventRemoved, TAudioObjectEvent, TAudioStreamEvent}, +}; + +use crate::components::{ + base::{card_entry::CardEntry, error_impl::ReSetErrorImpl, list_entry::ListEntry}, + utils::{create_dropdown_label_factory, set_combo_row_ellipsis, BASE, DBUS_PATH}, +}; + +use super::{ + audio_box_handlers::{ + audio_stream_added_handler, audio_stream_changed_handler, audio_stream_removed_handler, + dropdown_handler, mute_clicked_handler, object_added_handler, object_changed_handler, + object_removed_handler, volume_slider_handler, + }, + audio_const::GETCARDS, + audio_entry::{ + new_entry, DBusFunction, TAudioBox, TAudioBoxImpl, TAudioEntry, TAudioEntryImpl, + TAudioStream, TAudioStreamImpl, + }, + audio_functions::new_stream_entry, + audio_utils::audio_dbus_call, +}; + +pub fn setup_audio_box_callbacks< + AudioObject: TAudioObject, + StreamObject: TAudioStreamObject, + AudioEntry: TAudioEntry, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox, + AudioBoxImpl: TAudioBoxImpl, +>( + audio_box: &mut AudioBox, +) { + let imp = audio_box.box_imp(); + let object_row = imp.audio_object_row(); + object_row.set_activatable(true); + object_row.set_action_name(Some("navigation.push")); + object_row.set_action_target_value(Some(&Variant::from("devices"))); + + let cards_row = imp.cards_row(); + cards_row.set_activatable(true); + cards_row.set_action_name(Some("navigation.push")); + cards_row.set_action_target_value(Some(&Variant::from("profileConfiguration"))); + + let stream_button = imp.audio_object_stream_button(); + stream_button.set_activatable(true); + stream_button.set_action_name(Some("navigation.pop")); + + let cards_back_button = imp.cards_button(); + cards_back_button.set_activatable(true); + cards_back_button.set_action_name(Some("navigation.pop")); + + let audio_object_dropdown = imp.audio_object_dropdown(); + audio_object_dropdown.set_factory(Some(&create_dropdown_label_factory())); + set_combo_row_ellipsis(audio_object_dropdown.get()); +} + +pub fn populate_cards< + AudioObject: TAudioObject, + StreamObject: TAudioStreamObject, + AudioEntry: TAudioEntry, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox + ReSetErrorImpl + 'static, + AudioBoxImpl: TAudioBoxImpl, +>( + source_box: Arc, +) { + gio::spawn_blocking(move || { + let source_box_ref = source_box.clone(); + let cards = + audio_dbus_call::,), ()>(source_box.clone(), (), &GETCARDS); + if cards.is_none() { + return; + } + let cards = cards.unwrap().0; + glib::spawn_future(async move { + glib::idle_add_once(move || { + let imp = source_box_ref.box_imp(); + for card in cards { + imp.cards().add(&CardEntry::new(card)); + } + }); + }); + }); +} + +pub fn populate_streams< + AudioObject: TAudioObject + Sync + Send + 'static, + StreamObject: TAudioStreamObject + Arg + for<'z> Get<'z> + Sync + Send + 'static, + AudioEntry: TAudioEntry, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream + IsA, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox + ReSetErrorImpl + 'static, + AudioBoxImpl: TAudioBoxImpl, +>( + audio_box: Arc, + function: &'static DBusFunction, +) { + let audio_box_ref = audio_box.clone(); + gio::spawn_blocking(move || { + let streams = + audio_dbus_call::,), ()>(audio_box.clone(), (), function); + if streams.is_none() { + return; + } + let streams = streams.unwrap().0; + glib::spawn_future(async move { + glib::idle_add_once(move || { + let imp = audio_box_ref.box_imp(); + let mut list = imp.audio_object_stream_list().write().unwrap(); + for stream in streams { + let index = stream.index(); + let stream = new_stream_entry::< + AudioObject, + StreamObject, + AudioEntry, + AudioEntryImpl, + AudioStream, + AudioStreamImpl, + AudioBox, + AudioBoxImpl, + >(audio_box.clone(), stream); + let stream_clone = stream.clone(); + let entry = Arc::new(ListEntry::new(&*stream)); + entry.set_activatable(false); + list.insert(index, (entry.clone(), stream_clone)); + imp.audio_object_streams().append(&*entry); + } + }); + }); + }); +} + +pub fn refresh_default_audio_object< + AudioObject: TAudioObject + Sync + Send + 'static, + StreamObject: TAudioStreamObject + Arg + for<'z> Get<'z> + Sync + Send + 'static, + AudioEntry: TAudioEntry, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream + IsA, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox + ReSetErrorImpl + 'static, + AudioBoxImpl: TAudioBoxImpl, +>( + new_audio_object: AudioObject, + audio_box: Arc, + entry: bool, +) { + let volume = *new_audio_object.volume().first().unwrap_or(&0_u32); + let fraction = (volume as f64 / 655.36).round(); + let percentage = (fraction).to_string() + "%"; + glib::spawn_future(async move { + glib::idle_add_once(move || { + let imp = audio_box.box_imp(); + if !entry { + let list = imp.audio_object_list().read().unwrap(); + let entry = list.get(&new_audio_object.index()); + if entry.is_none() { + return; + } + let entry_imp = entry.unwrap().1.entry_imp(); + entry_imp.selected_audio_object().set_active(true); + } else { + let model_list = imp.model_list(); + let model_list = model_list.read().unwrap(); + for entry in 0..*imp.model_index().read().unwrap() { + if model_list.string(entry) == Some(new_audio_object.alias().clone().into()) { + imp.audio_object_dropdown().set_selected(entry); + break; + } + } + } + imp.volume_percentage().set_text(&percentage); + imp.volume_slider().set_value(volume as f64); + let icons = imp.icons(); + let mute_button = imp.audio_object_mute(); + if new_audio_object.muted() { + mute_button.set_icon_name(icons.muted); + } else { + mute_button.set_icon_name(icons.active); + } + imp.default_audio_object().replace(new_audio_object); + }); + }); +} + +pub fn populate_audio_object_information< + AudioObject: TAudioObject + Sync + Send + 'static + Arg + for<'z> Get<'z>, + StreamObject: TAudioStreamObject + Arg + for<'z> Get<'z> + Sync + Send + 'static, + AudioEntry: TAudioEntry + IsA, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream + IsA, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox + ReSetErrorImpl + 'static, + AudioBoxImpl: TAudioBoxImpl, +>( + audio_box: Arc, + audio_objects: Vec, + dropdown_function: &'static DBusFunction, + change_volume_function: &'static DBusFunction, + mute_function: &'static DBusFunction, +) { + glib::spawn_future(async move { + glib::idle_add_once(move || { + let source_box_ref_slider = audio_box.clone(); + let source_box_ref_toggle = audio_box.clone(); + let source_box_ref_mute = audio_box.clone(); + let imp = audio_box.box_imp(); + let default_sink = imp.default_audio_object().clone(); + let source = default_sink.borrow(); + + let icons = imp.icons(); + let mute_button = imp.audio_object_mute(); + if source.muted() { + mute_button.set_icon_name(icons.muted); + } else { + mute_button.set_icon_name(icons.active); + } + + let volume = source.volume(); + let volume = volume.first().unwrap_or(&0_u32); + let fraction = (*volume as f64 / 655.36).round(); + let percentage = (fraction).to_string() + "%"; + imp.volume_percentage().set_text(&percentage); + imp.volume_slider().set_value(*volume as f64); + let list = imp.audio_object_list(); + let mut list = list.write().unwrap(); + for source in audio_objects { + let index = source.index(); + let alias = source.alias().clone(); + let mut is_default = false; + if imp.default_audio_object().borrow().name() == source.name() { + is_default = true; + } + let source_entry = new_entry::< + AudioObject, + StreamObject, + AudioEntry, + AudioEntryImpl, + AudioStream, + AudioStreamImpl, + AudioBox, + AudioBoxImpl, + >( + is_default, + imp.default_check_button().clone(), + source, + audio_box.clone(), + ); + let source_clone = source_entry.clone(); + let entry = Arc::new(ListEntry::new(&*source_entry)); + entry.set_activatable(false); + list.insert(index, (entry.clone(), source_clone, alias)); + imp.audio_objects().append(&*entry); + } + let list = imp.model_list(); + let list = list.read().unwrap(); + imp.audio_object_dropdown().set_model(Some(&*list)); + let name = imp.default_audio_object(); + let name = name.borrow(); + + let index = imp.model_index(); + let index = index.read().unwrap(); + let model_list = imp.model_list(); + let model_list = model_list.read().unwrap(); + for entry in 0..*index { + if model_list.string(entry) == Some(name.alias().clone().into()) { + imp.audio_object_dropdown().set_selected(entry); + break; + } + } + imp.audio_object_dropdown() + .connect_selected_notify(move |dropdown| { + dropdown_handler(source_box_ref_toggle.clone(), dropdown, dropdown_function); + }); + imp.volume_slider() + .connect_change_value(move |_, _, value| { + volume_slider_handler( + source_box_ref_slider.clone(), + value, + change_volume_function, + ) + }); + + imp.audio_object_mute().connect_clicked(move |_| { + mute_clicked_handler(source_box_ref_mute.clone(), mute_function); + }); + }); + }); +} + +pub fn start_audio_box_listener< + AudioObject: TAudioObject, + StreamObject: TAudioStreamObject, + AudioEntry: TAudioEntry + IsA, + AudioEntryImpl: TAudioEntryImpl, + AudioStream: TAudioStream + IsA, + AudioStreamImpl: TAudioStreamImpl, + AudioBox: TAudioBox + ReSetErrorImpl + 'static, + AudioBoxImpl: TAudioBoxImpl, + ObjectAdded: TAudioObjectEvent + ReadAll + SignalArgs, + ObjectChanged: TAudioObjectEvent + ReadAll + SignalArgs, + ObjectRemoved: TAudioEventRemoved + ReadAll + SignalArgs, + StreamAdded: TAudioStreamEvent + ReadAll + SignalArgs, + StreamChanged: TAudioStreamEvent + ReadAll + SignalArgs, + StreamRemoved: TAudioEventRemoved + ReadAll + SignalArgs, +>( + conn: Connection, + source_box: Arc, + get_default_name_function: &'static DBusFunction, +) -> Connection { + // TODO: make the failed logs generically sound -> deynamic output for both + let object_added = + ObjectAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone(); + let object_changed = + ObjectChanged::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone(); + let object_removed = + ObjectRemoved::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone(); + let stream_added = + StreamAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone(); + let stream_changed = + StreamChanged::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone(); + let stream_removed = + StreamRemoved::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone(); + + let object_added_box = source_box.clone(); + let object_removed_box = source_box.clone(); + let object_changed_box = source_box.clone(); + let stream_added_box = source_box.clone(); + let stream_removed_box = source_box.clone(); + let stream_changed_box = source_box.clone(); + + let res = conn.add_match(object_added, move |ir: ObjectAdded, _, _| { + object_added_handler::< + AudioObject, + StreamObject, + AudioEntry, + AudioEntryImpl, + AudioStream, + AudioStreamImpl, + AudioBox, + AudioBoxImpl, + ObjectAdded, + >(object_added_box.clone(), ir) + }); + if res.is_err() { + // TODO: handle this with the log/error macro + println!("fail on source add event"); + return conn; + } + + let res = conn.add_match(object_changed, move |ir: ObjectChanged, _, _| { + // source_changed_handler(source_changed_box.clone(), ir) + object_changed_handler::< + AudioObject, + StreamObject, + AudioEntry, + AudioEntryImpl, + AudioStream, + AudioStreamImpl, + AudioBox, + AudioBoxImpl, + ObjectChanged, + >(object_changed_box.clone(), ir, get_default_name_function) + }); + if res.is_err() { + println!("fail on source change event"); + return conn; + } + + let res = conn.add_match(object_removed, move |ir: ObjectRemoved, _, _| { + // source_removed_handler(source_removed_box.clone(), ir) + object_removed_handler::< + AudioObject, + StreamObject, + AudioEntry, + AudioEntryImpl, + AudioStream, + AudioStreamImpl, + AudioBox, + AudioBoxImpl, + ObjectRemoved, + >(object_removed_box.clone(), ir) + }); + if res.is_err() { + println!("fail on source remove event"); + return conn; + } + + let res = conn.add_match(stream_added, move |ir: StreamAdded, _, _| { + audio_stream_added_handler::< + AudioObject, + StreamObject, + AudioEntry, + AudioEntryImpl, + AudioStream, + AudioStreamImpl, + AudioBox, + AudioBoxImpl, + StreamAdded, + >(stream_added_box.clone(), ir) + }); + if res.is_err() { + println!("fail on output stream add event"); + return conn; + } + + let res = conn.add_match(stream_changed, move |ir: StreamChanged, _, _| { + audio_stream_changed_handler::< + AudioObject, + StreamObject, + AudioEntry, + AudioEntryImpl, + AudioStream, + AudioStreamImpl, + AudioBox, + AudioBoxImpl, + StreamChanged, + >(stream_changed_box.clone(), ir) + }); + if res.is_err() { + println!("fail on output stream change event"); + return conn; + } + + let res = conn.add_match(stream_removed, move |ir: StreamRemoved, _, _| { + audio_stream_removed_handler::< + AudioObject, + StreamObject, + AudioEntry, + AudioEntryImpl, + AudioStream, + AudioStreamImpl, + AudioBox, + AudioBoxImpl, + StreamRemoved, + >(stream_removed_box.clone(), ir) + }); + if res.is_err() { + println!("fail on output stream remove event"); + return conn; + } + + conn +} diff --git a/src/components/audio/audio_const.rs b/src/components/audio/audio_const.rs new file mode 100644 index 0000000..49773cb --- /dev/null +++ b/src/components/audio/audio_const.rs @@ -0,0 +1,6 @@ +use super::audio_entry::DBusFunction; + +pub const GETCARDS: DBusFunction = DBusFunction { + function: "ListCards", + error: "Failed to get list profiles", +}; diff --git a/src/components/audio/audio_entry.rs b/src/components/audio/audio_entry.rs new file mode 100644 index 0000000..f93541f --- /dev/null +++ b/src/components/audio/audio_entry.rs @@ -0,0 +1,217 @@ +use std::collections::HashMap; +use std::sync::RwLock; +use std::time::Duration; +use std::{cell::RefCell, sync::Arc, time::SystemTime}; + +use adw::prelude::{ButtonExt, CheckButtonExt, PreferencesRowExt, RangeExt}; +use adw::{ActionRow, ComboRow, PreferencesGroup}; +use dbus::arg::{Arg, Get}; +use glib::Propagation; +use glib::{ + object::{IsA, IsClass}, + Object, +}; +use gtk::{gio, Button, CheckButton, Label, Scale, StringList, TemplateChild}; +use re_set_lib::audio::audio_structures::{TAudioObject, TAudioStreamObject}; + +use crate::components::base::error::ReSetError; +use crate::components::base::error_impl::ReSetErrorImpl; +use crate::components::base::list_entry::ListEntry; +use crate::components::utils::set_action_row_ellipsis; + +use super::audio_functions::refresh_default_audio_object; +use super::audio_utils::audio_dbus_call; + +pub type AudioEntryMap = Arc, Arc, String)>>>; +pub type AudioStreamEntryMap = Arc, Arc)>>>; +pub type AudioMap = Arc>>; + +pub trait TAudioBox { + fn box_imp(&self) -> &AudioBoxImpl; +} + +#[allow(dead_code)] +pub trait TAudioBoxImpl { + fn audio_object_row(&self) -> &TemplateChild; + fn cards_row(&self) -> &TemplateChild; + fn audio_object_dropdown(&self) -> &TemplateChild; + fn audio_object_mute(&self) -> &TemplateChild