mirror of
				https://github.com/Xetibo/ReSet.git
				synced 2025-10-24 22:25:19 +02:00 
			
		
		
		
	refactor: generic audio implementation (#98)
* refactor: Combine new_plugin and new for sidebar entries * chore: Format project * chore: Fix clippy warnings * wip: generic audio implementation * wip: format and fix warnings * wip: Refactor Dbus functions * wip: Add TAudioStreamObject * fix: use prelude instead of traits * wip: Add generic audio stream * wip: Work on generic audio listener * wip: Finish audio generics refactoring * fix: Use proper path for dbus function * chore: Format project * fix: Use adw prelude instead of traits
This commit is contained in:
		
							parent
							
								
									fd99d902c2
								
							
						
					
					
						commit
						5f0781ee18
					
				
					 52 changed files with 2590 additions and 2327 deletions
				
			
		|  | @ -7,8 +7,9 @@ repository = "https://github.com/Xetibo/ReSet" | ||||||
| license = "GPL-3.0-or-later" | license = "GPL-3.0-or-later" | ||||||
| 
 | 
 | ||||||
| [dependencies] | [dependencies] | ||||||
| #reset_daemon = "1.1.0" | # reset_daemon = "1.1.0" | ||||||
| re_set-lib = { git = "https://github.com/Xetibo/ReSet-Lib" } | 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" } | reset_daemon = { git = "https://github.com/Xetibo/ReSet-Daemon", branch = "dashie" } | ||||||
| adw = { version = "0.6.0", package = "libadwaita", features = ["v1_4"] } | adw = { version = "0.6.0", package = "libadwaita", features = ["v1_4"] } | ||||||
| dbus = "0.9.7" | dbus = "0.9.7" | ||||||
|  |  | ||||||
							
								
								
									
										590
									
								
								src/components/audio/audio_box_handlers.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										590
									
								
								src/components/audio/audio_box_handlers.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  | >( | ||||||
|  |     audio_box: Arc<AudioBox>, | ||||||
|  |     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::<AudioBox, (), (u32, bool)>( | ||||||
|  |         audio_box.clone(), | ||||||
|  |         (source.index(), source.muted()), | ||||||
|  |         function, | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn volume_slider_handler< | ||||||
|  |     AudioObject: TAudioObject, | ||||||
|  |     StreamObject: TAudioStreamObject, | ||||||
|  |     AudioEntry: TAudioEntry<AudioEntryImpl>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  | >( | ||||||
|  |     audio_box: Arc<AudioBox>, | ||||||
|  |     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::<AudioBox, (), (u32, u16, u32)>( | ||||||
|  |         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>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  | >( | ||||||
|  |     audio_box: Arc<AudioBox>, | ||||||
|  |     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::<StringObject>().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::<AudioBox, (AudioObject,), (&String,)>( | ||||||
|  |             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<AudioEntryImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  | >( | ||||||
|  |     audio_box: Arc<AudioBox>, | ||||||
|  |     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::<AudioBox, (Vec<AudioObject>,), ()>( | ||||||
|  |             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::<AudioBox, (AudioObject,), ()>( | ||||||
|  |                 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<AudioEntryImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  |     Event: TAudioObjectEvent<AudioObject>, | ||||||
|  | >( | ||||||
|  |     audio_box: Arc<AudioBox>, | ||||||
|  |     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<AudioEntryImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  |     Event: TAudioObjectEvent<AudioObject>, | ||||||
|  | >( | ||||||
|  |     audio_box: Arc<AudioBox>, | ||||||
|  |     ir: Event, | ||||||
|  |     function: &'static DBusFunction, | ||||||
|  | ) -> bool { | ||||||
|  |     let source = audio_dbus_call::<AudioBox, (String,), ()>(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<AudioEntryImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  |     Event: TAudioEventRemoved, | ||||||
|  | >( | ||||||
|  |     audio_box: Arc<AudioBox>, | ||||||
|  |     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<ListEntry>, Arc<AudioEntry>, 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<AudioEntryImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  |     Event: TAudioStreamEvent<StreamObject>, | ||||||
|  | >( | ||||||
|  |     audio_box: Arc<AudioBox>, | ||||||
|  |     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<AudioEntryImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  |     Event: TAudioStreamEvent<StreamObject>, | ||||||
|  | >( | ||||||
|  |     audio_box: Arc<AudioBox>, | ||||||
|  |     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<AudioStream>; | ||||||
|  |             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<AudioEntryImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  |     Event: TAudioEventRemoved, | ||||||
|  | >( | ||||||
|  |     audio_box: Arc<AudioBox>, | ||||||
|  |     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 | ||||||
|  | } | ||||||
							
								
								
									
										465
									
								
								src/components/audio/audio_box_utils.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										465
									
								
								src/components/audio/audio_box_utils.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl>, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  | >( | ||||||
|  |     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>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  | >( | ||||||
|  |     source_box: Arc<AudioBox>, | ||||||
|  | ) { | ||||||
|  |     gio::spawn_blocking(move || { | ||||||
|  |         let source_box_ref = source_box.clone(); | ||||||
|  |         let cards = | ||||||
|  |             audio_dbus_call::<AudioBox, (Vec<Card>,), ()>(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>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  | >( | ||||||
|  |     audio_box: Arc<AudioBox>, | ||||||
|  |     function: &'static DBusFunction, | ||||||
|  | ) { | ||||||
|  |     let audio_box_ref = audio_box.clone(); | ||||||
|  |     gio::spawn_blocking(move || { | ||||||
|  |         let streams = | ||||||
|  |             audio_dbus_call::<AudioBox, (Vec<StreamObject>,), ()>(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>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  | >( | ||||||
|  |     new_audio_object: AudioObject, | ||||||
|  |     audio_box: Arc<AudioBox>, | ||||||
|  |     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<AudioEntryImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  | >( | ||||||
|  |     audio_box: Arc<AudioBox>, | ||||||
|  |     audio_objects: Vec<AudioObject>, | ||||||
|  |     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<AudioEntryImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  |     ObjectAdded: TAudioObjectEvent<AudioObject> + ReadAll + SignalArgs, | ||||||
|  |     ObjectChanged: TAudioObjectEvent<AudioObject> + ReadAll + SignalArgs, | ||||||
|  |     ObjectRemoved: TAudioEventRemoved + ReadAll + SignalArgs, | ||||||
|  |     StreamAdded: TAudioStreamEvent<StreamObject> + ReadAll + SignalArgs, | ||||||
|  |     StreamChanged: TAudioStreamEvent<StreamObject> + ReadAll + SignalArgs, | ||||||
|  |     StreamRemoved: TAudioEventRemoved + ReadAll + SignalArgs, | ||||||
|  | >( | ||||||
|  |     conn: Connection, | ||||||
|  |     source_box: Arc<AudioBox>, | ||||||
|  |     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 | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								src/components/audio/audio_const.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/components/audio/audio_const.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | ||||||
|  | use super::audio_entry::DBusFunction; | ||||||
|  | 
 | ||||||
|  | pub const GETCARDS: DBusFunction = DBusFunction { | ||||||
|  |     function: "ListCards", | ||||||
|  |     error: "Failed to get list profiles", | ||||||
|  | }; | ||||||
							
								
								
									
										217
									
								
								src/components/audio/audio_entry.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								src/components/audio/audio_entry.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<T> = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<T>, String)>>>; | ||||||
|  | pub type AudioStreamEntryMap<T> = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<T>)>>>; | ||||||
|  | pub type AudioMap = Arc<RwLock<HashMap<String, (u32, String)>>>; | ||||||
|  | 
 | ||||||
|  | pub trait TAudioBox<AudioBoxImpl> { | ||||||
|  |     fn box_imp(&self) -> &AudioBoxImpl; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[allow(dead_code)] | ||||||
|  | pub trait TAudioBoxImpl<AObject, ENTRY, STREAMENTRY> { | ||||||
|  |     fn audio_object_row(&self) -> &TemplateChild<ActionRow>; | ||||||
|  |     fn cards_row(&self) -> &TemplateChild<ActionRow>; | ||||||
|  |     fn audio_object_dropdown(&self) -> &TemplateChild<ComboRow>; | ||||||
|  |     fn audio_object_mute(&self) -> &TemplateChild<Button>; | ||||||
|  |     fn volume_slider(&self) -> &TemplateChild<Scale>; | ||||||
|  |     fn volume_percentage(&self) -> &TemplateChild<Label>; | ||||||
|  |     fn audio_objects(&self) -> &TemplateChild<gtk::Box>; | ||||||
|  |     fn audio_object_stream_button(&self) -> &TemplateChild<ActionRow>; | ||||||
|  |     fn audio_object_streams(&self) -> &TemplateChild<gtk::Box>; | ||||||
|  |     fn cards_button(&self) -> &TemplateChild<ActionRow>; | ||||||
|  |     fn cards(&self) -> &TemplateChild<PreferencesGroup>; | ||||||
|  |     fn error(&self) -> &TemplateChild<ReSetError>; | ||||||
|  |     fn default_check_button(&self) -> Arc<CheckButton>; | ||||||
|  |     fn default_audio_object(&self) -> Arc<RefCell<AObject>>; | ||||||
|  |     fn audio_object_list(&self) -> &AudioEntryMap<ENTRY>; | ||||||
|  |     fn audio_object_stream_list(&self) -> &AudioStreamEntryMap<STREAMENTRY>; | ||||||
|  |     fn model_list(&self) -> Arc<RwLock<StringList>>; | ||||||
|  |     fn model_index(&self) -> Arc<RwLock<u32>>; | ||||||
|  |     fn source_map(&self) -> &AudioMap; | ||||||
|  |     fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>>; | ||||||
|  |     fn icons(&self) -> &AudioIcons; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub trait TAudioEntry<TAudioEntryImpl>: IsClass + IsA<glib::Object> { | ||||||
|  |     fn entry_imp(&self) -> &TAudioEntryImpl; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub trait TAudioEntryImpl<AudioObject: TAudioObject> { | ||||||
|  |     fn name(&self) -> &TemplateChild<ActionRow>; | ||||||
|  |     fn selected_audio_object(&self) -> &TemplateChild<CheckButton>; | ||||||
|  |     fn mute(&self) -> &TemplateChild<Button>; | ||||||
|  |     fn volume_slider(&self) -> &TemplateChild<Scale>; | ||||||
|  |     fn volume_percentage(&self) -> &TemplateChild<Label>; | ||||||
|  |     fn audio_object(&self) -> Arc<RefCell<AudioObject>>; | ||||||
|  |     fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>>; | ||||||
|  |     fn set_volume_fn(&self) -> &'static DBusFunction; | ||||||
|  |     fn set_audio_object_fn(&self) -> &'static DBusFunction; | ||||||
|  |     fn set_mute_fn(&self) -> &'static DBusFunction; | ||||||
|  |     fn icons(&self) -> &AudioIcons; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub trait TAudioStream<TAudioStreamImpl>: IsClass + IsA<glib::Object> { | ||||||
|  |     fn entry_imp(&self) -> &TAudioStreamImpl; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub trait TAudioStreamImpl<AudioObject: TAudioObject, StreamObject: TAudioStreamObject> { | ||||||
|  |     fn audio_object_selection(&self) -> &TemplateChild<ComboRow>; | ||||||
|  |     fn audio_object_mute(&self) -> &TemplateChild<Button>; | ||||||
|  |     fn volume_slider(&self) -> &TemplateChild<Scale>; | ||||||
|  |     fn volume_percentage(&self) -> &TemplateChild<Label>; | ||||||
|  |     fn stream_object(&self) -> Arc<RefCell<StreamObject>>; | ||||||
|  |     fn associated_audio_object(&self) -> Arc<RefCell<(u32, String)>>; | ||||||
|  |     fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>>; | ||||||
|  |     fn set_volume_fn(&self) -> &'static DBusFunction; | ||||||
|  |     fn set_audio_object_fn(&self) -> &'static DBusFunction; | ||||||
|  |     fn set_mute_fn(&self) -> &'static DBusFunction; | ||||||
|  |     fn icons(&self) -> &AudioIcons; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub struct AudioIcons { | ||||||
|  |     pub muted: &'static str, | ||||||
|  |     pub active: &'static str, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub struct DBusFunction { | ||||||
|  |     pub function: &'static str, | ||||||
|  |     pub error: &'static str, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn new_entry< | ||||||
|  |     AudioObject: TAudioObject + Arg + for<'z> Get<'z> + Send + Sync + 'static, | ||||||
|  |     StreamObject: TAudioStreamObject + Arg + for<'z> Get<'z> + Send + Sync + 'static, | ||||||
|  |     AudioEntry: TAudioEntry<AudioEntryImpl>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  | >( | ||||||
|  |     is_default: bool, | ||||||
|  |     check_group: Arc<CheckButton>, | ||||||
|  |     audio_object: AudioObject, | ||||||
|  |     reset_box: Arc<AudioBox>, | ||||||
|  | ) -> Arc<AudioEntry> { | ||||||
|  |     let obj: Arc<AudioEntry> = Arc::new(Object::builder().build()); | ||||||
|  |     // TODO use event callback for progress bar -> this is the "im speaking" indicator
 | ||||||
|  |     { | ||||||
|  |         let imp = obj.entry_imp(); | ||||||
|  |         let slider_obj_ref = obj.clone(); | ||||||
|  |         let mute_obj_ref = obj.clone(); | ||||||
|  |         imp.name().set_title(audio_object.alias().clone().as_str()); | ||||||
|  |         let name = Arc::new(audio_object.name().clone()); | ||||||
|  |         let volume = audio_object.volume(); | ||||||
|  |         let volume = volume.first().unwrap_or(&0_u32); | ||||||
|  |         let fraction = (*volume as f64 / 655.36).round(); | ||||||
|  |         let percentage = (fraction).to_string() + "%"; | ||||||
|  |         let output_box_slider = reset_box.clone(); | ||||||
|  |         let output_box_ref = reset_box.clone(); | ||||||
|  |         imp.volume_percentage().set_text(&percentage); | ||||||
|  |         imp.volume_slider().set_value(*volume as f64); | ||||||
|  |         imp.audio_object().replace(audio_object); | ||||||
|  |         imp.volume_slider() | ||||||
|  |             .connect_change_value(move |_, _, value| { | ||||||
|  |                 let imp = slider_obj_ref.entry_imp(); | ||||||
|  |                 let fraction = (value / 655.36).round(); | ||||||
|  |                 let percentage = (fraction).to_string() + "%"; | ||||||
|  |                 imp.volume_percentage().set_text(&percentage); | ||||||
|  |                 let sink = imp.audio_object(); | ||||||
|  |                 let sink = sink.borrow(); | ||||||
|  |                 let index = sink.index(); | ||||||
|  |                 let channels = sink.channels(); | ||||||
|  |                 { | ||||||
|  |                     let time = imp.volume_time_stamp(); | ||||||
|  |                     let mut time = time.borrow_mut(); | ||||||
|  |                     if time.is_some() | ||||||
|  |                         && time.unwrap().elapsed().unwrap() < Duration::from_millis(50) | ||||||
|  |                     { | ||||||
|  |                         return Propagation::Proceed; | ||||||
|  |                     } | ||||||
|  |                     *time = Some(SystemTime::now()); | ||||||
|  |                 } | ||||||
|  |                 audio_dbus_call::<AudioBox, (), (u32, u16, u32)>( | ||||||
|  |                     output_box_slider.clone(), | ||||||
|  |                     (index, channels, value as u32), | ||||||
|  |                     imp.set_volume_fn(), | ||||||
|  |                 ); | ||||||
|  |                 Propagation::Proceed | ||||||
|  |             }); | ||||||
|  |         imp.selected_audio_object().set_group(Some(&*check_group)); | ||||||
|  |         if is_default { | ||||||
|  |             imp.selected_audio_object().set_active(true); | ||||||
|  |         } else { | ||||||
|  |             imp.selected_audio_object().set_active(false); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let audio_object_fn = imp.set_audio_object_fn(); | ||||||
|  |         imp.selected_audio_object().connect_toggled(move |button| { | ||||||
|  |             let output_box_ref = reset_box.clone(); | ||||||
|  |             if button.is_active() { | ||||||
|  |                 let name = name.clone(); | ||||||
|  |                 gio::spawn_blocking(move || { | ||||||
|  |                     let result = audio_dbus_call::<AudioBox, (AudioObject,), (&String,)>( | ||||||
|  |                         output_box_ref.clone(), | ||||||
|  |                         (&name,), | ||||||
|  |                         audio_object_fn, | ||||||
|  |                     ); | ||||||
|  |                     if result.is_none() { | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                     refresh_default_audio_object::< | ||||||
|  |                         AudioObject, | ||||||
|  |                         StreamObject, | ||||||
|  |                         AudioEntry, | ||||||
|  |                         AudioEntryImpl, | ||||||
|  |                         AudioStream, | ||||||
|  |                         AudioStreamImpl, | ||||||
|  |                         AudioBox, | ||||||
|  |                         AudioBoxImpl, | ||||||
|  |                     >(result.unwrap().0, output_box_ref, true); | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         imp.mute().connect_clicked(move |_| { | ||||||
|  |             let imp = mute_obj_ref.entry_imp(); | ||||||
|  |             let audio_object = imp.audio_object().clone(); | ||||||
|  |             let mut audio_object = audio_object.borrow_mut(); | ||||||
|  |             audio_object.toggle_muted(); | ||||||
|  |             let icons = imp.icons(); | ||||||
|  |             if audio_object.muted() { | ||||||
|  |                 imp.mute().set_icon_name(icons.muted); | ||||||
|  |             } else { | ||||||
|  |                 imp.mute().set_icon_name(icons.active); | ||||||
|  |             } | ||||||
|  |             audio_dbus_call::<AudioBox, (), (u32, bool)>( | ||||||
|  |                 output_box_ref.clone(), | ||||||
|  |                 (audio_object.index(), audio_object.muted()), | ||||||
|  |                 imp.set_mute_fn(), | ||||||
|  |             ); | ||||||
|  |         }); | ||||||
|  |         set_action_row_ellipsis(imp.name().get()); | ||||||
|  |     } | ||||||
|  |     obj | ||||||
|  | } | ||||||
							
								
								
									
										242
									
								
								src/components/audio/audio_functions.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								src/components/audio/audio_functions.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,242 @@ | ||||||
|  | use std::{ | ||||||
|  |     sync::Arc, | ||||||
|  |     time::{Duration, SystemTime}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | use adw::{prelude::ComboRowExt, prelude::PreferencesRowExt}; | ||||||
|  | use glib::{object::Cast, Object, Propagation}; | ||||||
|  | use gtk::{ | ||||||
|  |     prelude::{ButtonExt, CheckButtonExt, RangeExt}, | ||||||
|  |     StringObject, | ||||||
|  | }; | ||||||
|  | use re_set_lib::audio::audio_structures::{TAudioObject, TAudioStreamObject}; | ||||||
|  | 
 | ||||||
|  | use crate::components::{ | ||||||
|  |     base::error_impl::ReSetErrorImpl, | ||||||
|  |     utils::{create_dropdown_label_factory, set_combo_row_ellipsis}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | use super::{ | ||||||
|  |     audio_entry::{ | ||||||
|  |         TAudioBox, TAudioBoxImpl, TAudioEntry, TAudioEntryImpl, TAudioStream, TAudioStreamImpl, | ||||||
|  |     }, | ||||||
|  |     audio_utils::audio_dbus_call, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub fn refresh_default_audio_object< | ||||||
|  |     AudioObject: TAudioObject + Send + Sync + 'static, | ||||||
|  |     StreamObject: TAudioStreamObject + Send + Sync + 'static, | ||||||
|  |     AudioEntry: TAudioEntry<AudioEntryImpl>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + Send + Sync + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  | >( | ||||||
|  |     new_audio_object: AudioObject, | ||||||
|  |     reset_box: Arc<AudioBox>, | ||||||
|  |     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 = reset_box.box_imp(); | ||||||
|  |             if !entry { | ||||||
|  |                 let list = imp.audio_object_list(); | ||||||
|  |                 let list = 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 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(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(); | ||||||
|  |             if new_audio_object.muted() { | ||||||
|  |                 imp.audio_object_mute().set_icon_name(icons.muted); | ||||||
|  |             } else { | ||||||
|  |                 imp.audio_object_mute().set_icon_name(icons.active); | ||||||
|  |             } | ||||||
|  |             imp.default_audio_object().replace(new_audio_object); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn new_stream_entry< | ||||||
|  |     AudioObject: TAudioObject + Send + Sync + 'static, | ||||||
|  |     StreamObject: TAudioStreamObject + Send + Sync + 'static, | ||||||
|  |     AudioEntry: TAudioEntry<AudioEntryImpl>, | ||||||
|  |     AudioEntryImpl: TAudioEntryImpl<AudioObject>, | ||||||
|  |     AudioStream: TAudioStream<AudioStreamImpl>, | ||||||
|  |     AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>, | ||||||
|  |     AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + Send + Sync + 'static, | ||||||
|  |     AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>, | ||||||
|  | >( | ||||||
|  |     audio_box: Arc<AudioBox>, | ||||||
|  |     stream: StreamObject, | ||||||
|  | ) -> Arc<AudioStream> { | ||||||
|  |     let obj: Arc<AudioStream> = Arc::new(Object::builder().build()); | ||||||
|  |     // TODO use event callback for progress bar -> this is the "im speaking" indicator
 | ||||||
|  |     let output_box_mute_ref = audio_box.clone(); | ||||||
|  |     let output_box_volume_ref = audio_box.clone(); | ||||||
|  |     let output_box_sink_ref = audio_box.clone(); | ||||||
|  |     let entry_mute_ref = obj.clone(); | ||||||
|  |     let entry_volume_ref = obj.clone(); | ||||||
|  |     let entry_sink_ref = obj.clone(); | ||||||
|  |     { | ||||||
|  |         let index = stream.audio_object_index(); | ||||||
|  |         let box_imp = audio_box.box_imp(); | ||||||
|  |         let imp = obj.entry_imp(); | ||||||
|  |         let icons = box_imp.icons(); | ||||||
|  |         if stream.muted() { | ||||||
|  |             imp.audio_object_mute().set_icon_name(icons.muted); | ||||||
|  |         } else { | ||||||
|  |             imp.audio_object_mute().set_icon_name(icons.active); | ||||||
|  |         } | ||||||
|  |         let name = stream.application_name().clone() + ": " + stream.name().as_str(); | ||||||
|  |         imp.audio_object_selection().set_title(name.as_str()); | ||||||
|  |         imp.audio_object_selection() | ||||||
|  |             .set_factory(Some(&create_dropdown_label_factory())); | ||||||
|  |         set_combo_row_ellipsis(imp.audio_object_selection().get()); | ||||||
|  |         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); | ||||||
|  |         imp.stream_object().replace(stream); | ||||||
|  |         { | ||||||
|  |             let sink = box_imp.default_audio_object(); | ||||||
|  |             let sink = sink.borrow(); | ||||||
|  |             imp.associated_audio_object() | ||||||
|  |                 .replace((sink.index(), sink.name().clone())); | ||||||
|  |         } | ||||||
|  |         imp.volume_slider() | ||||||
|  |             .connect_change_value(move |_, _, value| { | ||||||
|  |                 let imp = entry_volume_ref.entry_imp(); | ||||||
|  |                 let fraction = (value / 655.36).round(); | ||||||
|  |                 let percentage = (fraction).to_string() + "%"; | ||||||
|  |                 imp.volume_percentage().set_text(&percentage); | ||||||
|  |                 let stream = imp.stream_object(); | ||||||
|  |                 let mut stream_opt = stream.try_borrow(); | ||||||
|  |                 while stream_opt.is_err() { | ||||||
|  |                     stream_opt = stream.try_borrow(); | ||||||
|  |                 } | ||||||
|  |                 let stream = stream_opt.unwrap(); | ||||||
|  |                 let index = stream.index(); | ||||||
|  |                 let channels = stream.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::<AudioBox, (), (u32, u16, u32)>( | ||||||
|  |                     output_box_volume_ref.clone(), | ||||||
|  |                     (index, channels, value as u32), | ||||||
|  |                     imp.set_volume_fn(), | ||||||
|  |                 ); | ||||||
|  |                 Propagation::Proceed | ||||||
|  |             }); | ||||||
|  |         { | ||||||
|  |             let list = box_imp.model_list(); | ||||||
|  |             let list = list.read().unwrap(); | ||||||
|  |             imp.audio_object_selection().set_model(Some(&*list)); | ||||||
|  |             let sink_list = box_imp.audio_object_list().read().unwrap(); | ||||||
|  |             let name = sink_list.get(&index); | ||||||
|  |             let index = box_imp.model_index(); | ||||||
|  |             let index = index.read().unwrap(); | ||||||
|  |             if let Some(name) = name { | ||||||
|  |                 for entry in 0..*index { | ||||||
|  |                     if list.string(entry) == Some(name.2.clone().into()) { | ||||||
|  |                         imp.audio_object_selection().set_selected(entry); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 let name = box_imp.default_audio_object(); | ||||||
|  |                 let mut name_opt = name.try_borrow(); | ||||||
|  |                 while name_opt.is_err() { | ||||||
|  |                     name_opt = name.try_borrow(); | ||||||
|  |                 } | ||||||
|  |                 let name = name_opt.unwrap(); | ||||||
|  |                 for entry in 0..*index { | ||||||
|  |                     if list.string(entry) == Some(name.alias().into()) { | ||||||
|  |                         imp.audio_object_selection().set_selected(entry); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         imp.audio_object_selection() | ||||||
|  |             .connect_selected_notify(move |dropdown| { | ||||||
|  |                 let imp = entry_sink_ref.entry_imp(); | ||||||
|  |                 let box_imp = output_box_sink_ref.box_imp(); | ||||||
|  |                 let selected = dropdown.selected_item(); | ||||||
|  |                 if selected.is_none() { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 let selected = selected.unwrap(); | ||||||
|  |                 let selected = selected.downcast_ref::<StringObject>().unwrap(); | ||||||
|  |                 let selected = selected.string().to_string(); | ||||||
|  |                 let sink = box_imp.source_map().read().unwrap(); | ||||||
|  |                 let sink = sink.get(&selected); | ||||||
|  |                 if sink.is_none() { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 let stream = imp.stream_object(); | ||||||
|  |                 let mut stream_opt = stream.try_borrow(); | ||||||
|  |                 while stream_opt.is_err() { | ||||||
|  |                     stream_opt = stream.try_borrow(); | ||||||
|  |                 } | ||||||
|  |                 let stream = stream_opt.unwrap(); | ||||||
|  |                 let sink = sink.unwrap().0; | ||||||
|  |                 audio_dbus_call::<AudioBox, (), (u32, u32)>( | ||||||
|  |                     output_box_sink_ref.clone(), | ||||||
|  |                     (stream.index(), sink), | ||||||
|  |                     imp.set_audio_object_fn(), | ||||||
|  |                 ); | ||||||
|  |             }); | ||||||
|  |         imp.audio_object_mute().connect_clicked(move |_| { | ||||||
|  |             let imp = entry_mute_ref.entry_imp(); | ||||||
|  |             let stream = imp.stream_object().clone(); | ||||||
|  |             let mut stream_opt = stream.try_borrow_mut(); | ||||||
|  |             while stream_opt.is_err() { | ||||||
|  |                 stream_opt = stream.try_borrow_mut(); | ||||||
|  |             } | ||||||
|  |             let mut stream = stream_opt.unwrap(); | ||||||
|  |             stream.toggle_muted(); | ||||||
|  |             let icons = imp.icons(); | ||||||
|  |             let muted = stream.muted(); | ||||||
|  |             if muted { | ||||||
|  |                 imp.audio_object_mute().set_icon_name(icons.muted); | ||||||
|  |             } else { | ||||||
|  |                 imp.audio_object_mute().set_icon_name(icons.active); | ||||||
|  |             } | ||||||
|  |             audio_dbus_call::<AudioBox, (), (u32, bool)>( | ||||||
|  |                 output_box_mute_ref.clone(), | ||||||
|  |                 (stream.index(), muted), | ||||||
|  |                 imp.set_mute_fn(), | ||||||
|  |             ); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     obj | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								src/components/audio/audio_utils.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/components/audio/audio_utils.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | ||||||
|  | use std::{sync::Arc, time::Duration}; | ||||||
|  | 
 | ||||||
|  | use dbus::{ | ||||||
|  |     arg::{AppendAll, ReadAll}, | ||||||
|  |     blocking::Connection, | ||||||
|  |     Error, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | use crate::components::{ | ||||||
|  |     base::error_impl::{show_error, ReSetErrorImpl}, | ||||||
|  |     utils::{AUDIO, BASE, DBUS_PATH}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | use super::audio_entry::DBusFunction; | ||||||
|  | 
 | ||||||
|  | pub fn audio_dbus_call<B, O, I>( | ||||||
|  |     source_box: Arc<B>, | ||||||
|  |     args: I, | ||||||
|  |     function: &'static DBusFunction, | ||||||
|  | ) -> Option<O> | ||||||
|  | where | ||||||
|  |     O: ReadAll, | ||||||
|  |     I: AppendAll, | ||||||
|  |     B: ReSetErrorImpl + 'static, | ||||||
|  | { | ||||||
|  |     let conn = Connection::new_session().unwrap(); | ||||||
|  |     let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); | ||||||
|  |     let res: Result<O, Error> = proxy.method_call(AUDIO, function.function, args); | ||||||
|  |     if res.is_err() { | ||||||
|  |         show_error::<B>(source_box.clone(), function.error); | ||||||
|  |         return None; | ||||||
|  |     } | ||||||
|  |     Some(res.unwrap()) | ||||||
|  | } | ||||||
|  | @ -1,8 +1,7 @@ | ||||||
| pub mod output_stream_entry; | pub mod output_stream_entry; | ||||||
| pub mod output_stream_entry_impl; | pub mod output_stream_entry_impl; | ||||||
| pub mod source_box; | pub mod source_box; | ||||||
| mod source_box_handlers; |  | ||||||
| pub mod source_box_impl; | pub mod source_box_impl; | ||||||
| mod source_box_utils; | mod source_const; | ||||||
| pub mod source_entry; | pub mod source_entry; | ||||||
| pub mod source_entry_impl; | pub mod source_entry_impl; | ||||||
|  |  | ||||||
|  | @ -1,22 +1,13 @@ | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| use std::time::{Duration, SystemTime}; |  | ||||||
| 
 | 
 | ||||||
| use crate::components::base::error_impl::show_error; | use crate::components::audio::audio_entry::TAudioStream; | ||||||
| use crate::components::utils::{ | use crate::components::audio::audio_functions::new_stream_entry; | ||||||
|     create_dropdown_label_factory, set_combo_row_ellipsis, AUDIO, BASE, DBUS_PATH, |  | ||||||
| }; |  | ||||||
| use adw::glib::Object; |  | ||||||
| use adw::prelude::{ButtonExt, ComboRowExt, PreferencesRowExt, RangeExt}; |  | ||||||
| use dbus::blocking::Connection; |  | ||||||
| use dbus::Error; |  | ||||||
| use glib::prelude::Cast; |  | ||||||
| use glib::subclass::types::ObjectSubclassIsExt; | use glib::subclass::types::ObjectSubclassIsExt; | ||||||
| use glib::{clone, Propagation}; | use re_set_lib::audio::audio_structures::{OutputStream, Source}; | ||||||
| use gtk::{gio, StringObject}; |  | ||||||
| use re_set_lib::audio::audio_structures::OutputStream; |  | ||||||
| 
 | 
 | ||||||
| use super::output_stream_entry_impl; | use super::output_stream_entry_impl; | ||||||
| use super::source_box::SourceBox; | use super::source_box::SourceBox; | ||||||
|  | use super::source_entry::SourceEntry; | ||||||
| 
 | 
 | ||||||
| glib::wrapper! { | glib::wrapper! { | ||||||
|     pub struct OutputStreamEntry(ObjectSubclass<output_stream_entry_impl::OutputStreamEntry>) |     pub struct OutputStreamEntry(ObjectSubclass<output_stream_entry_impl::OutputStreamEntry>) | ||||||
|  | @ -27,174 +18,23 @@ glib::wrapper! { | ||||||
| unsafe impl Send for OutputStreamEntry {} | unsafe impl Send for OutputStreamEntry {} | ||||||
| unsafe impl Sync for OutputStreamEntry {} | unsafe impl Sync for OutputStreamEntry {} | ||||||
| 
 | 
 | ||||||
| impl OutputStreamEntry { | impl TAudioStream<super::output_stream_entry_impl::OutputStreamEntry> for OutputStreamEntry { | ||||||
|     pub fn new(source_box: Arc<SourceBox>, stream: OutputStream) -> Self { |     fn entry_imp(&self) -> &super::output_stream_entry_impl::OutputStreamEntry { | ||||||
|         let obj: Self = Object::builder().build(); |         self.imp() | ||||||
|         // TODO use event callback for progress bar -> this is the "im speaking" indicator
 |  | ||||||
|         let output_box_volume_ref = source_box.clone(); |  | ||||||
|         let output_box_mute_ref = source_box.clone(); |  | ||||||
|         let output_box_source_ref = source_box.clone(); |  | ||||||
|         { |  | ||||||
|             let index = stream.index; |  | ||||||
|             let box_imp = source_box.imp(); |  | ||||||
|             let imp = obj.imp(); |  | ||||||
|             let name = stream.application_name.clone() + ": " + stream.name.as_str(); |  | ||||||
|             imp.reset_source_selection.set_title(name.as_str()); |  | ||||||
|             imp.reset_source_selection |  | ||||||
|                 .set_factory(Some(&create_dropdown_label_factory())); |  | ||||||
|             set_combo_row_ellipsis(imp.reset_source_selection.get()); |  | ||||||
|             let volume = stream.volume.first().unwrap_or(&0_u32); |  | ||||||
|             let fraction = (*volume as f64 / 655.36).round(); |  | ||||||
|             let percentage = (fraction).to_string() + "%"; |  | ||||||
|             imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|             imp.reset_volume_slider.set_value(*volume as f64); |  | ||||||
|             imp.stream.replace(stream); |  | ||||||
|             imp.reset_volume_slider.connect_change_value( |  | ||||||
|                 clone!(@weak imp => @default-return Propagation::Stop, move |_, _, value| { |  | ||||||
|                     let fraction = (value / 655.36).round(); |  | ||||||
|                     let percentage = (fraction).to_string() + "%"; |  | ||||||
|                     imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|                     let mut stream = imp.stream.try_borrow(); |  | ||||||
|                     while stream.is_err() { |  | ||||||
|                         stream = imp.stream.try_borrow(); |  | ||||||
|                     } |  | ||||||
|                     let stream = stream.unwrap(); |  | ||||||
|                     let index = stream.index; |  | ||||||
|                     let channels = stream.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()); |  | ||||||
|                     } |  | ||||||
|                     set_outputstream_volume(value, index, channels, output_box_volume_ref.clone()); |  | ||||||
|                     Propagation::Proceed |  | ||||||
|                 }), |  | ||||||
|             ); |  | ||||||
|             { |  | ||||||
|                 let list = box_imp.reset_model_list.read().unwrap(); |  | ||||||
|                 imp.reset_source_selection.set_model(Some(&*list)); |  | ||||||
|                 let source_list = box_imp.reset_source_list.read().unwrap(); |  | ||||||
|                 let name = source_list.get(&index); |  | ||||||
|                 let index = box_imp.reset_model_index.read().unwrap(); |  | ||||||
|                 let model_list = box_imp.reset_model_list.read().unwrap(); |  | ||||||
|                 if let Some(name) = name { |  | ||||||
|                     for entry in 0..*index { |  | ||||||
|                         if model_list.string(entry) == Some(name.2.clone().into()) { |  | ||||||
|                             imp.reset_source_selection.set_selected(entry); |  | ||||||
|                             break; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     let mut name = box_imp.reset_default_source.try_borrow(); |  | ||||||
|                     while name.is_err() { |  | ||||||
|                         name = box_imp.reset_default_source.try_borrow(); |  | ||||||
|                     } |  | ||||||
|                     let name = name.unwrap(); |  | ||||||
|                     for entry in 0..*index { |  | ||||||
|                         if model_list.string(entry) == Some(name.alias.clone().into()) { |  | ||||||
|                             imp.reset_source_selection.set_selected(entry); |  | ||||||
|                             break; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             imp.reset_source_selection.connect_selected_notify( |  | ||||||
|                 clone!(@weak imp, @weak box_imp => move |dropdown| { |  | ||||||
|                     let selected = dropdown.selected_item(); |  | ||||||
|                     if selected.is_none() { |  | ||||||
|                         return; |  | ||||||
|                     } |  | ||||||
|                     let selected = selected.unwrap(); |  | ||||||
|                     let selected = selected.downcast_ref::<StringObject>().unwrap(); |  | ||||||
|                     let selected = selected.string().to_string(); |  | ||||||
|                     let source = box_imp.reset_source_map.write().unwrap(); |  | ||||||
|                     let source = source.get(&selected); |  | ||||||
|                     if source.is_none() { |  | ||||||
|                         return; |  | ||||||
|                     } |  | ||||||
|                     let mut stream = imp.stream.try_borrow(); |  | ||||||
|                     while stream.is_err() { |  | ||||||
|                         stream = imp.stream.try_borrow(); |  | ||||||
|                     } |  | ||||||
|                     let stream = stream.unwrap(); |  | ||||||
|                     let source = source.unwrap().0; |  | ||||||
|                     set_source_of_output_stream(stream.index, source, output_box_source_ref.clone()); |  | ||||||
|                 }), |  | ||||||
|             ); |  | ||||||
|             imp.reset_source_mute |  | ||||||
|                 .connect_clicked(clone!(@weak imp => move |_| { |  | ||||||
|                     let stream = imp.stream.clone(); |  | ||||||
|                     let mut stream = stream.try_borrow_mut(); |  | ||||||
|                     while stream.is_err() { |  | ||||||
|                         stream = imp.stream.try_borrow_mut(); |  | ||||||
|                     } |  | ||||||
|                     let mut stream = stream.unwrap(); |  | ||||||
|                     stream.muted = !stream.muted; |  | ||||||
|                     let muted = stream.muted; |  | ||||||
|                     let index = stream.index; |  | ||||||
|                     if muted { |  | ||||||
|                         imp.reset_source_mute |  | ||||||
|                            .set_icon_name("microphone-disabled-symbolic"); |  | ||||||
|                     } else { |  | ||||||
|                         imp.reset_source_mute |  | ||||||
|                            .set_icon_name("audio-input-microphone-symbolic"); |  | ||||||
|                     } |  | ||||||
|                     toggle_output_stream_mute(index, muted, output_box_mute_ref.clone()); |  | ||||||
|                 })); |  | ||||||
|         } |  | ||||||
|         obj |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn set_outputstream_volume( | impl OutputStreamEntry { | ||||||
|     value: f64, |     pub fn new(source_box: Arc<SourceBox>, stream: OutputStream) -> Arc<Self> { | ||||||
|     index: u32, |         new_stream_entry::< | ||||||
|     channels: u16, |             Source, | ||||||
|     input_box: Arc<SourceBox>, |             OutputStream, | ||||||
| ) -> bool { |             SourceEntry, | ||||||
|     gio::spawn_blocking(move || { |             super::source_entry_impl::SourceEntry, | ||||||
|         let conn = Connection::new_session().unwrap(); |             OutputStreamEntry, | ||||||
|         let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |             super::output_stream_entry_impl::OutputStreamEntry, | ||||||
|         let res: Result<(), Error> = proxy.method_call( |             SourceBox, | ||||||
|             AUDIO, |             super::source_box_impl::SourceBox, | ||||||
|             "SetOutputStreamVolume", |         >(source_box, stream) | ||||||
|             (index, channels, value as u32), |     } | ||||||
|         ); |  | ||||||
|         if res.is_err() { |  | ||||||
|             show_error::<SourceBox>(input_box.clone(), "Failed to set output stream volume"); |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } | } | ||||||
| 
 |  | ||||||
| fn toggle_output_stream_mute(index: u32, muted: bool, input_box: Arc<SourceBox>) -> bool { |  | ||||||
|     gio::spawn_blocking(move || { |  | ||||||
|         let conn = Connection::new_session().unwrap(); |  | ||||||
|         let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|         let res: Result<(), Error> = |  | ||||||
|             proxy.method_call(AUDIO, "SetOutputStreamMute", (index, muted)); |  | ||||||
|         if res.is_err() { |  | ||||||
|             show_error::<SourceBox>(input_box.clone(), "Failed to mute output stream"); |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn set_source_of_output_stream(stream: u32, source: u32, input_box: Arc<SourceBox>) -> bool { |  | ||||||
|     gio::spawn_blocking(move || { |  | ||||||
|         let conn = Connection::new_session().unwrap(); |  | ||||||
|         let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|         let res: Result<(bool,), Error> = |  | ||||||
|             proxy.method_call(AUDIO, "SetSourceOfOutputStream", (stream, source)); |  | ||||||
|         if res.is_err() { |  | ||||||
|             show_error::<SourceBox>(input_box.clone(), "Failed to set source of output stream"); |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // TODO propagate error from dbus
 |  | ||||||
|  |  | ||||||
|  | @ -1,14 +1,17 @@ | ||||||
| use adw::subclass::prelude::PreferencesGroupImpl; | use adw::subclass::prelude::PreferencesGroupImpl; | ||||||
| use adw::{ComboRow, PreferencesGroup}; | use adw::{ComboRow, PreferencesGroup}; | ||||||
| use re_set_lib::audio::audio_structures::OutputStream; | use re_set_lib::audio::audio_structures::{OutputStream, Source}; | ||||||
| use std::cell::RefCell; | use std::cell::RefCell; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| use std::time::SystemTime; | use std::time::SystemTime; | ||||||
| 
 | 
 | ||||||
|  | use crate::components::audio::audio_entry::{AudioIcons, TAudioStreamImpl}; | ||||||
| use crate::components::audio::input::output_stream_entry; | use crate::components::audio::input::output_stream_entry; | ||||||
| use gtk::subclass::prelude::*; | use gtk::subclass::prelude::*; | ||||||
| use gtk::{Button, CompositeTemplate, Label, Scale}; | use gtk::{Button, CompositeTemplate, Label, Scale}; | ||||||
| 
 | 
 | ||||||
|  | use super::source_const::{ICONS, SETSTREAMMUTE, SETSTREAMOBJECT, SETSTREAMVOLUME}; | ||||||
|  | 
 | ||||||
| #[derive(Default, CompositeTemplate)] | #[derive(Default, CompositeTemplate)] | ||||||
| #[template(resource = "/org/Xetibo/ReSet/resetOutputStreamEntry.ui")] | #[template(resource = "/org/Xetibo/ReSet/resetOutputStreamEntry.ui")] | ||||||
| pub struct OutputStreamEntry { | pub struct OutputStreamEntry { | ||||||
|  | @ -46,3 +49,49 @@ impl PreferencesGroupImpl for OutputStreamEntry {} | ||||||
| impl ObjectImpl for OutputStreamEntry {} | impl ObjectImpl for OutputStreamEntry {} | ||||||
| 
 | 
 | ||||||
| impl WidgetImpl for OutputStreamEntry {} | impl WidgetImpl for OutputStreamEntry {} | ||||||
|  | 
 | ||||||
|  | impl TAudioStreamImpl<Source, OutputStream> for OutputStreamEntry { | ||||||
|  |     fn audio_object_selection(&self) -> &TemplateChild<ComboRow> { | ||||||
|  |         &self.reset_source_selection | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object_mute(&self) -> &TemplateChild<Button> { | ||||||
|  |         &self.reset_source_mute | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_slider(&self) -> &TemplateChild<Scale> { | ||||||
|  |         &self.reset_volume_slider | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_percentage(&self) -> &TemplateChild<Label> { | ||||||
|  |         &self.reset_volume_percentage | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn stream_object(&self) -> Arc<RefCell<OutputStream>> { | ||||||
|  |         self.stream.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn associated_audio_object(&self) -> Arc<RefCell<(u32, String)>> { | ||||||
|  |         self.associated_source.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>> { | ||||||
|  |         &self.volume_time_stamp | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn set_volume_fn(&self) -> &'static crate::components::audio::audio_entry::DBusFunction { | ||||||
|  |         &SETSTREAMVOLUME | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn set_audio_object_fn(&self) -> &'static crate::components::audio::audio_entry::DBusFunction { | ||||||
|  |         &SETSTREAMOBJECT | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn set_mute_fn(&self) -> &'static crate::components::audio::audio_entry::DBusFunction { | ||||||
|  |         &SETSTREAMMUTE | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn icons(&self) -> &AudioIcons { | ||||||
|  |         &ICONS | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | use re_set_lib::audio::audio_structures::{OutputStream, Source}; | ||||||
| use re_set_lib::signals::{ | use re_set_lib::signals::{ | ||||||
|     OutputStreamAdded, OutputStreamChanged, OutputStreamRemoved, SourceAdded, SourceChanged, |     OutputStreamAdded, OutputStreamChanged, OutputStreamRemoved, SourceAdded, SourceChanged, | ||||||
|     SourceRemoved, |     SourceRemoved, | ||||||
|  | @ -5,30 +6,23 @@ use re_set_lib::signals::{ | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| 
 | 
 | ||||||
| use adw::glib::Object; | use adw::glib::Object; | ||||||
| use adw::prelude::{ComboRowExt, ListBoxRowExt}; |  | ||||||
| use dbus::blocking::Connection; | use dbus::blocking::Connection; | ||||||
| use dbus::message::SignalArgs; |  | ||||||
| use dbus::Path; |  | ||||||
| use glib::subclass::prelude::ObjectSubclassIsExt; | use glib::subclass::prelude::ObjectSubclassIsExt; | ||||||
| use glib::Variant; |  | ||||||
| use gtk::gio; |  | ||||||
| use gtk::prelude::ActionableExt; |  | ||||||
| 
 | 
 | ||||||
|  | use crate::components::audio::audio_box_handlers::populate_audio_objects; | ||||||
|  | use crate::components::audio::audio_box_utils::{ | ||||||
|  |     setup_audio_box_callbacks, start_audio_box_listener, | ||||||
|  | }; | ||||||
|  | use crate::components::audio::audio_entry::TAudioBox; | ||||||
| use crate::components::audio::input::source_box_impl; | use crate::components::audio::input::source_box_impl; | ||||||
| use crate::components::base::error::{self}; | use crate::components::base::error::{self}; | ||||||
| use crate::components::base::error_impl::ReSetErrorImpl; | use crate::components::base::error_impl::ReSetErrorImpl; | ||||||
| use crate::components::utils::{ |  | ||||||
|     create_dropdown_label_factory, set_combo_row_ellipsis, BASE, DBUS_PATH, |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| use super::source_box_handlers::{ | use super::output_stream_entry::OutputStreamEntry; | ||||||
|     output_stream_added_handler, output_stream_changed_handler, output_stream_removed_handler, | use super::source_const::{ | ||||||
|     source_added_handler, source_changed_handler, source_removed_handler, |     GETDEFAULT, GETDEFAULTNAME, GETOBJECTS, GETSTREAMS, SETDEFAULT, SETMUTE, SETVOLUME, | ||||||
| }; |  | ||||||
| use super::source_box_utils::{ |  | ||||||
|     get_default_source, get_sources, populate_cards, populate_outputstreams, |  | ||||||
|     populate_source_information, |  | ||||||
| }; | }; | ||||||
|  | use super::source_entry::SourceEntry; | ||||||
| 
 | 
 | ||||||
| glib::wrapper! { | glib::wrapper! { | ||||||
|     pub struct SourceBox(ObjectSubclass<source_box_impl::SourceBox>) |     pub struct SourceBox(ObjectSubclass<source_box_impl::SourceBox>) | ||||||
|  | @ -45,9 +39,25 @@ impl ReSetErrorImpl for SourceBox { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl TAudioBox<super::source_box_impl::SourceBox> for SourceBox { | ||||||
|  |     fn box_imp(&self) -> &super::source_box_impl::SourceBox { | ||||||
|  |         self.imp() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl SourceBox { | impl SourceBox { | ||||||
|     pub fn new() -> Self { |     pub fn new() -> Self { | ||||||
|         let obj: Self = Object::builder().build(); |         let mut obj: Self = Object::builder().build(); | ||||||
|  |         setup_audio_box_callbacks::< | ||||||
|  |             Source, | ||||||
|  |             OutputStream, | ||||||
|  |             SourceEntry, | ||||||
|  |             super::source_entry_impl::SourceEntry, | ||||||
|  |             OutputStreamEntry, | ||||||
|  |             super::output_stream_entry_impl::OutputStreamEntry, | ||||||
|  |             SourceBox, | ||||||
|  |             super::source_box_impl::SourceBox, | ||||||
|  |         >(&mut obj); | ||||||
|         { |         { | ||||||
|             let imp = obj.imp(); |             let imp = obj.imp(); | ||||||
|             let mut model_index = imp.reset_model_index.write().unwrap(); |             let mut model_index = imp.reset_model_index.write().unwrap(); | ||||||
|  | @ -55,39 +65,6 @@ impl SourceBox { | ||||||
|         } |         } | ||||||
|         obj |         obj | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     pub fn setup_callbacks(&self) { |  | ||||||
|         let self_imp = self.imp(); |  | ||||||
|         self_imp.reset_source_row.set_activatable(true); |  | ||||||
|         self_imp |  | ||||||
|             .reset_source_row |  | ||||||
|             .set_action_name(Some("navigation.push")); |  | ||||||
|         self_imp |  | ||||||
|             .reset_source_row |  | ||||||
|             .set_action_target_value(Some(&Variant::from("sources"))); |  | ||||||
|         self_imp.reset_cards_row.set_activatable(true); |  | ||||||
|         self_imp |  | ||||||
|             .reset_cards_row |  | ||||||
|             .set_action_name(Some("navigation.push")); |  | ||||||
|         self_imp |  | ||||||
|             .reset_cards_row |  | ||||||
|             .set_action_target_value(Some(&Variant::from("profileConfiguration"))); |  | ||||||
| 
 |  | ||||||
|         self_imp.reset_output_stream_button.set_activatable(true); |  | ||||||
|         self_imp |  | ||||||
|             .reset_output_stream_button |  | ||||||
|             .set_action_name(Some("navigation.pop")); |  | ||||||
| 
 |  | ||||||
|         self_imp.reset_input_cards_back_button.set_activatable(true); |  | ||||||
|         self_imp |  | ||||||
|             .reset_input_cards_back_button |  | ||||||
|             .set_action_name(Some("navigation.pop")); |  | ||||||
| 
 |  | ||||||
|         self_imp |  | ||||||
|             .reset_source_dropdown |  | ||||||
|             .set_factory(Some(&create_dropdown_label_factory())); |  | ||||||
|         set_combo_row_ellipsis(self_imp.reset_source_dropdown.get()); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Default for SourceBox { | impl Default for SourceBox { | ||||||
|  | @ -97,107 +74,41 @@ impl Default for SourceBox { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn populate_sources(source_box: Arc<SourceBox>) { | pub fn populate_sources(source_box: Arc<SourceBox>) { | ||||||
|     gio::spawn_blocking(move || { |     populate_audio_objects::< | ||||||
|         let sources = get_sources(source_box.clone()); |         Source, | ||||||
|         { |         OutputStream, | ||||||
|             let source_box_imp = source_box.imp(); |         SourceEntry, | ||||||
|             let list = source_box_imp.reset_model_list.write().unwrap(); |         super::source_entry_impl::SourceEntry, | ||||||
|             let mut map = source_box_imp.reset_source_map.write().unwrap(); |         OutputStreamEntry, | ||||||
|             let mut model_index = source_box_imp.reset_model_index.write().unwrap(); |         super::output_stream_entry_impl::OutputStreamEntry, | ||||||
|             source_box_imp |         SourceBox, | ||||||
|                 .reset_default_source |         super::source_box_impl::SourceBox, | ||||||
|                 .replace(get_default_source(source_box.clone())); |     >( | ||||||
|             for source in sources.iter() { |         source_box, | ||||||
|                 list.append(&source.alias); |         &GETOBJECTS, | ||||||
|                 map.insert(source.alias.clone(), (source.index, source.name.clone())); |         &GETDEFAULT, | ||||||
|                 *model_index += 1; |         &SETDEFAULT, | ||||||
|             } |         &GETSTREAMS, | ||||||
|         } |         &SETVOLUME, | ||||||
| 
 |         &SETMUTE, | ||||||
|         populate_outputstreams(source_box.clone()); |     ); | ||||||
|         populate_cards(source_box.clone()); |  | ||||||
|         populate_source_information(source_box, sources); |  | ||||||
|     }); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn start_source_box_listener(conn: Connection, source_box: Arc<SourceBox>) -> Connection { | pub fn start_source_box_listener(conn: Connection, source_box: Arc<SourceBox>) -> Connection { | ||||||
|     let source_added = |     start_audio_box_listener::< | ||||||
|         SourceAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone(); |         Source, | ||||||
|     let source_removed = |         OutputStream, | ||||||
|         SourceRemoved::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone(); |         SourceEntry, | ||||||
|     let source_changed = |         super::source_entry_impl::SourceEntry, | ||||||
|         SourceChanged::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone(); |         OutputStreamEntry, | ||||||
|     let output_stream_added = |         super::output_stream_entry_impl::OutputStreamEntry, | ||||||
|         OutputStreamAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))) |         SourceBox, | ||||||
|             .static_clone(); |         super::source_box_impl::SourceBox, | ||||||
|     let output_stream_removed = |         SourceAdded, | ||||||
|         OutputStreamRemoved::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))) |         SourceChanged, | ||||||
|             .static_clone(); |         SourceRemoved, | ||||||
|     let output_stream_changed = |         OutputStreamAdded, | ||||||
|         OutputStreamChanged::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))) |         OutputStreamChanged, | ||||||
|             .static_clone(); |         OutputStreamRemoved, | ||||||
| 
 |     >(conn, source_box, &GETDEFAULTNAME) | ||||||
|     let source_added_box = source_box.clone(); |  | ||||||
|     let source_removed_box = source_box.clone(); |  | ||||||
|     let source_changed_box = source_box.clone(); |  | ||||||
|     let output_stream_added_box = source_box.clone(); |  | ||||||
|     let output_stream_removed_box = source_box.clone(); |  | ||||||
|     let output_stream_changed_box = source_box.clone(); |  | ||||||
| 
 |  | ||||||
|     let res = conn.add_match(source_added, move |ir: SourceAdded, _, _| { |  | ||||||
|         source_added_handler(source_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(source_removed, move |ir: SourceRemoved, _, _| { |  | ||||||
|         source_removed_handler(source_removed_box.clone(), ir) |  | ||||||
|     }); |  | ||||||
|     if res.is_err() { |  | ||||||
|         println!("fail on source remove event"); |  | ||||||
|         return conn; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let res = conn.add_match(source_changed, move |ir: SourceChanged, _, _| { |  | ||||||
|         source_changed_handler(source_changed_box.clone(), ir) |  | ||||||
|     }); |  | ||||||
|     if res.is_err() { |  | ||||||
|         println!("fail on source change event"); |  | ||||||
|         return conn; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let res = conn.add_match(output_stream_added, move |ir: OutputStreamAdded, _, _| { |  | ||||||
|         output_stream_added_handler(output_stream_added_box.clone(), ir) |  | ||||||
|     }); |  | ||||||
|     if res.is_err() { |  | ||||||
|         println!("fail on output stream add event"); |  | ||||||
|         return conn; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let res = conn.add_match( |  | ||||||
|         output_stream_changed, |  | ||||||
|         move |ir: OutputStreamChanged, _, _| { |  | ||||||
|             output_stream_changed_handler(output_stream_changed_box.clone(), ir) |  | ||||||
|         }, |  | ||||||
|     ); |  | ||||||
|     if res.is_err() { |  | ||||||
|         println!("fail on output stream change event"); |  | ||||||
|         return conn; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let res = conn.add_match( |  | ||||||
|         output_stream_removed, |  | ||||||
|         move |ir: OutputStreamRemoved, _, _| { |  | ||||||
|             output_stream_removed_handler(output_stream_removed_box.clone(), ir) |  | ||||||
|         }, |  | ||||||
|     ); |  | ||||||
|     if res.is_err() { |  | ||||||
|         println!("fail on output stream remove event"); |  | ||||||
|         return conn; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     conn |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,309 +0,0 @@ | ||||||
| use std::{ |  | ||||||
|     sync::Arc, |  | ||||||
|     time::{Duration, SystemTime}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| use adw::prelude::{ComboRowExt, PreferencesRowExt}; |  | ||||||
| use glib::prelude::Cast; |  | ||||||
| use glib::{subclass::types::ObjectSubclassIsExt, ControlFlow, Propagation}; |  | ||||||
| use gtk::{ |  | ||||||
|     gio, |  | ||||||
|     prelude::{BoxExt, ButtonExt, CheckButtonExt, ListBoxRowExt, RangeExt}, |  | ||||||
|     StringObject, |  | ||||||
| }; |  | ||||||
| use re_set_lib::signals::{ |  | ||||||
|     OutputStreamAdded, OutputStreamChanged, OutputStreamRemoved, SourceAdded, SourceChanged, |  | ||||||
|     SourceRemoved, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| use crate::components::base::list_entry::ListEntry; |  | ||||||
| 
 |  | ||||||
| use super::{ |  | ||||||
|     output_stream_entry::OutputStreamEntry, |  | ||||||
|     source_box::SourceBox, |  | ||||||
|     source_box_utils::{get_default_source_name, refresh_default_source}, |  | ||||||
|     source_entry::{set_default_source, set_source_volume, toggle_source_mute, SourceEntry}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| pub fn source_added_handler(source_box: Arc<SourceBox>, ir: SourceAdded) -> bool { |  | ||||||
|     glib::spawn_future(async move { |  | ||||||
|         glib::idle_add_once(move || { |  | ||||||
|             let source_box = source_box.clone(); |  | ||||||
|             let source_box_imp = source_box.imp(); |  | ||||||
|             let source_index = ir.source.index; |  | ||||||
|             let alias = ir.source.alias.clone(); |  | ||||||
|             let name = ir.source.name.clone(); |  | ||||||
|             let mut is_default = false; |  | ||||||
|             if source_box_imp.reset_default_source.borrow().name == ir.source.name { |  | ||||||
|                 is_default = true; |  | ||||||
|             } |  | ||||||
|             let source_entry = Arc::new(SourceEntry::new( |  | ||||||
|                 is_default, |  | ||||||
|                 source_box_imp.reset_default_check_button.clone(), |  | ||||||
|                 ir.source, |  | ||||||
|                 source_box.clone(), |  | ||||||
|             )); |  | ||||||
|             let source_clone = source_entry.clone(); |  | ||||||
|             let entry = Arc::new(ListEntry::new(&*source_entry)); |  | ||||||
|             entry.set_activatable(false); |  | ||||||
|             let mut list = source_box_imp.reset_source_list.write().unwrap(); |  | ||||||
|             list.insert(source_index, (entry.clone(), source_clone, alias.clone())); |  | ||||||
|             source_box_imp.reset_sources.append(&*entry); |  | ||||||
|             let mut map = source_box_imp.reset_source_map.write().unwrap(); |  | ||||||
|             let mut index = source_box_imp.reset_model_index.write().unwrap(); |  | ||||||
|             let model_list = source_box_imp.reset_model_list.write().unwrap(); |  | ||||||
|             if model_list.string(*index - 1) == Some("Monitor of Dummy Output".into()) { |  | ||||||
|                 model_list.append(&alias); |  | ||||||
|                 model_list.remove(*index - 1); |  | ||||||
|                 map.insert(alias, (source_index, name)); |  | ||||||
|                 source_box_imp.reset_source_dropdown.set_selected(0); |  | ||||||
|             } else { |  | ||||||
|                 model_list.append(&alias); |  | ||||||
|                 map.insert(alias.clone(), (source_index, name)); |  | ||||||
|                 if alias == "Monitor of Dummy Output" { |  | ||||||
|                     source_box_imp.reset_source_dropdown.set_selected(0); |  | ||||||
|                 } |  | ||||||
|                 *index += 1; |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn source_removed_handler(source_box: Arc<SourceBox>, ir: SourceRemoved) -> bool { |  | ||||||
|     glib::spawn_future(async move { |  | ||||||
|         glib::idle_add_once(move || { |  | ||||||
|             let source_box = source_box.clone(); |  | ||||||
|             let source_box_imp = source_box.imp(); |  | ||||||
|             let entry: Option<(Arc<ListEntry>, Arc<SourceEntry>, String)>; |  | ||||||
|             { |  | ||||||
|                 let mut list = source_box_imp.reset_source_list.write().unwrap(); |  | ||||||
|                 entry = list.remove(&ir.index); |  | ||||||
|                 if entry.is_none() { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             source_box_imp |  | ||||||
|                 .reset_sources |  | ||||||
|                 .remove(&*entry.clone().unwrap().0); |  | ||||||
|             let mut map = source_box_imp.reset_source_map.write().unwrap(); |  | ||||||
|             let alias = entry.unwrap().2; |  | ||||||
|             map.remove(&alias); |  | ||||||
|             let mut index = source_box_imp.reset_model_index.write().unwrap(); |  | ||||||
|             let model_list = source_box_imp.reset_model_list.write().unwrap(); |  | ||||||
| 
 |  | ||||||
|             if *index == 1 { |  | ||||||
|                 model_list.append("Monitor of Dummy Output"); |  | ||||||
|             } |  | ||||||
|             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 source_changed_handler(source_box: Arc<SourceBox>, ir: SourceChanged) -> bool { |  | ||||||
|     let default_source = get_default_source_name(source_box.clone()); |  | ||||||
|     glib::spawn_future(async move { |  | ||||||
|         glib::idle_add_once(move || { |  | ||||||
|             let source_box = source_box.clone(); |  | ||||||
|             let source_box_imp = source_box.imp(); |  | ||||||
|             let is_default = ir.source.name == default_source; |  | ||||||
|             let volume = ir.source.volume.first().unwrap_or(&0_u32); |  | ||||||
|             let fraction = (*volume as f64 / 655.36).round(); |  | ||||||
|             let percentage = (fraction).to_string() + "%"; |  | ||||||
| 
 |  | ||||||
|             let list = source_box_imp.reset_source_list.read().unwrap(); |  | ||||||
|             let entry = list.get(&ir.source.index); |  | ||||||
|             if entry.is_none() { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|             let imp = entry.unwrap().1.imp(); |  | ||||||
|             if is_default { |  | ||||||
|                 source_box_imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|                 source_box_imp.reset_volume_slider.set_value(*volume as f64); |  | ||||||
|                 source_box_imp |  | ||||||
|                     .reset_default_source |  | ||||||
|                     .replace(ir.source.clone()); |  | ||||||
|                 if ir.source.muted { |  | ||||||
|                     source_box_imp |  | ||||||
|                         .reset_source_mute |  | ||||||
|                         .set_icon_name("microphone-disabled-symbolic"); |  | ||||||
|                 } else { |  | ||||||
|                     source_box_imp |  | ||||||
|                         .reset_source_mute |  | ||||||
|                         .set_icon_name("audio-input-microphone-symbolic"); |  | ||||||
|                 } |  | ||||||
|                 imp.reset_selected_source.set_active(true); |  | ||||||
|             } else { |  | ||||||
|                 imp.reset_selected_source.set_active(false); |  | ||||||
|             } |  | ||||||
|             imp.reset_source_name |  | ||||||
|                 .set_title(ir.source.alias.clone().as_str()); |  | ||||||
|             imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|             imp.reset_volume_slider.set_value(*volume as f64); |  | ||||||
|             if ir.source.muted { |  | ||||||
|                 imp.reset_source_mute |  | ||||||
|                     .set_icon_name("microphone-disabled-symbolic"); |  | ||||||
|             } else { |  | ||||||
|                 imp.reset_source_mute |  | ||||||
|                     .set_icon_name("audio-input-microphone-symbolic"); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn output_stream_added_handler(source_box: Arc<SourceBox>, ir: OutputStreamAdded) -> bool { |  | ||||||
|     glib::spawn_future(async move { |  | ||||||
|         glib::idle_add_once(move || { |  | ||||||
|             let source_box = source_box.clone(); |  | ||||||
|             let source_box_imp = source_box.imp(); |  | ||||||
|             let mut list = source_box_imp.reset_output_stream_list.write().unwrap(); |  | ||||||
|             let index = ir.stream.index; |  | ||||||
|             let output_stream = Arc::new(OutputStreamEntry::new(source_box.clone(), ir.stream)); |  | ||||||
|             let entry = Arc::new(ListEntry::new(&*output_stream)); |  | ||||||
|             entry.set_activatable(false); |  | ||||||
|             list.insert(index, (entry.clone(), output_stream.clone())); |  | ||||||
|             source_box_imp.reset_output_streams.append(&*entry); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn output_stream_changed_handler(source_box: Arc<SourceBox>, ir: OutputStreamChanged) -> bool { |  | ||||||
|     let imp = source_box.imp(); |  | ||||||
|     let alias: String; |  | ||||||
|     { |  | ||||||
|         let source_list = imp.reset_source_list.read().unwrap(); |  | ||||||
|         if let Some(alias_opt) = source_list.get(&ir.stream.source_index) { |  | ||||||
|             alias = alias_opt.2.clone(); |  | ||||||
|         } else { |  | ||||||
|             alias = String::from(""); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     glib::spawn_future(async move { |  | ||||||
|         glib::idle_add_once(move || { |  | ||||||
|             let source_box = source_box.clone(); |  | ||||||
|             let source_box_imp = source_box.imp(); |  | ||||||
|             let entry: Arc<OutputStreamEntry>; |  | ||||||
|             { |  | ||||||
|                 let list = source_box_imp.reset_output_stream_list.read().unwrap(); |  | ||||||
|                 let entry_opt = list.get(&ir.stream.index); |  | ||||||
|                 if entry_opt.is_none() { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 entry = entry_opt.unwrap().1.clone(); |  | ||||||
|             } |  | ||||||
|             let imp = entry.imp(); |  | ||||||
|             if ir.stream.muted { |  | ||||||
|                 imp.reset_source_mute |  | ||||||
|                     .set_icon_name("microphone-disabled-symbolic"); |  | ||||||
|             } else { |  | ||||||
|                 imp.reset_source_mute |  | ||||||
|                     .set_icon_name("audio-input-microphone-symbolic"); |  | ||||||
|             } |  | ||||||
|             let name = ir.stream.application_name.clone() + ": " + ir.stream.name.as_str(); |  | ||||||
|             imp.reset_source_selection.set_title(name.as_str()); |  | ||||||
|             let volume = ir.stream.volume.first().unwrap_or(&0_u32); |  | ||||||
|             let fraction = (*volume as f64 / 655.36).round(); |  | ||||||
|             let percentage = (fraction).to_string() + "%"; |  | ||||||
|             imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|             imp.reset_volume_slider.set_value(*volume as f64); |  | ||||||
|             let index = source_box_imp.reset_model_index.read().unwrap(); |  | ||||||
|             let model_list = source_box_imp.reset_model_list.read().unwrap(); |  | ||||||
|             for entry in 0..*index { |  | ||||||
|                 if model_list.string(entry) == Some(alias.clone().into()) { |  | ||||||
|                     imp.reset_source_selection.set_selected(entry); |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn output_stream_removed_handler(source_box: Arc<SourceBox>, ir: OutputStreamRemoved) -> bool { |  | ||||||
|     glib::spawn_future(async move { |  | ||||||
|         glib::idle_add_once(move || { |  | ||||||
|             let source_box = source_box.clone(); |  | ||||||
|             let source_box_imp = source_box.imp(); |  | ||||||
|             let mut list = source_box_imp.reset_output_stream_list.write().unwrap(); |  | ||||||
|             let entry = list.remove(&ir.index); |  | ||||||
|             if entry.is_none() { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|             source_box_imp |  | ||||||
|                 .reset_output_streams |  | ||||||
|                 .remove(&*entry.unwrap().0); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn dropdown_handler(source_box: Arc<SourceBox>, dropdown: &adw::ComboRow) -> ControlFlow { |  | ||||||
|     let source_box_imp = source_box.imp(); |  | ||||||
|     let source_box_ref = source_box.clone(); |  | ||||||
|     let selected = dropdown.selected_item(); |  | ||||||
|     if selected.is_none() { |  | ||||||
|         return ControlFlow::Break; |  | ||||||
|     } |  | ||||||
|     let selected = selected.unwrap(); |  | ||||||
|     let selected = selected.downcast_ref::<StringObject>().unwrap(); |  | ||||||
|     let selected = selected.string().to_string(); |  | ||||||
|     let source = source_box_imp.reset_source_map.read().unwrap(); |  | ||||||
|     let source = source.get(&selected); |  | ||||||
|     if source.is_none() { |  | ||||||
|         return ControlFlow::Break; |  | ||||||
|     } |  | ||||||
|     let source = Arc::new(source.unwrap().1.clone()); |  | ||||||
|     gio::spawn_blocking(move || { |  | ||||||
|         let result = set_default_source(source, source_box_ref.clone()); |  | ||||||
|         if result.is_none() { |  | ||||||
|             return ControlFlow::Break; |  | ||||||
|         } |  | ||||||
|         refresh_default_source(result.unwrap(), source_box_ref.clone(), false); |  | ||||||
|         ControlFlow::Continue |  | ||||||
|     }); |  | ||||||
|     ControlFlow::Continue |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn volume_slider_handler(source_box: Arc<SourceBox>, value: f64) -> Propagation { |  | ||||||
|     let imp = source_box.imp(); |  | ||||||
|     let fraction = (value / 655.36).round(); |  | ||||||
|     let percentage = (fraction).to_string() + "%"; |  | ||||||
|     imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|     let source = imp.reset_default_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()); |  | ||||||
|     } |  | ||||||
|     set_source_volume(value, index, channels, source_box.clone()); |  | ||||||
|     Propagation::Proceed |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn mute_clicked_handler(source_box_ref_mute: Arc<SourceBox>) { |  | ||||||
|     let imp = source_box_ref_mute.imp(); |  | ||||||
|     let mut source = imp.reset_default_source.borrow_mut(); |  | ||||||
|     source.muted = !source.muted; |  | ||||||
|     if source.muted { |  | ||||||
|         imp.reset_source_mute |  | ||||||
|             .set_icon_name("microphone-disabled-symbolic"); |  | ||||||
|     } else { |  | ||||||
|         imp.reset_source_mute |  | ||||||
|             .set_icon_name("audio-input-microphone-symbolic"); |  | ||||||
|     } |  | ||||||
|     toggle_source_mute(source.index, source.muted, source_box_ref_mute.clone()); |  | ||||||
| } |  | ||||||
|  | @ -5,6 +5,7 @@ use std::collections::HashMap; | ||||||
| use std::sync::{Arc, RwLock}; | use std::sync::{Arc, RwLock}; | ||||||
| use std::time::SystemTime; | use std::time::SystemTime; | ||||||
| 
 | 
 | ||||||
|  | use crate::components::audio::audio_entry::{AudioIcons, TAudioBoxImpl}; | ||||||
| use crate::components::audio::input::source_box; | use crate::components::audio::input::source_box; | ||||||
| use crate::components::base::error::ReSetError; | use crate::components::base::error::ReSetError; | ||||||
| use crate::components::base::list_entry::ListEntry; | use crate::components::base::list_entry::ListEntry; | ||||||
|  | @ -13,6 +14,7 @@ use gtk::{prelude::*, Button, Label, Scale}; | ||||||
| use gtk::{CheckButton, CompositeTemplate, StringList}; | use gtk::{CheckButton, CompositeTemplate, StringList}; | ||||||
| 
 | 
 | ||||||
| use super::output_stream_entry::OutputStreamEntry; | use super::output_stream_entry::OutputStreamEntry; | ||||||
|  | use super::source_const::ICONS; | ||||||
| use super::source_entry::SourceEntry; | use super::source_entry::SourceEntry; | ||||||
| 
 | 
 | ||||||
| type SourceEntryMap = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<SourceEntry>, String)>>>; | type SourceEntryMap = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<SourceEntry>, String)>>>; | ||||||
|  | @ -35,7 +37,6 @@ pub struct SourceBox { | ||||||
|     pub reset_volume_slider: TemplateChild<Scale>, |     pub reset_volume_slider: TemplateChild<Scale>, | ||||||
|     #[template_child] |     #[template_child] | ||||||
|     pub reset_volume_percentage: TemplateChild<Label>, |     pub reset_volume_percentage: TemplateChild<Label>, | ||||||
| 
 |  | ||||||
|     #[template_child] |     #[template_child] | ||||||
|     pub reset_sources: TemplateChild<gtk::Box>, |     pub reset_sources: TemplateChild<gtk::Box>, | ||||||
|     #[template_child] |     #[template_child] | ||||||
|  | @ -79,12 +80,7 @@ impl ObjectSubclass for SourceBox { | ||||||
| 
 | 
 | ||||||
| impl BoxImpl for SourceBox {} | impl BoxImpl for SourceBox {} | ||||||
| 
 | 
 | ||||||
| impl ObjectImpl for SourceBox { | impl ObjectImpl for SourceBox {} | ||||||
|     fn constructed(&self) { |  | ||||||
|         let obj = self.obj(); |  | ||||||
|         obj.setup_callbacks(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| impl ListBoxRowImpl for SourceBox {} | impl ListBoxRowImpl for SourceBox {} | ||||||
| 
 | 
 | ||||||
|  | @ -93,3 +89,93 @@ impl WidgetImpl for SourceBox {} | ||||||
| impl WindowImpl for SourceBox {} | impl WindowImpl for SourceBox {} | ||||||
| 
 | 
 | ||||||
| impl ApplicationWindowImpl for SourceBox {} | impl ApplicationWindowImpl for SourceBox {} | ||||||
|  | 
 | ||||||
|  | impl TAudioBoxImpl<Source, SourceEntry, OutputStreamEntry> for SourceBox { | ||||||
|  |     fn audio_object_row(&self) -> &TemplateChild<ActionRow> { | ||||||
|  |         &self.reset_source_row | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn cards_row(&self) -> &TemplateChild<ActionRow> { | ||||||
|  |         &self.reset_cards_row | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object_dropdown(&self) -> &TemplateChild<ComboRow> { | ||||||
|  |         &self.reset_source_dropdown | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object_mute(&self) -> &TemplateChild<Button> { | ||||||
|  |         &self.reset_source_mute | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_slider(&self) -> &TemplateChild<Scale> { | ||||||
|  |         &self.reset_volume_slider | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_percentage(&self) -> &TemplateChild<Label> { | ||||||
|  |         &self.reset_volume_percentage | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_objects(&self) -> &TemplateChild<gtk::Box> { | ||||||
|  |         &self.reset_sources | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object_stream_button(&self) -> &TemplateChild<ActionRow> { | ||||||
|  |         &self.reset_output_stream_button | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object_streams(&self) -> &TemplateChild<gtk::Box> { | ||||||
|  |         &self.reset_output_streams | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn cards_button(&self) -> &TemplateChild<ActionRow> { | ||||||
|  |         &self.reset_input_cards_back_button | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn cards(&self) -> &TemplateChild<PreferencesGroup> { | ||||||
|  |         &self.reset_cards | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn error(&self) -> &TemplateChild<ReSetError> { | ||||||
|  |         &self.error | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn default_check_button(&self) -> Arc<CheckButton> { | ||||||
|  |         self.reset_default_check_button.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn default_audio_object(&self) -> Arc<RefCell<Source>> { | ||||||
|  |         self.reset_default_source.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object_list( | ||||||
|  |         &self, | ||||||
|  |     ) -> &crate::components::audio::audio_entry::AudioEntryMap<SourceEntry> { | ||||||
|  |         &self.reset_source_list | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object_stream_list( | ||||||
|  |         &self, | ||||||
|  |     ) -> &crate::components::audio::audio_entry::AudioStreamEntryMap<OutputStreamEntry> { | ||||||
|  |         &self.reset_output_stream_list | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn model_list(&self) -> Arc<RwLock<StringList>> { | ||||||
|  |         self.reset_model_list.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn model_index(&self) -> Arc<RwLock<u32>> { | ||||||
|  |         self.reset_model_index.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn source_map(&self) -> &crate::components::audio::audio_entry::AudioMap { | ||||||
|  |         &self.reset_source_map | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>> { | ||||||
|  |         &self.volume_time_stamp | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn icons(&self) -> &AudioIcons { | ||||||
|  |         &ICONS | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,229 +0,0 @@ | ||||||
| use std::{sync::Arc, time::Duration}; |  | ||||||
| 
 |  | ||||||
| use adw::prelude::{ComboRowExt, PreferencesGroupExt}; |  | ||||||
| use dbus::{blocking::Connection, Error}; |  | ||||||
| use glib::subclass::types::ObjectSubclassIsExt; |  | ||||||
| use gtk::{ |  | ||||||
|     gio, |  | ||||||
|     prelude::{BoxExt, ButtonExt, CheckButtonExt, ListBoxRowExt, RangeExt}, |  | ||||||
| }; |  | ||||||
| use re_set_lib::audio::audio_structures::{Card, OutputStream, Source}; |  | ||||||
| 
 |  | ||||||
| use crate::components::{ |  | ||||||
|     base::{card_entry::CardEntry, error_impl::show_error, list_entry::ListEntry}, |  | ||||||
|     utils::{AUDIO, BASE, DBUS_PATH}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| use super::{ |  | ||||||
|     output_stream_entry::OutputStreamEntry, |  | ||||||
|     source_box::SourceBox, |  | ||||||
|     source_box_handlers::{dropdown_handler, mute_clicked_handler, volume_slider_handler}, |  | ||||||
|     source_entry::SourceEntry, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| pub fn populate_source_information(source_box: Arc<SourceBox>, sources: Vec<Source>) { |  | ||||||
|     glib::spawn_future(async move { |  | ||||||
|         glib::idle_add_once(move || { |  | ||||||
|             let source_box_ref_slider = source_box.clone(); |  | ||||||
|             let source_box_ref_toggle = source_box.clone(); |  | ||||||
|             let source_box_ref_mute = source_box.clone(); |  | ||||||
|             let source_box_imp = source_box.imp(); |  | ||||||
|             let default_sink = source_box_imp.reset_default_source.clone(); |  | ||||||
|             let source = default_sink.borrow(); |  | ||||||
| 
 |  | ||||||
|             if source.muted { |  | ||||||
|                 source_box_imp |  | ||||||
|                     .reset_source_mute |  | ||||||
|                     .set_icon_name("microphone-disabled-symbolic"); |  | ||||||
|             } else { |  | ||||||
|                 source_box_imp |  | ||||||
|                     .reset_source_mute |  | ||||||
|                     .set_icon_name("audio-input-microphone-symbolic"); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             let volume = source.volume.first().unwrap_or(&0_u32); |  | ||||||
|             let fraction = (*volume as f64 / 655.36).round(); |  | ||||||
|             let percentage = (fraction).to_string() + "%"; |  | ||||||
|             source_box_imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|             source_box_imp.reset_volume_slider.set_value(*volume as f64); |  | ||||||
|             let mut list = source_box_imp.reset_source_list.write().unwrap(); |  | ||||||
|             for source in sources { |  | ||||||
|                 let index = source.index; |  | ||||||
|                 let alias = source.alias.clone(); |  | ||||||
|                 let mut is_default = false; |  | ||||||
|                 if source_box_imp.reset_default_source.borrow().name == source.name { |  | ||||||
|                     is_default = true; |  | ||||||
|                 } |  | ||||||
|                 let source_entry = Arc::new(SourceEntry::new( |  | ||||||
|                     is_default, |  | ||||||
|                     source_box_imp.reset_default_check_button.clone(), |  | ||||||
|                     source, |  | ||||||
|                     source_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)); |  | ||||||
|                 source_box_imp.reset_sources.append(&*entry); |  | ||||||
|             } |  | ||||||
|             let list = source_box_imp.reset_model_list.read().unwrap(); |  | ||||||
|             source_box_imp.reset_source_dropdown.set_model(Some(&*list)); |  | ||||||
|             let name = source_box_imp.reset_default_source.borrow(); |  | ||||||
| 
 |  | ||||||
|             let index = source_box_imp.reset_model_index.read().unwrap(); |  | ||||||
|             let model_list = source_box_imp.reset_model_list.read().unwrap(); |  | ||||||
|             for entry in 0..*index { |  | ||||||
|                 if model_list.string(entry) == Some(name.alias.clone().into()) { |  | ||||||
|                     source_box_imp.reset_source_dropdown.set_selected(entry); |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             source_box_imp |  | ||||||
|                 .reset_source_dropdown |  | ||||||
|                 .connect_selected_notify(move |dropdown| { |  | ||||||
|                     dropdown_handler(source_box_ref_toggle.clone(), dropdown); |  | ||||||
|                 }); |  | ||||||
|             source_box_imp |  | ||||||
|                 .reset_volume_slider |  | ||||||
|                 .connect_change_value(move |_, _, value| { |  | ||||||
|                     volume_slider_handler(source_box_ref_slider.clone(), value) |  | ||||||
|                 }); |  | ||||||
| 
 |  | ||||||
|             source_box_imp.reset_source_mute.connect_clicked(move |_| { |  | ||||||
|                 mute_clicked_handler(source_box_ref_mute.clone()); |  | ||||||
|             }); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn refresh_default_source(new_source: Source, source_box: Arc<SourceBox>, entry: bool) { |  | ||||||
|     let volume = *new_source.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 = source_box.imp(); |  | ||||||
|             if !entry { |  | ||||||
|                 let list = imp.reset_source_list.read().unwrap(); |  | ||||||
|                 let entry = list.get(&new_source.index); |  | ||||||
|                 if entry.is_none() { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 let entry_imp = entry.unwrap().1.imp(); |  | ||||||
|                 entry_imp.reset_selected_source.set_active(true); |  | ||||||
|             } else { |  | ||||||
|                 let model_list = imp.reset_model_list.read().unwrap(); |  | ||||||
|                 for entry in 0..*imp.reset_model_index.read().unwrap() { |  | ||||||
|                     if model_list.string(entry) == Some(new_source.alias.clone().into()) { |  | ||||||
|                         imp.reset_source_dropdown.set_selected(entry); |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|             imp.reset_volume_slider.set_value(volume as f64); |  | ||||||
|             if new_source.muted { |  | ||||||
|                 imp.reset_source_mute |  | ||||||
|                     .set_icon_name("microphone-disabled-symbolic"); |  | ||||||
|             } else { |  | ||||||
|                 imp.reset_source_mute |  | ||||||
|                     .set_icon_name("audio-input-microphone-symbolic"); |  | ||||||
|             } |  | ||||||
|             imp.reset_default_source.replace(new_source); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn populate_outputstreams(source_box: Arc<SourceBox>) { |  | ||||||
|     let source_box_ref = source_box.clone(); |  | ||||||
| 
 |  | ||||||
|     gio::spawn_blocking(move || { |  | ||||||
|         let streams = get_output_streams(source_box.clone()); |  | ||||||
|         glib::spawn_future(async move { |  | ||||||
|             glib::idle_add_once(move || { |  | ||||||
|                 let source_box_imp = source_box_ref.imp(); |  | ||||||
|                 let mut list = source_box_imp.reset_output_stream_list.write().unwrap(); |  | ||||||
|                 for stream in streams { |  | ||||||
|                     let index = stream.index; |  | ||||||
|                     let input_stream = Arc::new(OutputStreamEntry::new(source_box.clone(), stream)); |  | ||||||
|                     let input_stream_clone = input_stream.clone(); |  | ||||||
|                     let entry = Arc::new(ListEntry::new(&*input_stream)); |  | ||||||
|                     entry.set_activatable(false); |  | ||||||
|                     list.insert(index, (entry.clone(), input_stream_clone)); |  | ||||||
|                     source_box_imp.reset_output_streams.append(&*entry); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn populate_cards(source_box: Arc<SourceBox>) { |  | ||||||
|     gio::spawn_blocking(move || { |  | ||||||
|         let source_box_ref = source_box.clone(); |  | ||||||
|         let cards = get_cards(source_box.clone()); |  | ||||||
|         glib::spawn_future(async move { |  | ||||||
|             glib::idle_add_once(move || { |  | ||||||
|                 let imp = source_box_ref.imp(); |  | ||||||
|                 for card in cards { |  | ||||||
|                     imp.reset_cards.add(&CardEntry::new(card)); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn get_output_streams(source_box: Arc<SourceBox>) -> Vec<OutputStream> { |  | ||||||
|     let conn = Connection::new_session().unwrap(); |  | ||||||
|     let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|     let res: Result<(Vec<OutputStream>,), Error> = |  | ||||||
|         proxy.method_call(AUDIO, "ListOutputStreams", ()); |  | ||||||
|     if res.is_err() { |  | ||||||
|         show_error::<SourceBox>(source_box.clone(), "Failed to get output streams"); |  | ||||||
|         return Vec::new(); |  | ||||||
|     } |  | ||||||
|     res.unwrap().0 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn get_sources(source_box: Arc<SourceBox>) -> Vec<Source> { |  | ||||||
|     let conn = Connection::new_session().unwrap(); |  | ||||||
|     let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|     let res: Result<(Vec<Source>,), Error> = proxy.method_call(AUDIO, "ListSources", ()); |  | ||||||
|     if res.is_err() { |  | ||||||
|         show_error::<SourceBox>(source_box.clone(), "Failed to get sources"); |  | ||||||
|         return Vec::new(); |  | ||||||
|     } |  | ||||||
|     res.unwrap().0 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn get_cards(source_box: Arc<SourceBox>) -> Vec<Card> { |  | ||||||
|     let conn = Connection::new_session().unwrap(); |  | ||||||
|     let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|     let res: Result<(Vec<Card>,), Error> = proxy.method_call(AUDIO, "ListCards", ()); |  | ||||||
|     if res.is_err() { |  | ||||||
|         show_error::<SourceBox>(source_box.clone(), "Failed to get profiles"); |  | ||||||
|         return Vec::new(); |  | ||||||
|     } |  | ||||||
|     res.unwrap().0 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn get_default_source_name(source_box: Arc<SourceBox>) -> String { |  | ||||||
|     let conn = Connection::new_session().unwrap(); |  | ||||||
|     let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|     let res: Result<(String,), Error> = proxy.method_call(AUDIO, "GetDefaultSourceName", ()); |  | ||||||
|     if res.is_err() { |  | ||||||
|         show_error::<SourceBox>(source_box.clone(), "Failed to get default source name"); |  | ||||||
|         return String::from(""); |  | ||||||
|     } |  | ||||||
|     res.unwrap().0 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn get_default_source(source_box: Arc<SourceBox>) -> Source { |  | ||||||
|     let conn = Connection::new_session().unwrap(); |  | ||||||
|     let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|     let res: Result<(Source,), Error> = proxy.method_call(AUDIO, "GetDefaultSource", ()); |  | ||||||
|     if res.is_err() { |  | ||||||
|         show_error::<SourceBox>(source_box.clone(), "Failed to get default source"); |  | ||||||
|         return Source::default(); |  | ||||||
|     } |  | ||||||
|     res.unwrap().0 |  | ||||||
| } |  | ||||||
							
								
								
									
										56
									
								
								src/components/audio/input/source_const.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/components/audio/input/source_const.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | ||||||
|  | use crate::components::audio::audio_entry::{AudioIcons, DBusFunction}; | ||||||
|  | 
 | ||||||
|  | pub const ICONS: AudioIcons = AudioIcons { | ||||||
|  |     muted: "microphone-disabled-symbolic", | ||||||
|  |     active: "audio-input-microphone-symbolic", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const SETVOLUME: DBusFunction = DBusFunction { | ||||||
|  |     function: "SetSourceVolume", | ||||||
|  |     error: "Failed to set source volume", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const SETMUTE: DBusFunction = DBusFunction { | ||||||
|  |     function: "SetSourceMute", | ||||||
|  |     error: "Failed to mute source", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const SETDEFAULT: DBusFunction = DBusFunction { | ||||||
|  |     function: "SetDefaultSource", | ||||||
|  |     error: "Failed to set default source", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const GETDEFAULT: DBusFunction = DBusFunction { | ||||||
|  |     function: "GetDefaultSource", | ||||||
|  |     error: "Failed to get default source", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const GETDEFAULTNAME: DBusFunction = DBusFunction { | ||||||
|  |     function: "GetDefaultSourceName", | ||||||
|  |     error: "Failed to get default source name", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const GETOBJECTS: DBusFunction = DBusFunction { | ||||||
|  |     function: "ListSources", | ||||||
|  |     error: "Failed to list sources", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const GETSTREAMS: DBusFunction = DBusFunction { | ||||||
|  |     function: "ListOutputStreams", | ||||||
|  |     error: "Failed to list output streams", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const SETSTREAMVOLUME: DBusFunction = DBusFunction { | ||||||
|  |     function: "SetOutputStreamVolume", | ||||||
|  |     error: "Failed to set output stream volume", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const SETSTREAMMUTE: DBusFunction = DBusFunction { | ||||||
|  |     function: "SetOutputStreamMute", | ||||||
|  |     error: "Failed to mute output stream", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const SETSTREAMOBJECT: DBusFunction = DBusFunction { | ||||||
|  |     function: "SetSourceOfOutputStream", | ||||||
|  |     error: "Failed to set source of output stream", | ||||||
|  | }; | ||||||
|  | @ -1,21 +1,12 @@ | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| use std::time::{Duration, SystemTime}; |  | ||||||
| 
 | 
 | ||||||
| use crate::components::base::error_impl::show_error; | use crate::components::audio::audio_entry::{new_entry, TAudioEntry}; | ||||||
| use crate::components::utils::set_action_row_ellipsis; |  | ||||||
| use adw::glib::Object; |  | ||||||
| use adw::prelude::{ButtonExt, CheckButtonExt, PreferencesRowExt, RangeExt}; |  | ||||||
| use dbus::blocking::Connection; |  | ||||||
| use dbus::Error; |  | ||||||
| use glib::subclass::types::ObjectSubclassIsExt; | use glib::subclass::types::ObjectSubclassIsExt; | ||||||
| use glib::{clone, Propagation}; | use gtk::CheckButton; | ||||||
| use gtk::{gio, CheckButton}; | use re_set_lib::audio::audio_structures::{OutputStream, Source}; | ||||||
| use re_set_lib::audio::audio_structures::Source; |  | ||||||
| 
 |  | ||||||
| use crate::components::utils::{AUDIO, BASE, DBUS_PATH}; |  | ||||||
| 
 | 
 | ||||||
|  | use super::output_stream_entry::OutputStreamEntry; | ||||||
| use super::source_box::SourceBox; | use super::source_box::SourceBox; | ||||||
| use super::source_box_utils::refresh_default_source; |  | ||||||
| use super::source_entry_impl; | use super::source_entry_impl; | ||||||
| 
 | 
 | ||||||
| glib::wrapper! { | glib::wrapper! { | ||||||
|  | @ -27,121 +18,28 @@ glib::wrapper! { | ||||||
| unsafe impl Send for SourceEntry {} | unsafe impl Send for SourceEntry {} | ||||||
| unsafe impl Sync for SourceEntry {} | unsafe impl Sync for SourceEntry {} | ||||||
| 
 | 
 | ||||||
|  | impl TAudioEntry<super::source_entry_impl::SourceEntry> for SourceEntry { | ||||||
|  |     fn entry_imp(&self) -> &super::source_entry_impl::SourceEntry { | ||||||
|  |         self.imp() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl SourceEntry { | impl SourceEntry { | ||||||
|     pub fn new( |     pub fn new( | ||||||
|         is_default: bool, |         is_default: bool, | ||||||
|         check_group: Arc<CheckButton>, |         check_group: Arc<CheckButton>, | ||||||
|         source: Source, |         source: Source, | ||||||
|         input_box: Arc<SourceBox>, |         input_box: Arc<SourceBox>, | ||||||
|     ) -> Self { |     ) -> Arc<Self> { | ||||||
|         let obj: Self = Object::builder().build(); |         new_entry::< | ||||||
|         // TODO use event callback for progress bar -> this is the "im speaking" indicator
 |             Source, | ||||||
|         { |             OutputStream, | ||||||
|             let imp = obj.imp(); |             SourceEntry, | ||||||
|             imp.reset_source_name |             super::source_entry_impl::SourceEntry, | ||||||
|                 .set_title(source.alias.clone().as_str()); |             OutputStreamEntry, | ||||||
|             let name = Arc::new(source.name.clone()); |             super::output_stream_entry_impl::OutputStreamEntry, | ||||||
|             let volume = source.volume.first().unwrap_or(&0_u32); |             SourceBox, | ||||||
|             let fraction = (*volume as f64 / 655.36).round(); |             super::source_box_impl::SourceBox, | ||||||
|             let percentage = (fraction).to_string() + "%"; |         >(is_default, check_group, source, input_box) | ||||||
|             let input_box_slider = input_box.clone(); |  | ||||||
|             let input_box_ref = input_box.clone(); |  | ||||||
|             imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|             imp.reset_volume_slider.set_value(*volume as f64); |  | ||||||
|             imp.source.replace(source); |  | ||||||
|             imp.reset_volume_slider.connect_change_value( |  | ||||||
|                 clone!(@weak imp => @default-return Propagation::Stop, move |_, _, value| { |  | ||||||
|                     let fraction = (value / 655.36).round(); |  | ||||||
|                     let percentage = (fraction).to_string() + "%"; |  | ||||||
|                     imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|                     let source = imp.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()); |  | ||||||
|                     } |  | ||||||
|                     set_source_volume(value, index, channels, input_box_slider.clone()); |  | ||||||
|                     Propagation::Proceed |  | ||||||
|                 }), |  | ||||||
|             ); |  | ||||||
|             imp.reset_selected_source.set_group(Some(&*check_group)); |  | ||||||
|             if is_default { |  | ||||||
|                 imp.reset_selected_source.set_active(true); |  | ||||||
|             } else { |  | ||||||
|                 imp.reset_selected_source.set_active(false); |  | ||||||
|             } |  | ||||||
|             imp.reset_selected_source.connect_toggled(move |button| { |  | ||||||
|                 let input_box = input_box.clone(); |  | ||||||
|                 if button.is_active() { |  | ||||||
|                     let name = name.clone(); |  | ||||||
|                     gio::spawn_blocking(move || { |  | ||||||
|                         let result = set_default_source(name, input_box.clone()); |  | ||||||
|                         if result.is_none() { |  | ||||||
|                             return; |  | ||||||
|                         } |  | ||||||
|                         refresh_default_source(result.unwrap(), input_box, true); |  | ||||||
|                     }); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|             imp.reset_source_mute |  | ||||||
|                 .connect_clicked(clone!(@weak imp => move |_| { |  | ||||||
|                     let mut source = imp.source.borrow_mut(); |  | ||||||
|                     source.muted = !source.muted; |  | ||||||
|                     if source.muted { |  | ||||||
|                         imp.reset_source_mute |  | ||||||
|                            .set_icon_name("microphone-disabled-symbolic"); |  | ||||||
|                     } else { |  | ||||||
|                         imp.reset_source_mute |  | ||||||
|                            .set_icon_name("audio-input-microphone-symbolic"); |  | ||||||
|                     } |  | ||||||
|                     toggle_source_mute(source.index, source.muted, input_box_ref.clone()); |  | ||||||
|                 })); |  | ||||||
|             set_action_row_ellipsis(imp.reset_source_name.get()); |  | ||||||
|         } |  | ||||||
|         obj |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| pub fn set_source_volume(value: f64, index: u32, channels: u16, input_box: Arc<SourceBox>) -> bool { |  | ||||||
|     gio::spawn_blocking(move || { |  | ||||||
|         let conn = Connection::new_session().unwrap(); |  | ||||||
|         let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|         let res: Result<(), Error> = |  | ||||||
|             proxy.method_call(AUDIO, "SetSourceVolume", (index, channels, value as u32)); |  | ||||||
|         if res.is_err() { |  | ||||||
|             // TODO: also log this with LOG/ERROR
 |  | ||||||
|             show_error::<SourceBox>(input_box.clone(), "Failed to set source volume"); |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn toggle_source_mute(index: u32, muted: bool, input_box: Arc<SourceBox>) -> bool { |  | ||||||
|     gio::spawn_blocking(move || { |  | ||||||
|         let conn = Connection::new_session().unwrap(); |  | ||||||
|         let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|         let res: Result<(), Error> = proxy.method_call(AUDIO, "SetSourceMute", (index, muted)); |  | ||||||
|         if res.is_err() { |  | ||||||
|             show_error::<SourceBox>(input_box.clone(), "Failed to mute source"); |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn set_default_source(name: Arc<String>, input_box: Arc<SourceBox>) -> Option<Source> { |  | ||||||
|     let conn = Connection::new_session().unwrap(); |  | ||||||
|     let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|     let res: Result<(Source,), Error> = |  | ||||||
|         proxy.method_call(AUDIO, "SetDefaultSource", (name.as_str(),)); |  | ||||||
|     if res.is_err() { |  | ||||||
|         show_error::<SourceBox>(input_box.clone(), "Failed to set default source"); |  | ||||||
|         return None; |  | ||||||
|     } |  | ||||||
|     Some(res.unwrap().0) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -8,6 +8,9 @@ use std::time::SystemTime; | ||||||
| use gtk::subclass::prelude::*; | use gtk::subclass::prelude::*; | ||||||
| use gtk::{Button, CheckButton, CompositeTemplate, Label, Scale}; | use gtk::{Button, CheckButton, CompositeTemplate, Label, Scale}; | ||||||
| 
 | 
 | ||||||
|  | use crate::components::audio::audio_entry::{AudioIcons, DBusFunction, TAudioEntryImpl}; | ||||||
|  | 
 | ||||||
|  | use super::source_const::{ICONS, SETDEFAULT, SETMUTE, SETVOLUME}; | ||||||
| use super::source_entry; | use super::source_entry; | ||||||
| 
 | 
 | ||||||
| #[derive(Default, CompositeTemplate)] | #[derive(Default, CompositeTemplate)] | ||||||
|  | @ -48,3 +51,49 @@ impl PreferencesGroupImpl for SourceEntry {} | ||||||
| impl ObjectImpl for SourceEntry {} | impl ObjectImpl for SourceEntry {} | ||||||
| 
 | 
 | ||||||
| impl WidgetImpl for SourceEntry {} | impl WidgetImpl for SourceEntry {} | ||||||
|  | 
 | ||||||
|  | impl TAudioEntryImpl<Source> for SourceEntry { | ||||||
|  |     fn name(&self) -> &TemplateChild<ActionRow> { | ||||||
|  |         &self.reset_source_name | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn selected_audio_object(&self) -> &TemplateChild<CheckButton> { | ||||||
|  |         &self.reset_selected_source | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn mute(&self) -> &TemplateChild<Button> { | ||||||
|  |         &self.reset_source_mute | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_slider(&self) -> &TemplateChild<Scale> { | ||||||
|  |         &self.reset_volume_slider | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_percentage(&self) -> &TemplateChild<Label> { | ||||||
|  |         &self.reset_volume_percentage | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object(&self) -> Arc<RefCell<Source>> { | ||||||
|  |         self.source.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>> { | ||||||
|  |         &self.volume_time_stamp | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn set_volume_fn(&self) -> &'static DBusFunction { | ||||||
|  |         &SETVOLUME | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn set_audio_object_fn(&self) -> &'static DBusFunction { | ||||||
|  |         &SETDEFAULT | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn set_mute_fn(&self) -> &'static DBusFunction { | ||||||
|  |         &SETMUTE | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn icons(&self) -> &AudioIcons { | ||||||
|  |         &ICONS | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,2 +1,8 @@ | ||||||
|  | mod audio_box_handlers; | ||||||
|  | mod audio_box_utils; | ||||||
|  | mod audio_const; | ||||||
|  | pub mod audio_entry; | ||||||
|  | pub mod audio_functions; | ||||||
|  | mod audio_utils; | ||||||
| pub mod input; | pub mod input; | ||||||
| pub mod output; | pub mod output; | ||||||
|  |  | ||||||
|  | @ -1,25 +1,15 @@ | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| use std::time::{Duration, SystemTime}; |  | ||||||
| 
 | 
 | ||||||
| use crate::components::base::error_impl::show_error; | use crate::components::audio::audio_entry::TAudioStream; | ||||||
| use crate::components::utils::{ | use crate::components::audio::audio_functions::new_stream_entry; | ||||||
|     create_dropdown_label_factory, set_combo_row_ellipsis, AUDIO, BASE, DBUS_PATH, |  | ||||||
| }; |  | ||||||
| use adw::glib::Object; |  | ||||||
| use adw::prelude::{ButtonExt, ComboRowExt, PreferencesRowExt, RangeExt}; |  | ||||||
| use dbus::blocking::Connection; |  | ||||||
| use dbus::Error; |  | ||||||
| use glib::prelude::Cast; |  | ||||||
| use glib::subclass::types::ObjectSubclassIsExt; | use glib::subclass::types::ObjectSubclassIsExt; | ||||||
| use glib::{clone, Propagation}; | use re_set_lib::audio::audio_structures::{InputStream, Sink}; | ||||||
| use gtk::{gio, StringObject}; |  | ||||||
| use re_set_lib::audio::audio_structures::InputStream; |  | ||||||
| 
 | 
 | ||||||
| use super::input_stream_entry_impl; |  | ||||||
| use super::sink_box::SinkBox; | use super::sink_box::SinkBox; | ||||||
|  | use super::sink_entry::SinkEntry; | ||||||
| 
 | 
 | ||||||
| glib::wrapper! { | glib::wrapper! { | ||||||
|     pub struct InputStreamEntry(ObjectSubclass<input_stream_entry_impl::InputStreamEntry>) |     pub struct InputStreamEntry(ObjectSubclass<super::input_stream_entry_impl::InputStreamEntry>) | ||||||
|     @extends adw::PreferencesGroup, gtk::Widget, |     @extends adw::PreferencesGroup, gtk::Widget, | ||||||
|     @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable; |     @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable; | ||||||
| } | } | ||||||
|  | @ -27,179 +17,23 @@ glib::wrapper! { | ||||||
| unsafe impl Send for InputStreamEntry {} | unsafe impl Send for InputStreamEntry {} | ||||||
| unsafe impl Sync for InputStreamEntry {} | unsafe impl Sync for InputStreamEntry {} | ||||||
| 
 | 
 | ||||||
| impl InputStreamEntry { | impl TAudioStream<super::input_stream_entry_impl::InputStreamEntry> for InputStreamEntry { | ||||||
|     pub fn new(sink_box: Arc<SinkBox>, stream: InputStream) -> Self { |     fn entry_imp(&self) -> &super::input_stream_entry_impl::InputStreamEntry { | ||||||
|         let obj: Self = Object::builder().build(); |         self.imp() | ||||||
|         // TODO use event callback for progress bar -> this is the "im speaking" indicator
 |  | ||||||
|         let output_box_mute_ref = sink_box.clone(); |  | ||||||
|         let output_box_volume_ref = sink_box.clone(); |  | ||||||
|         let output_box_sink_ref = sink_box.clone(); |  | ||||||
|         { |  | ||||||
|             let index = stream.sink_index; |  | ||||||
|             let box_imp = sink_box.imp(); |  | ||||||
|             let imp = obj.imp(); |  | ||||||
|             if stream.muted { |  | ||||||
|                 imp.reset_sink_mute |  | ||||||
|                     .set_icon_name("audio-volume-muted-symbolic"); |  | ||||||
|             } else { |  | ||||||
|                 imp.reset_sink_mute |  | ||||||
|                     .set_icon_name("audio-volume-high-symbolic"); |  | ||||||
|             } |  | ||||||
|             let name = stream.application_name.clone() + ": " + stream.name.as_str(); |  | ||||||
|             imp.reset_sink_selection.set_title(name.as_str()); |  | ||||||
|             imp.reset_sink_selection |  | ||||||
|                 .set_factory(Some(&create_dropdown_label_factory())); |  | ||||||
|             set_combo_row_ellipsis(imp.reset_sink_selection.get()); |  | ||||||
|             let volume = stream.volume.first().unwrap_or(&0_u32); |  | ||||||
|             let fraction = (*volume as f64 / 655.36).round(); |  | ||||||
|             let percentage = (fraction).to_string() + "%"; |  | ||||||
|             imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|             imp.reset_volume_slider.set_value(*volume as f64); |  | ||||||
|             imp.stream.replace(stream); |  | ||||||
|             { |  | ||||||
|                 let sink = box_imp.reset_default_sink.borrow(); |  | ||||||
|                 imp.associated_sink.replace((sink.index, sink.name.clone())); |  | ||||||
|             } |  | ||||||
|             imp.reset_volume_slider.connect_change_value( |  | ||||||
|                 clone!(@weak imp => @default-return Propagation::Stop, move |_, _, value| { |  | ||||||
|                     let fraction = (value / 655.36).round(); |  | ||||||
|                     let percentage = (fraction).to_string() + "%"; |  | ||||||
|                     imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|                     let mut stream = imp.stream.try_borrow(); |  | ||||||
|                     while stream.is_err() { |  | ||||||
|                         stream = imp.stream.try_borrow(); |  | ||||||
|                     } |  | ||||||
|                     let stream = stream.unwrap(); |  | ||||||
|                     let index = stream.index; |  | ||||||
|                     let channels = stream.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()); |  | ||||||
|                     } |  | ||||||
|                     set_inputstream_volume(value, index, channels, output_box_volume_ref.clone()); |  | ||||||
|                     Propagation::Proceed |  | ||||||
|                 }), |  | ||||||
|             ); |  | ||||||
|             { |  | ||||||
|                 let list = box_imp.reset_model_list.read().unwrap(); |  | ||||||
|                 imp.reset_sink_selection.set_model(Some(&*list)); |  | ||||||
|                 let sink_list = box_imp.reset_sink_list.read().unwrap(); |  | ||||||
|                 let name = sink_list.get(&index); |  | ||||||
|                 let index = box_imp.reset_model_index.read().unwrap(); |  | ||||||
|                 let model_list = box_imp.reset_model_list.read().unwrap(); |  | ||||||
|                 if let Some(name) = name { |  | ||||||
|                     for entry in 0..*index { |  | ||||||
|                         if model_list.string(entry) == Some(name.2.clone().into()) { |  | ||||||
|                             imp.reset_sink_selection.set_selected(entry); |  | ||||||
|                             break; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     let mut name = box_imp.reset_default_sink.try_borrow(); |  | ||||||
|                     while name.is_err() { |  | ||||||
|                         name = box_imp.reset_default_sink.try_borrow(); |  | ||||||
|                     } |  | ||||||
|                     let name = name.unwrap(); |  | ||||||
|                     for entry in 0..*index { |  | ||||||
|                         if model_list.string(entry) == Some(name.alias.clone().into()) { |  | ||||||
|                             imp.reset_sink_selection.set_selected(entry); |  | ||||||
|                             break; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             imp.reset_sink_selection.connect_selected_notify( |  | ||||||
|                 clone!(@weak imp, @weak box_imp => move |dropdown| { |  | ||||||
|                     let selected = dropdown.selected_item(); |  | ||||||
|                     if selected.is_none() { |  | ||||||
|                         return; |  | ||||||
|                     } |  | ||||||
|                     let selected = selected.unwrap(); |  | ||||||
|                     let selected = selected.downcast_ref::<StringObject>().unwrap(); |  | ||||||
|                     let selected = selected.string().to_string(); |  | ||||||
|                     let sink = box_imp.reset_sink_map.read().unwrap(); |  | ||||||
|                     // if sink.is_err() {
 |  | ||||||
|                     //     return;
 |  | ||||||
|                     // }
 |  | ||||||
|                     // let sink = sink.unwrap();
 |  | ||||||
|                     let sink = sink.get(&selected); |  | ||||||
|                     if sink.is_none() { |  | ||||||
|                         return; |  | ||||||
|                     } |  | ||||||
|                     let mut stream = imp.stream.try_borrow(); |  | ||||||
|                     while stream.is_err() { |  | ||||||
|                         stream = imp.stream.try_borrow(); |  | ||||||
|                     } |  | ||||||
|                     let stream = stream.unwrap(); |  | ||||||
|                 let sink = sink.unwrap().0; |  | ||||||
|                     set_sink_of_input_stream(stream.index, sink, output_box_sink_ref.clone()); |  | ||||||
|                 }), |  | ||||||
|             ); |  | ||||||
|             imp.reset_sink_mute |  | ||||||
|                 .connect_clicked(clone!(@weak imp => move |_| { |  | ||||||
|                     let stream = imp.stream.clone(); |  | ||||||
|                     let mut stream = stream.try_borrow_mut(); |  | ||||||
|                     while stream.is_err() { |  | ||||||
|                         stream = imp.stream.try_borrow_mut(); |  | ||||||
|                     } |  | ||||||
|                     let mut stream = stream.unwrap(); |  | ||||||
|                     stream.muted = !stream.muted; |  | ||||||
|                     let muted = stream.muted; |  | ||||||
|                     let index = stream.index; |  | ||||||
|                     if muted { |  | ||||||
|                         imp.reset_sink_mute |  | ||||||
|                            .set_icon_name("audio-volume-muted-symbolic"); |  | ||||||
|                     } else { |  | ||||||
|                         imp.reset_sink_mute |  | ||||||
|                            .set_icon_name("audio-volume-high-symbolic"); |  | ||||||
|                     } |  | ||||||
|                     toggle_input_stream_mute(index, muted, output_box_mute_ref.clone()); |  | ||||||
|                 })); |  | ||||||
|         } |  | ||||||
|         obj |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn set_inputstream_volume(value: f64, index: u32, channels: u16, output_box: Arc<SinkBox>) -> bool { | impl InputStreamEntry { | ||||||
|     gio::spawn_blocking(move || { |     pub fn new(source_box: Arc<SinkBox>, stream: InputStream) -> Arc<Self> { | ||||||
|         let conn = Connection::new_session().unwrap(); |         new_stream_entry::< | ||||||
|         let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |             Sink, | ||||||
|         let res: Result<(), Error> = proxy.method_call( |             InputStream, | ||||||
|             AUDIO, |             SinkEntry, | ||||||
|             "SetInputStreamVolume", |             super::sink_entry_impl::SinkEntry, | ||||||
|             (index, channels, value as u32), |             InputStreamEntry, | ||||||
|         ); |             super::input_stream_entry_impl::InputStreamEntry, | ||||||
|         if res.is_err() { |             SinkBox, | ||||||
|             show_error::<SinkBox>(output_box.clone(), "Failed to set input stream volume"); |             super::sink_box_impl::SinkBox, | ||||||
|         } |         >(source_box, stream) | ||||||
|     }); |     } | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn toggle_input_stream_mute(index: u32, muted: bool, output_box: Arc<SinkBox>) -> bool { |  | ||||||
|     gio::spawn_blocking(move || { |  | ||||||
|         let conn = Connection::new_session().unwrap(); |  | ||||||
|         let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|         let res: Result<(), Error> = proxy.method_call(AUDIO, "SetInputStreamMute", (index, muted)); |  | ||||||
|         if res.is_err() { |  | ||||||
|             show_error::<SinkBox>(output_box.clone(), "Failed to mute input stream"); |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn set_sink_of_input_stream(stream: u32, sink: u32, output_box: Arc<SinkBox>) -> bool { |  | ||||||
|     gio::spawn_blocking(move || { |  | ||||||
|         let conn = Connection::new_session().unwrap(); |  | ||||||
|         let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|         let res: Result<(), Error> = |  | ||||||
|             proxy.method_call(AUDIO, "SetSinkOfInputStream", (stream, sink)); |  | ||||||
|         if res.is_err() { |  | ||||||
|             show_error::<SinkBox>(output_box.clone(), "Failed to set sink of input stream"); |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| use adw::subclass::prelude::PreferencesGroupImpl; | use adw::subclass::prelude::PreferencesGroupImpl; | ||||||
| use adw::{ComboRow, PreferencesGroup}; | use adw::{ComboRow, PreferencesGroup}; | ||||||
| use re_set_lib::audio::audio_structures::InputStream; | use re_set_lib::audio::audio_structures::{InputStream, Sink}; | ||||||
| use std::cell::RefCell; | use std::cell::RefCell; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| use std::time::SystemTime; | use std::time::SystemTime; | ||||||
|  | @ -8,7 +8,10 @@ use std::time::SystemTime; | ||||||
| use gtk::subclass::prelude::*; | use gtk::subclass::prelude::*; | ||||||
| use gtk::{Button, CompositeTemplate, Label, Scale}; | use gtk::{Button, CompositeTemplate, Label, Scale}; | ||||||
| 
 | 
 | ||||||
|  | use crate::components::audio::audio_entry::{AudioIcons, TAudioStreamImpl}; | ||||||
|  | 
 | ||||||
| use super::input_stream_entry; | use super::input_stream_entry; | ||||||
|  | use super::sink_const::{ICONS, SETSTREAMMUTE, SETSTREAMOBJECT, SETSTREAMVOLUME}; | ||||||
| 
 | 
 | ||||||
| #[derive(Default, CompositeTemplate)] | #[derive(Default, CompositeTemplate)] | ||||||
| #[template(resource = "/org/Xetibo/ReSet/resetInputStreamEntry.ui")] | #[template(resource = "/org/Xetibo/ReSet/resetInputStreamEntry.ui")] | ||||||
|  | @ -47,3 +50,49 @@ impl PreferencesGroupImpl for InputStreamEntry {} | ||||||
| impl ObjectImpl for InputStreamEntry {} | impl ObjectImpl for InputStreamEntry {} | ||||||
| 
 | 
 | ||||||
| impl WidgetImpl for InputStreamEntry {} | impl WidgetImpl for InputStreamEntry {} | ||||||
|  | 
 | ||||||
|  | impl TAudioStreamImpl<Sink, InputStream> for InputStreamEntry { | ||||||
|  |     fn audio_object_selection(&self) -> &TemplateChild<ComboRow> { | ||||||
|  |         &self.reset_sink_selection | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object_mute(&self) -> &TemplateChild<Button> { | ||||||
|  |         &self.reset_sink_mute | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_slider(&self) -> &TemplateChild<Scale> { | ||||||
|  |         &self.reset_volume_slider | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_percentage(&self) -> &TemplateChild<Label> { | ||||||
|  |         &self.reset_volume_percentage | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn stream_object(&self) -> Arc<RefCell<InputStream>> { | ||||||
|  |         self.stream.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn associated_audio_object(&self) -> Arc<RefCell<(u32, String)>> { | ||||||
|  |         self.associated_sink.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>> { | ||||||
|  |         &self.volume_time_stamp | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn set_volume_fn(&self) -> &'static crate::components::audio::audio_entry::DBusFunction { | ||||||
|  |         &SETSTREAMVOLUME | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn set_audio_object_fn(&self) -> &'static crate::components::audio::audio_entry::DBusFunction { | ||||||
|  |         &SETSTREAMOBJECT | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn set_mute_fn(&self) -> &'static crate::components::audio::audio_entry::DBusFunction { | ||||||
|  |         &SETSTREAMMUTE | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn icons(&self) -> &AudioIcons { | ||||||
|  |         &ICONS | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,8 +1,7 @@ | ||||||
| pub mod input_stream_entry; | pub mod input_stream_entry; | ||||||
| pub mod input_stream_entry_impl; | pub mod input_stream_entry_impl; | ||||||
| pub mod sink_box; | pub mod sink_box; | ||||||
| mod sink_box_handlers; |  | ||||||
| pub mod sink_box_impl; | pub mod sink_box_impl; | ||||||
| mod sink_box_utils; | mod sink_const; | ||||||
| pub mod sink_entry; | pub mod sink_entry; | ||||||
| pub mod sink_entry_impl; | pub mod sink_entry_impl; | ||||||
|  |  | ||||||
|  | @ -1,3 +1,5 @@ | ||||||
|  | use re_set_lib::audio::audio_structures::InputStream; | ||||||
|  | use re_set_lib::audio::audio_structures::Sink; | ||||||
| use re_set_lib::signals::InputStreamAdded; | use re_set_lib::signals::InputStreamAdded; | ||||||
| use re_set_lib::signals::InputStreamChanged; | use re_set_lib::signals::InputStreamChanged; | ||||||
| use re_set_lib::signals::InputStreamRemoved; | use re_set_lib::signals::InputStreamRemoved; | ||||||
|  | @ -7,33 +9,25 @@ use re_set_lib::signals::SinkRemoved; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| 
 | 
 | ||||||
| use adw::glib::Object; | use adw::glib::Object; | ||||||
| use adw::prelude::ComboRowExt; |  | ||||||
| use adw::prelude::ListBoxRowExt; |  | ||||||
| use dbus::blocking::Connection; | use dbus::blocking::Connection; | ||||||
| use dbus::message::SignalArgs; |  | ||||||
| use dbus::Path; |  | ||||||
| use glib::subclass::prelude::ObjectSubclassIsExt; | use glib::subclass::prelude::ObjectSubclassIsExt; | ||||||
| use glib::Variant; |  | ||||||
| use gtk::gio; |  | ||||||
| use gtk::prelude::ActionableExt; |  | ||||||
| 
 | 
 | ||||||
|  | use crate::components::audio::audio_box_handlers::populate_audio_objects; | ||||||
|  | use crate::components::audio::audio_box_utils::setup_audio_box_callbacks; | ||||||
|  | use crate::components::audio::audio_box_utils::start_audio_box_listener; | ||||||
|  | use crate::components::audio::audio_entry::TAudioBox; | ||||||
| use crate::components::base::error_impl::ReSetErrorImpl; | use crate::components::base::error_impl::ReSetErrorImpl; | ||||||
| use crate::components::utils::BASE; |  | ||||||
| use crate::components::utils::DBUS_PATH; |  | ||||||
| use crate::components::utils::{create_dropdown_label_factory, set_combo_row_ellipsis}; |  | ||||||
| 
 | 
 | ||||||
| use super::sink_box_handlers::input_stream_added_handler; | use super::input_stream_entry::InputStreamEntry; | ||||||
| use super::sink_box_handlers::input_stream_changed_handler; |  | ||||||
| use super::sink_box_handlers::input_stream_removed_handler; |  | ||||||
| use super::sink_box_handlers::sink_added_handler; |  | ||||||
| use super::sink_box_handlers::sink_changed_handler; |  | ||||||
| use super::sink_box_handlers::sink_removed_handler; |  | ||||||
| use super::sink_box_impl; | use super::sink_box_impl; | ||||||
| use super::sink_box_utils::get_default_sink; | use super::sink_const::GETDEFAULT; | ||||||
| use super::sink_box_utils::get_sinks; | use super::sink_const::GETDEFAULTNAME; | ||||||
| use super::sink_box_utils::populate_cards; | use super::sink_const::GETOBJECTS; | ||||||
| use super::sink_box_utils::populate_inputstreams; | use super::sink_const::GETSTREAMS; | ||||||
| use super::sink_box_utils::populate_sink_information; | use super::sink_const::SETDEFAULT; | ||||||
|  | use super::sink_const::SETMUTE; | ||||||
|  | use super::sink_const::SETVOLUME; | ||||||
|  | use super::sink_entry::SinkEntry; | ||||||
| 
 | 
 | ||||||
| glib::wrapper! { | glib::wrapper! { | ||||||
|     pub struct SinkBox(ObjectSubclass<sink_box_impl::SinkBox>) |     pub struct SinkBox(ObjectSubclass<sink_box_impl::SinkBox>) | ||||||
|  | @ -52,9 +46,25 @@ impl ReSetErrorImpl for SinkBox { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl TAudioBox<super::sink_box_impl::SinkBox> for SinkBox { | ||||||
|  |     fn box_imp(&self) -> &super::sink_box_impl::SinkBox { | ||||||
|  |         self.imp() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl SinkBox { | impl SinkBox { | ||||||
|     pub fn new() -> Self { |     pub fn new() -> Self { | ||||||
|         let obj: Self = Object::builder().build(); |         let mut obj: Self = Object::builder().build(); | ||||||
|  |         setup_audio_box_callbacks::< | ||||||
|  |             Sink, | ||||||
|  |             InputStream, | ||||||
|  |             SinkEntry, | ||||||
|  |             super::sink_entry_impl::SinkEntry, | ||||||
|  |             InputStreamEntry, | ||||||
|  |             super::input_stream_entry_impl::InputStreamEntry, | ||||||
|  |             SinkBox, | ||||||
|  |             super::sink_box_impl::SinkBox, | ||||||
|  |         >(&mut obj); | ||||||
|         { |         { | ||||||
|             let imp = obj.imp(); |             let imp = obj.imp(); | ||||||
|             let mut model_index = imp.reset_model_index.write().unwrap(); |             let mut model_index = imp.reset_model_index.write().unwrap(); | ||||||
|  | @ -62,39 +72,6 @@ impl SinkBox { | ||||||
|         } |         } | ||||||
|         obj |         obj | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     pub fn setup_callbacks(&self) { |  | ||||||
|         let self_imp = self.imp(); |  | ||||||
|         self_imp.reset_sinks_row.set_activatable(true); |  | ||||||
|         self_imp |  | ||||||
|             .reset_sinks_row |  | ||||||
|             .set_action_name(Some("navigation.push")); |  | ||||||
|         self_imp |  | ||||||
|             .reset_sinks_row |  | ||||||
|             .set_action_target_value(Some(&Variant::from("outputDevices"))); |  | ||||||
|         self_imp.reset_cards_row.set_activatable(true); |  | ||||||
|         self_imp |  | ||||||
|             .reset_cards_row |  | ||||||
|             .set_action_name(Some("navigation.push")); |  | ||||||
|         self_imp |  | ||||||
|             .reset_cards_row |  | ||||||
|             .set_action_target_value(Some(&Variant::from("profileConfiguration"))); |  | ||||||
| 
 |  | ||||||
|         self_imp.reset_input_stream_button.set_activatable(true); |  | ||||||
|         self_imp |  | ||||||
|             .reset_input_stream_button |  | ||||||
|             .set_action_name(Some("navigation.pop")); |  | ||||||
| 
 |  | ||||||
|         self_imp.reset_input_cards_back_button.set_activatable(true); |  | ||||||
|         self_imp |  | ||||||
|             .reset_input_cards_back_button |  | ||||||
|             .set_action_name(Some("navigation.pop")); |  | ||||||
| 
 |  | ||||||
|         self_imp |  | ||||||
|             .reset_sink_dropdown |  | ||||||
|             .set_factory(Some(&create_dropdown_label_factory())); |  | ||||||
|         set_combo_row_ellipsis(self_imp.reset_sink_dropdown.get()); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Default for SinkBox { | impl Default for SinkBox { | ||||||
|  | @ -104,100 +81,41 @@ impl Default for SinkBox { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn populate_sinks(sink_box: Arc<SinkBox>) { | pub fn populate_sinks(sink_box: Arc<SinkBox>) { | ||||||
|     gio::spawn_blocking(move || { |     populate_audio_objects::< | ||||||
|         let sinks = get_sinks(sink_box.clone()); |         Sink, | ||||||
|         { |         InputStream, | ||||||
|             let sink_box_imp = sink_box.imp(); |         SinkEntry, | ||||||
|             let list = sink_box_imp.reset_model_list.write().unwrap(); |         super::sink_entry_impl::SinkEntry, | ||||||
|             let mut map = sink_box_imp.reset_sink_map.write().unwrap(); |         InputStreamEntry, | ||||||
|             let mut model_index = sink_box_imp.reset_model_index.write().unwrap(); |         super::input_stream_entry_impl::InputStreamEntry, | ||||||
|             sink_box_imp |         SinkBox, | ||||||
|                 .reset_default_sink |         super::sink_box_impl::SinkBox, | ||||||
|                 .replace(get_default_sink(sink_box.clone())); |     >( | ||||||
|             for sink in sinks.iter() { |         sink_box, | ||||||
|                 list.append(&sink.alias); |         &GETOBJECTS, | ||||||
|                 map.insert(sink.alias.clone(), (sink.index, sink.name.clone())); |         &GETDEFAULT, | ||||||
|                 *model_index += 1; |         &SETDEFAULT, | ||||||
|             } |         &GETSTREAMS, | ||||||
|         } |         &SETVOLUME, | ||||||
|         populate_inputstreams(sink_box.clone()); |         &SETMUTE, | ||||||
|         populate_cards(sink_box.clone()); |     ); | ||||||
|         populate_sink_information(sink_box, sinks); |  | ||||||
|     }); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn start_sink_box_listener(conn: Connection, sink_box: Arc<SinkBox>) -> Connection { | pub fn start_sink_box_listener(conn: Connection, sink_box: Arc<SinkBox>) -> Connection { | ||||||
|     let sink_added = |     start_audio_box_listener::< | ||||||
|         SinkAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone(); |         Sink, | ||||||
|     let sink_removed = |         InputStream, | ||||||
|         SinkRemoved::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone(); |         SinkEntry, | ||||||
|     let sink_changed = |         super::sink_entry_impl::SinkEntry, | ||||||
|         SinkChanged::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone(); |         InputStreamEntry, | ||||||
|     let input_stream_added = |         super::input_stream_entry_impl::InputStreamEntry, | ||||||
|         InputStreamAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))) |         SinkBox, | ||||||
|             .static_clone(); |         super::sink_box_impl::SinkBox, | ||||||
|     let input_stream_removed = |         SinkAdded, | ||||||
|         InputStreamRemoved::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))) |         SinkChanged, | ||||||
|             .static_clone(); |         SinkRemoved, | ||||||
|     let input_stream_changed = |         InputStreamAdded, | ||||||
|         InputStreamChanged::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))) |         InputStreamChanged, | ||||||
|             .static_clone(); |         InputStreamRemoved, | ||||||
| 
 |     >(conn, sink_box, &GETDEFAULTNAME) | ||||||
|     let sink_added_box = sink_box.clone(); |  | ||||||
|     let sink_removed_box = sink_box.clone(); |  | ||||||
|     let sink_changed_box = sink_box.clone(); |  | ||||||
|     let input_stream_added_box = sink_box.clone(); |  | ||||||
|     let input_stream_removed_box = sink_box.clone(); |  | ||||||
|     let input_stream_changed_box = sink_box.clone(); |  | ||||||
| 
 |  | ||||||
|     let res = conn.add_match(sink_added, move |ir: SinkAdded, _, _| { |  | ||||||
|         sink_added_handler(sink_added_box.clone(), ir) |  | ||||||
|     }); |  | ||||||
|     if res.is_err() { |  | ||||||
|         // TODO: handle this with the log/error macro
 |  | ||||||
|         println!("fail on sink add event"); |  | ||||||
|         return conn; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let res = conn.add_match(sink_removed, move |ir: SinkRemoved, _, _| { |  | ||||||
|         sink_removed_handler(sink_removed_box.clone(), ir) |  | ||||||
|     }); |  | ||||||
|     if res.is_err() { |  | ||||||
|         println!("fail on sink remove event"); |  | ||||||
|         return conn; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let res = conn.add_match(sink_changed, move |ir: SinkChanged, _, _| { |  | ||||||
|         sink_changed_handler(sink_changed_box.clone(), ir) |  | ||||||
|     }); |  | ||||||
|     if res.is_err() { |  | ||||||
|         println!("fail on sink change event"); |  | ||||||
|         return conn; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let res = conn.add_match(input_stream_added, move |ir: InputStreamAdded, _, _| { |  | ||||||
|         input_stream_added_handler(input_stream_added_box.clone(), ir) |  | ||||||
|     }); |  | ||||||
|     if res.is_err() { |  | ||||||
|         println!("fail on input stream add event"); |  | ||||||
|         return conn; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let res = conn.add_match(input_stream_removed, move |ir: InputStreamRemoved, _, _| { |  | ||||||
|         input_stream_removed_handler(input_stream_removed_box.clone(), ir) |  | ||||||
|     }); |  | ||||||
|     if res.is_err() { |  | ||||||
|         println!("fail on input stream remove event"); |  | ||||||
|         return conn; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let res = conn.add_match(input_stream_changed, move |ir: InputStreamChanged, _, _| { |  | ||||||
|         input_stream_changed_handler(input_stream_changed_box.clone(), ir) |  | ||||||
|     }); |  | ||||||
|     if res.is_err() { |  | ||||||
|         println!("fail on input stream change event"); |  | ||||||
|         return conn; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     conn |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,308 +0,0 @@ | ||||||
| use std::{ |  | ||||||
|     sync::Arc, |  | ||||||
|     time::{Duration, SystemTime}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| use adw::{ |  | ||||||
|     prelude::{ComboRowExt, PreferencesRowExt}, |  | ||||||
|     ComboRow, |  | ||||||
| }; |  | ||||||
| use glib::prelude::Cast; |  | ||||||
| use glib::{subclass::types::ObjectSubclassIsExt, Propagation}; |  | ||||||
| use gtk::{ |  | ||||||
|     gio, |  | ||||||
|     prelude::{BoxExt, ButtonExt, CheckButtonExt, ListBoxRowExt, RangeExt}, |  | ||||||
|     StringObject, |  | ||||||
| }; |  | ||||||
| use re_set_lib::signals::{ |  | ||||||
|     InputStreamAdded, InputStreamChanged, InputStreamRemoved, SinkAdded, SinkChanged, SinkRemoved, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| use crate::components::base::list_entry::ListEntry; |  | ||||||
| 
 |  | ||||||
| use super::{ |  | ||||||
|     input_stream_entry::InputStreamEntry, |  | ||||||
|     sink_box::SinkBox, |  | ||||||
|     sink_box_utils::{get_default_sink_name, refresh_default_sink}, |  | ||||||
|     sink_entry::{set_default_sink, set_sink_volume, toggle_sink_mute, SinkEntry}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| pub fn drop_down_handler(sink_box: Arc<SinkBox>, dropdown: &ComboRow) { |  | ||||||
|     let sink_box_ref = sink_box.clone(); |  | ||||||
|     let sink_box_imp = sink_box.imp(); |  | ||||||
|     let selected = dropdown.selected_item(); |  | ||||||
|     if selected.is_none() { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|     let selected = selected.unwrap(); |  | ||||||
|     let selected = selected.downcast_ref::<StringObject>().unwrap(); |  | ||||||
|     let selected = selected.string().to_string(); |  | ||||||
| 
 |  | ||||||
|     let sink = sink_box_imp.reset_sink_map.read().unwrap(); |  | ||||||
|     let sink = sink.get(&selected); |  | ||||||
|     if sink.is_none() { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|     let new_sink_name = Arc::new(sink.unwrap().1.clone()); |  | ||||||
|     gio::spawn_blocking(move || { |  | ||||||
|         let result = set_default_sink(new_sink_name, sink_box_ref.clone()); |  | ||||||
|         if result.is_none() { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         let new_sink = result.unwrap(); |  | ||||||
|         refresh_default_sink(new_sink, sink_box_ref, false); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn volume_slider_handler(sink_box: Arc<SinkBox>, value: f64) -> glib::Propagation { |  | ||||||
|     let imp = sink_box.imp(); |  | ||||||
|     let fraction = (value / 655.36).round(); |  | ||||||
|     let percentage = (fraction).to_string() + "%"; |  | ||||||
|     imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|     let sink = imp.reset_default_sink.borrow(); |  | ||||||
|     let index = sink.index; |  | ||||||
|     let channels = sink.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()); |  | ||||||
|     } |  | ||||||
|     set_sink_volume(value, index, channels, sink_box.clone()); |  | ||||||
|     Propagation::Proceed |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn mute_handler(sink_box: Arc<SinkBox>) { |  | ||||||
|     let imp = sink_box.imp(); |  | ||||||
|     let mut stream = imp.reset_default_sink.borrow_mut(); |  | ||||||
|     stream.muted = !stream.muted; |  | ||||||
|     if stream.muted { |  | ||||||
|         imp.reset_sink_mute |  | ||||||
|             .set_icon_name("audio-volume-muted-symbolic"); |  | ||||||
|     } else { |  | ||||||
|         imp.reset_sink_mute |  | ||||||
|             .set_icon_name("audio-volume-high-symbolic"); |  | ||||||
|     } |  | ||||||
|     toggle_sink_mute(stream.index, stream.muted, sink_box.clone()); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn sink_added_handler(sink_box: Arc<SinkBox>, ir: SinkAdded) -> bool { |  | ||||||
|     glib::spawn_future(async move { |  | ||||||
|         glib::idle_add_once(move || { |  | ||||||
|             let sink_box_imp = sink_box.imp(); |  | ||||||
|             let sink_index = ir.sink.index; |  | ||||||
|             let alias = ir.sink.alias.clone(); |  | ||||||
|             let name = ir.sink.name.clone(); |  | ||||||
|             let mut is_default = false; |  | ||||||
| 
 |  | ||||||
|             if sink_box_imp.reset_default_sink.borrow().name == ir.sink.name { |  | ||||||
|                 is_default = true; |  | ||||||
|             } |  | ||||||
|             let sink_entry = Arc::new(SinkEntry::new( |  | ||||||
|                 is_default, |  | ||||||
|                 sink_box_imp.reset_default_check_button.clone(), |  | ||||||
|                 ir.sink, |  | ||||||
|                 sink_box.clone(), |  | ||||||
|             )); |  | ||||||
|             let sink_clone = sink_entry.clone(); |  | ||||||
|             let entry = Arc::new(ListEntry::new(&*sink_entry)); |  | ||||||
|             entry.set_activatable(false); |  | ||||||
|             let mut list = sink_box_imp.reset_sink_list.write().unwrap(); |  | ||||||
|             list.insert(sink_index, (entry.clone(), sink_clone, alias.clone())); |  | ||||||
|             sink_box_imp.reset_sinks.append(&*entry); |  | ||||||
|             let mut map = sink_box_imp.reset_sink_map.write().unwrap(); |  | ||||||
|             let mut index = sink_box_imp.reset_model_index.write().unwrap(); |  | ||||||
|             let model_list = sink_box_imp.reset_model_list.write().unwrap(); |  | ||||||
|             if model_list.string(*index - 1) == Some("Dummy Output".into()) { |  | ||||||
|                 model_list.append(&alias); |  | ||||||
|                 model_list.remove(*index - 1); |  | ||||||
|                 map.insert(alias, (sink_index, name)); |  | ||||||
|                 sink_box_imp.reset_sink_dropdown.set_selected(0); |  | ||||||
|             } else { |  | ||||||
|                 model_list.append(&alias); |  | ||||||
|                 map.insert(alias.clone(), (sink_index, name)); |  | ||||||
|                 if alias == "Dummy Output" { |  | ||||||
|                     sink_box_imp.reset_sink_dropdown.set_selected(0); |  | ||||||
|                 } |  | ||||||
|                 *index += 1; |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn sink_removed_handler(sink_box: Arc<SinkBox>, ir: SinkRemoved) -> bool { |  | ||||||
|     glib::spawn_future(async move { |  | ||||||
|         glib::idle_add_once(move || { |  | ||||||
|             let sink_box_imp = sink_box.imp(); |  | ||||||
| 
 |  | ||||||
|             let entry: Option<(Arc<ListEntry>, Arc<SinkEntry>, String)>; |  | ||||||
|             { |  | ||||||
|                 let mut list = sink_box_imp.reset_sink_list.write().unwrap(); |  | ||||||
|                 entry = list.remove(&ir.index); |  | ||||||
|                 if entry.is_none() { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             sink_box_imp.reset_sinks.remove(&*entry.clone().unwrap().0); |  | ||||||
|             let alias = entry.unwrap().2; |  | ||||||
|             let mut index = sink_box_imp.reset_model_index.write().unwrap(); |  | ||||||
|             let model_list = sink_box_imp.reset_model_list.write().unwrap(); |  | ||||||
| 
 |  | ||||||
|             // add dummy entry when no other devices are available
 |  | ||||||
|             if *index == 1 { |  | ||||||
|                 model_list.append("Dummy Output"); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             let mut map = sink_box_imp.reset_sink_map.write().unwrap(); |  | ||||||
|             map.remove(&alias); |  | ||||||
| 
 |  | ||||||
|             for entry in 0..*index { |  | ||||||
|                 if model_list.string(entry) == Some(alias.clone().into()) { |  | ||||||
|                     model_list.splice(entry, 1, &[]); |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             // dummy enforces a minimum of 1
 |  | ||||||
|             if *index > 1 { |  | ||||||
|                 *index -= 1; |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn sink_changed_handler(sink_box: Arc<SinkBox>, ir: SinkChanged) -> bool { |  | ||||||
|     glib::spawn_future(async move { |  | ||||||
|         glib::idle_add_once(move || { |  | ||||||
|             let default_sink = get_default_sink_name(sink_box.clone()); |  | ||||||
|             let sink_box_imp = sink_box.imp(); |  | ||||||
|             let is_default = ir.sink.name == default_sink; |  | ||||||
|             let volume = ir.sink.volume.first().unwrap_or(&0_u32); |  | ||||||
|             let fraction = (*volume as f64 / 655.36).round(); |  | ||||||
|             let percentage = (fraction).to_string() + "%"; |  | ||||||
| 
 |  | ||||||
|             let list = sink_box_imp.reset_sink_list.read().unwrap(); |  | ||||||
|             let entry = list.get(&ir.sink.index); |  | ||||||
|             if entry.is_none() { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|             let imp = entry.unwrap().1.imp(); |  | ||||||
|             if is_default { |  | ||||||
|                 sink_box_imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|                 sink_box_imp.reset_volume_slider.set_value(*volume as f64); |  | ||||||
|                 sink_box_imp.reset_default_sink.replace(ir.sink.clone()); |  | ||||||
|                 if ir.sink.muted { |  | ||||||
|                     sink_box_imp |  | ||||||
|                         .reset_sink_mute |  | ||||||
|                         .set_icon_name("audio-volume-muted-symbolic"); |  | ||||||
|                 } else { |  | ||||||
|                     sink_box_imp |  | ||||||
|                         .reset_sink_mute |  | ||||||
|                         .set_icon_name("audio-volume-high-symbolic"); |  | ||||||
|                 } |  | ||||||
|                 imp.reset_selected_sink.set_active(true); |  | ||||||
|             } else { |  | ||||||
|                 imp.reset_selected_sink.set_active(false); |  | ||||||
|             } |  | ||||||
|             imp.reset_sink_name |  | ||||||
|                 .set_title(ir.sink.alias.clone().as_str()); |  | ||||||
|             imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|             imp.reset_volume_slider.set_value(*volume as f64); |  | ||||||
|             if ir.sink.muted { |  | ||||||
|                 imp.reset_sink_mute |  | ||||||
|                     .set_icon_name("audio-volume-muted-symbolic"); |  | ||||||
|             } else { |  | ||||||
|                 imp.reset_sink_mute |  | ||||||
|                     .set_icon_name("audio-volume-high-symbolic"); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn input_stream_added_handler(sink_box: Arc<SinkBox>, ir: InputStreamAdded) -> bool { |  | ||||||
|     glib::spawn_future(async move { |  | ||||||
|         glib::idle_add_once(move || { |  | ||||||
|             let sink_box_imp = sink_box.imp(); |  | ||||||
|             let mut list = sink_box_imp.reset_input_stream_list.write().unwrap(); |  | ||||||
|             let index = ir.stream.index; |  | ||||||
|             let input_stream = Arc::new(InputStreamEntry::new(sink_box.clone(), ir.stream)); |  | ||||||
|             let entry = Arc::new(ListEntry::new(&*input_stream)); |  | ||||||
|             entry.set_activatable(false); |  | ||||||
|             list.insert(index, (entry.clone(), input_stream.clone())); |  | ||||||
|             sink_box_imp.reset_input_streams.append(&*entry); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn input_stream_removed_handler(sink_box: Arc<SinkBox>, ir: InputStreamRemoved) -> bool { |  | ||||||
|     glib::spawn_future(async move { |  | ||||||
|         glib::idle_add_once(move || { |  | ||||||
|             let sink_box_imp = sink_box.imp(); |  | ||||||
|             let mut list = sink_box_imp.reset_input_stream_list.write().unwrap(); |  | ||||||
|             let entry = list.remove(&ir.index); |  | ||||||
|             if entry.is_none() { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|             sink_box_imp.reset_input_streams.remove(&*entry.unwrap().0); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn input_stream_changed_handler(sink_box: Arc<SinkBox>, ir: InputStreamChanged) -> bool { |  | ||||||
|     let imp = sink_box.imp(); |  | ||||||
|     let alias: String; |  | ||||||
|     { |  | ||||||
|         let sink_list = imp.reset_sink_list.read().unwrap(); |  | ||||||
|         if let Some(alias_opt) = sink_list.get(&ir.stream.sink_index) { |  | ||||||
|             alias = alias_opt.2.clone(); |  | ||||||
|         } else { |  | ||||||
|             alias = String::from(""); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     let sink_box = sink_box.clone(); |  | ||||||
|     glib::spawn_future(async move { |  | ||||||
|         glib::idle_add_once(move || { |  | ||||||
|             let sink_box = sink_box.clone(); |  | ||||||
|             let sink_box_imp = sink_box.imp(); |  | ||||||
|             let entry: Arc<InputStreamEntry>; |  | ||||||
|             { |  | ||||||
|                 let list = sink_box_imp.reset_input_stream_list.read().unwrap(); |  | ||||||
|                 let entry_opt = list.get(&ir.stream.index); |  | ||||||
|                 if entry_opt.is_none() { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 entry = entry_opt.unwrap().1.clone(); |  | ||||||
|             } |  | ||||||
|             let imp = entry.imp(); |  | ||||||
|             if ir.stream.muted { |  | ||||||
|                 imp.reset_sink_mute |  | ||||||
|                     .set_icon_name("audio-volume-muted-symbolic"); |  | ||||||
|             } else { |  | ||||||
|                 imp.reset_sink_mute |  | ||||||
|                     .set_icon_name("audio-volume-high-symbolic"); |  | ||||||
|             } |  | ||||||
|             let name = ir.stream.application_name.clone() + ": " + ir.stream.name.as_str(); |  | ||||||
|             imp.reset_sink_selection.set_title(name.as_str()); |  | ||||||
|             let volume = ir.stream.volume.first().unwrap_or(&0_u32); |  | ||||||
|             let fraction = (*volume as f64 / 655.36).round(); |  | ||||||
|             let percentage = (fraction).to_string() + "%"; |  | ||||||
|             imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|             imp.reset_volume_slider.set_value(*volume as f64); |  | ||||||
|             let index = sink_box_imp.reset_model_index.read().unwrap(); |  | ||||||
|             let model_list = sink_box_imp.reset_model_list.read().unwrap(); |  | ||||||
|             for entry in 0..*index { |  | ||||||
|                 if model_list.string(entry) == Some(alias.clone().into()) { |  | ||||||
|                     imp.reset_sink_selection.set_selected(entry); |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
|  | @ -5,6 +5,7 @@ use std::collections::HashMap; | ||||||
| use std::sync::{Arc, RwLock}; | use std::sync::{Arc, RwLock}; | ||||||
| use std::time::SystemTime; | use std::time::SystemTime; | ||||||
| 
 | 
 | ||||||
|  | use crate::components::audio::audio_entry::{AudioIcons, TAudioBoxImpl}; | ||||||
| use crate::components::audio::output::input_stream_entry::InputStreamEntry; | use crate::components::audio::output::input_stream_entry::InputStreamEntry; | ||||||
| use crate::components::base::error::ReSetError; | use crate::components::base::error::ReSetError; | ||||||
| use crate::components::base::list_entry::ListEntry; | use crate::components::base::list_entry::ListEntry; | ||||||
|  | @ -13,6 +14,7 @@ use gtk::{prelude::*, Scale}; | ||||||
| use gtk::{Box, Button, CheckButton, CompositeTemplate, Label, StringList}; | use gtk::{Box, Button, CheckButton, CompositeTemplate, Label, StringList}; | ||||||
| 
 | 
 | ||||||
| use super::sink_box; | use super::sink_box; | ||||||
|  | use super::sink_const::ICONS; | ||||||
| use super::sink_entry::SinkEntry; | use super::sink_entry::SinkEntry; | ||||||
| 
 | 
 | ||||||
| type SinkEntryMap = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<SinkEntry>, String)>>>; | type SinkEntryMap = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<SinkEntry>, String)>>>; | ||||||
|  | @ -79,12 +81,7 @@ impl ObjectSubclass for SinkBox { | ||||||
| 
 | 
 | ||||||
| impl BoxImpl for SinkBox {} | impl BoxImpl for SinkBox {} | ||||||
| 
 | 
 | ||||||
| impl ObjectImpl for SinkBox { | impl ObjectImpl for SinkBox {} | ||||||
|     fn constructed(&self) { |  | ||||||
|         let obj = self.obj(); |  | ||||||
|         obj.setup_callbacks(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| impl ListBoxRowImpl for SinkBox {} | impl ListBoxRowImpl for SinkBox {} | ||||||
| 
 | 
 | ||||||
|  | @ -93,3 +90,93 @@ impl WidgetImpl for SinkBox {} | ||||||
| impl WindowImpl for SinkBox {} | impl WindowImpl for SinkBox {} | ||||||
| 
 | 
 | ||||||
| impl ApplicationWindowImpl for SinkBox {} | impl ApplicationWindowImpl for SinkBox {} | ||||||
|  | 
 | ||||||
|  | impl TAudioBoxImpl<Sink, SinkEntry, InputStreamEntry> for SinkBox { | ||||||
|  |     fn audio_object_row(&self) -> &TemplateChild<ActionRow> { | ||||||
|  |         &self.reset_sinks_row | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn cards_row(&self) -> &TemplateChild<ActionRow> { | ||||||
|  |         &self.reset_cards_row | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object_dropdown(&self) -> &TemplateChild<ComboRow> { | ||||||
|  |         &self.reset_sink_dropdown | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object_mute(&self) -> &TemplateChild<Button> { | ||||||
|  |         &self.reset_sink_mute | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_slider(&self) -> &TemplateChild<Scale> { | ||||||
|  |         &self.reset_volume_slider | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_percentage(&self) -> &TemplateChild<Label> { | ||||||
|  |         &self.reset_volume_percentage | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_objects(&self) -> &TemplateChild<gtk::Box> { | ||||||
|  |         &self.reset_sinks | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object_stream_button(&self) -> &TemplateChild<ActionRow> { | ||||||
|  |         &self.reset_input_stream_button | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object_streams(&self) -> &TemplateChild<gtk::Box> { | ||||||
|  |         &self.reset_input_streams | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn cards_button(&self) -> &TemplateChild<ActionRow> { | ||||||
|  |         &self.reset_input_cards_back_button | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn cards(&self) -> &TemplateChild<PreferencesGroup> { | ||||||
|  |         &self.reset_cards | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn error(&self) -> &TemplateChild<ReSetError> { | ||||||
|  |         &self.error | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn default_check_button(&self) -> Arc<CheckButton> { | ||||||
|  |         self.reset_default_check_button.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn default_audio_object(&self) -> Arc<RefCell<Sink>> { | ||||||
|  |         self.reset_default_sink.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object_list( | ||||||
|  |         &self, | ||||||
|  |     ) -> &crate::components::audio::audio_entry::AudioEntryMap<SinkEntry> { | ||||||
|  |         &self.reset_sink_list | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object_stream_list( | ||||||
|  |         &self, | ||||||
|  |     ) -> &crate::components::audio::audio_entry::AudioStreamEntryMap<InputStreamEntry> { | ||||||
|  |         &self.reset_input_stream_list | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn model_list(&self) -> Arc<RwLock<StringList>> { | ||||||
|  |         self.reset_model_list.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn model_index(&self) -> Arc<RwLock<u32>> { | ||||||
|  |         self.reset_model_index.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn source_map(&self) -> &crate::components::audio::audio_entry::AudioMap { | ||||||
|  |         &self.reset_sink_map | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>> { | ||||||
|  |         &self.volume_time_stamp | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn icons(&self) -> &AudioIcons { | ||||||
|  |         &ICONS | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,232 +0,0 @@ | ||||||
| use std::{sync::Arc, time::Duration}; |  | ||||||
| 
 |  | ||||||
| use adw::prelude::{ComboRowExt, PreferencesGroupExt}; |  | ||||||
| use dbus::{blocking::Connection, Error}; |  | ||||||
| use glib::subclass::types::ObjectSubclassIsExt; |  | ||||||
| use gtk::{ |  | ||||||
|     gio, |  | ||||||
|     prelude::{BoxExt, ButtonExt, CheckButtonExt, ListBoxRowExt, RangeExt}, |  | ||||||
| }; |  | ||||||
| use re_set_lib::audio::audio_structures::{Card, InputStream, Sink}; |  | ||||||
| 
 |  | ||||||
| use crate::components::{ |  | ||||||
|     base::{card_entry::CardEntry, error_impl::show_error, list_entry::ListEntry}, |  | ||||||
|     utils::{AUDIO, BASE, DBUS_PATH}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| use super::{ |  | ||||||
|     input_stream_entry::InputStreamEntry, |  | ||||||
|     sink_box::SinkBox, |  | ||||||
|     sink_box_handlers::{drop_down_handler, mute_handler, volume_slider_handler}, |  | ||||||
|     sink_entry::SinkEntry, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| pub fn populate_sink_information(sink_box: Arc<SinkBox>, sinks: Vec<Sink>) { |  | ||||||
|     glib::spawn_future(async move { |  | ||||||
|         glib::idle_add_once(move || { |  | ||||||
|             let sink_box_ref_select = sink_box.clone(); |  | ||||||
|             let sink_box_ref_slider = sink_box.clone(); |  | ||||||
|             let sink_box_ref_mute = sink_box.clone(); |  | ||||||
|             let sink_box_ref = sink_box.clone(); |  | ||||||
|             { |  | ||||||
|                 let sink_box_imp = sink_box_ref.imp(); |  | ||||||
|                 let default_sink = sink_box_imp.reset_default_sink.clone(); |  | ||||||
|                 let sink = default_sink.borrow(); |  | ||||||
| 
 |  | ||||||
|                 if sink.muted { |  | ||||||
|                     sink_box_imp |  | ||||||
|                         .reset_sink_mute |  | ||||||
|                         .set_icon_name("audio-volume-muted-symbolic"); |  | ||||||
|                 } else { |  | ||||||
|                     sink_box_imp |  | ||||||
|                         .reset_sink_mute |  | ||||||
|                         .set_icon_name("audio-volume-high-symbolic"); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 let volume = sink.volume.first().unwrap_or(&0); |  | ||||||
|                 let fraction = (*volume as f64 / 655.36).round(); |  | ||||||
|                 let percentage = (fraction).to_string() + "%"; |  | ||||||
|                 sink_box_imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|                 sink_box_imp.reset_volume_slider.set_value(*volume as f64); |  | ||||||
|                 let mut list = sink_box_imp.reset_sink_list.write().unwrap(); |  | ||||||
|                 for sink in sinks { |  | ||||||
|                     let index = sink.index; |  | ||||||
|                     let alias = sink.alias.clone(); |  | ||||||
|                     let mut is_default = false; |  | ||||||
|                     if sink_box_imp.reset_default_sink.borrow().name == sink.name { |  | ||||||
|                         is_default = true; |  | ||||||
|                     } |  | ||||||
|                     let sink_entry = Arc::new(SinkEntry::new( |  | ||||||
|                         is_default, |  | ||||||
|                         sink_box_imp.reset_default_check_button.clone(), |  | ||||||
|                         sink, |  | ||||||
|                         sink_box.clone(), |  | ||||||
|                     )); |  | ||||||
|                     let sink_clone = sink_entry.clone(); |  | ||||||
|                     let entry = Arc::new(ListEntry::new(&*sink_entry)); |  | ||||||
|                     entry.set_activatable(false); |  | ||||||
|                     list.insert(index, (entry.clone(), sink_clone, alias)); |  | ||||||
|                     sink_box_imp.reset_sinks.append(&*entry); |  | ||||||
|                 } |  | ||||||
|                 let list = sink_box_imp.reset_model_list.read().unwrap(); |  | ||||||
|                 sink_box_imp.reset_sink_dropdown.set_model(Some(&*list)); |  | ||||||
|                 let name = sink_box_imp.reset_default_sink.borrow(); |  | ||||||
| 
 |  | ||||||
|                 let index = sink_box_imp.reset_model_index.read().unwrap(); |  | ||||||
|                 let model_list = sink_box_imp.reset_model_list.read().unwrap(); |  | ||||||
|                 for entry in 0..*index { |  | ||||||
|                     if model_list.string(entry) == Some(name.alias.clone().into()) { |  | ||||||
|                         sink_box_imp.reset_sink_dropdown.set_selected(entry); |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 sink_box_imp |  | ||||||
|                     .reset_sink_dropdown |  | ||||||
|                     .connect_selected_notify(move |dropdown| { |  | ||||||
|                         drop_down_handler(sink_box_ref_select.clone(), dropdown); |  | ||||||
|                     }); |  | ||||||
|             } |  | ||||||
|             sink_box_ref |  | ||||||
|                 .imp() |  | ||||||
|                 .reset_volume_slider |  | ||||||
|                 .connect_change_value(move |_, _, value| { |  | ||||||
|                     volume_slider_handler(sink_box_ref_slider.clone(), value) |  | ||||||
|                 }); |  | ||||||
|             sink_box_ref |  | ||||||
|                 .imp() |  | ||||||
|                 .reset_sink_mute |  | ||||||
|                 .connect_clicked(move |_| { |  | ||||||
|                     mute_handler(sink_box_ref_mute.clone()); |  | ||||||
|                 }); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn refresh_default_sink(new_sink: Sink, sink_box: Arc<SinkBox>, entry: bool) { |  | ||||||
|     let volume = *new_sink.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 = sink_box.imp(); |  | ||||||
|             if !entry { |  | ||||||
|                 let list = imp.reset_sink_list.read().unwrap(); |  | ||||||
|                 let entry = list.get(&new_sink.index); |  | ||||||
|                 if entry.is_none() { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 let entry_imp = entry.unwrap().1.imp(); |  | ||||||
|                 entry_imp.reset_selected_sink.set_active(true); |  | ||||||
|             } else { |  | ||||||
|                 let index = imp.reset_model_index.read().unwrap(); |  | ||||||
|                 let model_list = imp.reset_model_list.read().unwrap(); |  | ||||||
|                 for entry in 0..*index { |  | ||||||
|                     if model_list.string(entry) == Some(new_sink.alias.clone().into()) { |  | ||||||
|                         imp.reset_sink_dropdown.set_selected(entry); |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|             imp.reset_volume_slider.set_value(volume as f64); |  | ||||||
|             if new_sink.muted { |  | ||||||
|                 imp.reset_sink_mute |  | ||||||
|                     .set_icon_name("audio-volume-muted-symbolic"); |  | ||||||
|             } else { |  | ||||||
|                 imp.reset_sink_mute |  | ||||||
|                     .set_icon_name("audio-volume-high-symbolic"); |  | ||||||
|             } |  | ||||||
|             imp.reset_default_sink.replace(new_sink); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn populate_inputstreams(sink_box: Arc<SinkBox>) { |  | ||||||
|     gio::spawn_blocking(move || { |  | ||||||
|         let streams = get_input_streams(sink_box.clone()); |  | ||||||
|         glib::spawn_future(async move { |  | ||||||
|             glib::idle_add_once(move || { |  | ||||||
|                 let sink_box_imp = sink_box.imp(); |  | ||||||
|                 let mut list = sink_box_imp.reset_input_stream_list.write().unwrap(); |  | ||||||
|                 for stream in streams { |  | ||||||
|                     let index = stream.index; |  | ||||||
|                     let input_stream = Arc::new(InputStreamEntry::new(sink_box.clone(), stream)); |  | ||||||
|                     let entry = Arc::new(ListEntry::new(&*input_stream)); |  | ||||||
|                     entry.set_activatable(false); |  | ||||||
|                     list.insert(index, (entry.clone(), input_stream.clone())); |  | ||||||
|                     sink_box_imp.reset_input_streams.append(&*entry); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn populate_cards(sink_box: Arc<SinkBox>) { |  | ||||||
|     gio::spawn_blocking(move || { |  | ||||||
|         let sink_box_ref = sink_box.clone(); |  | ||||||
|         let cards = get_cards(sink_box.clone()); |  | ||||||
|         glib::spawn_future(async move { |  | ||||||
|             glib::idle_add_once(move || { |  | ||||||
|                 let imp = sink_box_ref.imp(); |  | ||||||
|                 for card in cards { |  | ||||||
|                     imp.reset_cards.add(&CardEntry::new(card)); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn get_input_streams(sink_box: Arc<SinkBox>) -> Vec<InputStream> { |  | ||||||
|     let conn = Connection::new_session().unwrap(); |  | ||||||
|     let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|     let res: Result<(Vec<InputStream>,), Error> = proxy.method_call(AUDIO, "ListInputStreams", ()); |  | ||||||
|     if res.is_err() { |  | ||||||
|         show_error::<SinkBox>(sink_box.clone(), "Failed to list input streams"); |  | ||||||
|         return Vec::new(); |  | ||||||
|     } |  | ||||||
|     res.unwrap().0 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn get_sinks(sink_box: Arc<SinkBox>) -> Vec<Sink> { |  | ||||||
|     let conn = Connection::new_session().unwrap(); |  | ||||||
|     let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|     let res: Result<(Vec<Sink>,), Error> = proxy.method_call(AUDIO, "ListSinks", ()); |  | ||||||
|     if res.is_err() { |  | ||||||
|         show_error::<SinkBox>(sink_box.clone(), "Failed to list sinks"); |  | ||||||
|         return Vec::new(); |  | ||||||
|     } |  | ||||||
|     res.unwrap().0 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn get_cards(sink_box: Arc<SinkBox>) -> Vec<Card> { |  | ||||||
|     let conn = Connection::new_session().unwrap(); |  | ||||||
|     let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|     let res: Result<(Vec<Card>,), Error> = proxy.method_call(AUDIO, "ListCards", ()); |  | ||||||
|     if res.is_err() { |  | ||||||
|         show_error::<SinkBox>(sink_box.clone(), "Failed to list profiles"); |  | ||||||
|         return Vec::new(); |  | ||||||
|     } |  | ||||||
|     res.unwrap().0 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn get_default_sink_name(sink_box: Arc<SinkBox>) -> String { |  | ||||||
|     let conn = Connection::new_session().unwrap(); |  | ||||||
|     let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|     let res: Result<(String,), Error> = proxy.method_call(AUDIO, "GetDefaultSinkName", ()); |  | ||||||
|     if res.is_err() { |  | ||||||
|         show_error::<SinkBox>(sink_box.clone(), "Failed to get default sink name"); |  | ||||||
|         return String::from(""); |  | ||||||
|     } |  | ||||||
|     res.unwrap().0 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn get_default_sink(sink_box: Arc<SinkBox>) -> Sink { |  | ||||||
|     let conn = Connection::new_session().unwrap(); |  | ||||||
|     let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|     let res: Result<(Sink,), Error> = proxy.method_call(AUDIO, "GetDefaultSink", ()); |  | ||||||
|     if res.is_err() { |  | ||||||
|         show_error::<SinkBox>(sink_box.clone(), "Failed to get default sink"); |  | ||||||
|         return Sink::default(); |  | ||||||
|     } |  | ||||||
|     res.unwrap().0 |  | ||||||
| } |  | ||||||
							
								
								
									
										56
									
								
								src/components/audio/output/sink_const.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/components/audio/output/sink_const.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | ||||||
|  | use crate::components::audio::audio_entry::{AudioIcons, DBusFunction}; | ||||||
|  | 
 | ||||||
|  | pub const ICONS: AudioIcons = AudioIcons { | ||||||
|  |     muted: "audio-volume-muted-symbolic", | ||||||
|  |     active: "audio-volume-high-symbolic", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const SETVOLUME: DBusFunction = DBusFunction { | ||||||
|  |     function: "SetSinkVolume", | ||||||
|  |     error: "Failed to set sink volume", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const SETMUTE: DBusFunction = DBusFunction { | ||||||
|  |     function: "SetSinkMute", | ||||||
|  |     error: "Failed to mute sink", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const SETDEFAULT: DBusFunction = DBusFunction { | ||||||
|  |     function: "SetDefaultSink", | ||||||
|  |     error: "Failed to set default sink", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const GETDEFAULT: DBusFunction = DBusFunction { | ||||||
|  |     function: "GetDefaultSink", | ||||||
|  |     error: "Failed to get default sink", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const GETDEFAULTNAME: DBusFunction = DBusFunction { | ||||||
|  |     function: "GetDefaultSinkName", | ||||||
|  |     error: "Failed to get default sink name", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const GETOBJECTS: DBusFunction = DBusFunction { | ||||||
|  |     function: "ListSinks", | ||||||
|  |     error: "Failed to list sinks", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const GETSTREAMS: DBusFunction = DBusFunction { | ||||||
|  |     function: "ListInputStreams", | ||||||
|  |     error: "Failed to list input streams", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const SETSTREAMVOLUME: DBusFunction = DBusFunction { | ||||||
|  |     function: "SetInputStreamVolume", | ||||||
|  |     error: "Failed to set input stream volume", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const SETSTREAMMUTE: DBusFunction = DBusFunction { | ||||||
|  |     function: "SetInputStreamMute", | ||||||
|  |     error: "Failed to mute input stream", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const SETSTREAMOBJECT: DBusFunction = DBusFunction { | ||||||
|  |     function: "SetSinkOfInputStream", | ||||||
|  |     error: "Failed to set sink of input stream", | ||||||
|  | }; | ||||||
|  | @ -1,21 +1,12 @@ | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| use std::time::{Duration, SystemTime}; |  | ||||||
| 
 | 
 | ||||||
| use crate::components::base::error_impl::show_error; | use crate::components::audio::audio_entry::{new_entry, TAudioEntry}; | ||||||
| use crate::components::utils::set_action_row_ellipsis; |  | ||||||
| use adw::glib::Object; |  | ||||||
| use adw::prelude::{ButtonExt, CheckButtonExt, PreferencesRowExt, RangeExt}; |  | ||||||
| use dbus::blocking::Connection; |  | ||||||
| use dbus::Error; |  | ||||||
| use glib::subclass::types::ObjectSubclassIsExt; | use glib::subclass::types::ObjectSubclassIsExt; | ||||||
| use glib::{clone, Propagation}; | use gtk::CheckButton; | ||||||
| use gtk::{gio, CheckButton}; | use re_set_lib::audio::audio_structures::{InputStream, Sink}; | ||||||
| use re_set_lib::audio::audio_structures::Sink; |  | ||||||
| 
 |  | ||||||
| use crate::components::utils::{AUDIO, BASE, DBUS_PATH}; |  | ||||||
| 
 | 
 | ||||||
|  | use super::input_stream_entry::InputStreamEntry; | ||||||
| use super::sink_box::SinkBox; | use super::sink_box::SinkBox; | ||||||
| use super::sink_box_utils::refresh_default_sink; |  | ||||||
| use super::sink_entry_impl; | use super::sink_entry_impl; | ||||||
| 
 | 
 | ||||||
| glib::wrapper! { | glib::wrapper! { | ||||||
|  | @ -27,117 +18,28 @@ glib::wrapper! { | ||||||
| unsafe impl Send for SinkEntry {} | unsafe impl Send for SinkEntry {} | ||||||
| unsafe impl Sync for SinkEntry {} | unsafe impl Sync for SinkEntry {} | ||||||
| 
 | 
 | ||||||
|  | impl TAudioEntry<super::sink_entry_impl::SinkEntry> for SinkEntry { | ||||||
|  |     fn entry_imp(&self) -> &super::sink_entry_impl::SinkEntry { | ||||||
|  |         self.imp() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl SinkEntry { | impl SinkEntry { | ||||||
|     pub fn new( |     pub fn new( | ||||||
|         is_default: bool, |         is_default: bool, | ||||||
|         check_group: Arc<CheckButton>, |         check_group: Arc<CheckButton>, | ||||||
|         stream: Sink, |         sink: Sink, | ||||||
|         output_box: Arc<SinkBox>, |         output_box: Arc<SinkBox>, | ||||||
|     ) -> Self { |     ) -> Arc<Self> { | ||||||
|         let obj: Self = Object::builder().build(); |         new_entry::< | ||||||
|         // TODO use event callback for progress bar -> this is the "im speaking" indicator
 |             Sink, | ||||||
|         { |             InputStream, | ||||||
|             let imp = obj.imp(); |             SinkEntry, | ||||||
|             imp.reset_sink_name.set_title(stream.alias.clone().as_str()); |             super::sink_entry_impl::SinkEntry, | ||||||
|             let name = Arc::new(stream.name.clone()); |             InputStreamEntry, | ||||||
|             let volume = stream.volume.first().unwrap_or(&0_u32); |             super::input_stream_entry_impl::InputStreamEntry, | ||||||
|             let fraction = (*volume as f64 / 655.36).round(); |             SinkBox, | ||||||
|             let percentage = (fraction).to_string() + "%"; |             super::sink_box_impl::SinkBox, | ||||||
|             let output_box_slider = output_box.clone(); |         >(is_default, check_group, sink, output_box) | ||||||
|             let output_box_ref = output_box.clone(); |  | ||||||
|             imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|             imp.reset_volume_slider.set_value(*volume as f64); |  | ||||||
|             imp.stream.replace(stream); |  | ||||||
|             imp.reset_volume_slider.connect_change_value( |  | ||||||
|                 clone!(@weak imp => @default-return Propagation::Stop, move |_, _, value| { |  | ||||||
|                     let fraction = (value / 655.36).round(); |  | ||||||
|                     let percentage = (fraction).to_string() + "%"; |  | ||||||
|                     imp.reset_volume_percentage.set_text(&percentage); |  | ||||||
|                      let sink = imp.stream.borrow(); |  | ||||||
|                      let index = sink.index; |  | ||||||
|                      let channels = sink.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()); |  | ||||||
|                     } |  | ||||||
|                      set_sink_volume(value, index, channels, output_box_slider.clone()); |  | ||||||
|                     Propagation::Proceed |  | ||||||
|                 }), |  | ||||||
|             ); |  | ||||||
|             imp.reset_selected_sink.set_group(Some(&*check_group)); |  | ||||||
|             if is_default { |  | ||||||
|                 imp.reset_selected_sink.set_active(true); |  | ||||||
|             } else { |  | ||||||
|                 imp.reset_selected_sink.set_active(false); |  | ||||||
|             } |  | ||||||
|             imp.reset_selected_sink.connect_toggled(move |button| { |  | ||||||
|                 let output_box_ref = output_box.clone(); |  | ||||||
|                 if button.is_active() { |  | ||||||
|                     let name = name.clone(); |  | ||||||
|                     gio::spawn_blocking(move || { |  | ||||||
|                         let result = set_default_sink(name, output_box_ref.clone()); |  | ||||||
|                         if result.is_none() { |  | ||||||
|                             return; |  | ||||||
|                         } |  | ||||||
|                         refresh_default_sink(result.unwrap(), output_box_ref, true); |  | ||||||
|                     }); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|             imp.reset_sink_mute |  | ||||||
|                 .connect_clicked(clone!(@weak imp => move |_| { |  | ||||||
|                     let stream = imp.stream.clone(); |  | ||||||
|                     let mut stream = stream.borrow_mut(); |  | ||||||
|                     stream.muted = !stream.muted; |  | ||||||
|                     if stream.muted { |  | ||||||
|                         imp.reset_sink_mute |  | ||||||
|                            .set_icon_name("audio-volume-muted-symbolic"); |  | ||||||
|                     } else { |  | ||||||
|                         imp.reset_sink_mute |  | ||||||
|                            .set_icon_name("audio-volume-high-symbolic"); |  | ||||||
|                     } |  | ||||||
|                     toggle_sink_mute(stream.index, stream.muted, output_box_ref.clone()); |  | ||||||
|                 })); |  | ||||||
|             set_action_row_ellipsis(imp.reset_sink_name.get()); |  | ||||||
|         } |  | ||||||
|         obj |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| pub fn set_sink_volume(value: f64, index: u32, channels: u16, output_box: Arc<SinkBox>) -> bool { |  | ||||||
|     gio::spawn_blocking(move || { |  | ||||||
|         let conn = Connection::new_session().unwrap(); |  | ||||||
|         let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|         let res: Result<(), Error> = |  | ||||||
|             proxy.method_call(AUDIO, "SetSinkVolume", (index, channels, value as u32)); |  | ||||||
|         if res.is_err() { |  | ||||||
|             show_error::<SinkBox>(output_box, "Failed to set sink volume") |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn toggle_sink_mute(index: u32, muted: bool, output_box: Arc<SinkBox>) -> bool { |  | ||||||
|     gio::spawn_blocking(move || { |  | ||||||
|         let conn = Connection::new_session().unwrap(); |  | ||||||
|         let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|         let res: Result<(), Error> = proxy.method_call(AUDIO, "SetSinkMute", (index, muted)); |  | ||||||
|         if res.is_err() { |  | ||||||
|             show_error::<SinkBox>(output_box, "Failed to mute sink") |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|     true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn set_default_sink(name: Arc<String>, output_box: Arc<SinkBox>) -> Option<Sink> { |  | ||||||
|     let conn = Connection::new_session().unwrap(); |  | ||||||
|     let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); |  | ||||||
|     let res: Result<(Sink,), Error> = proxy.method_call(AUDIO, "SetDefaultSink", (name.as_str(),)); |  | ||||||
|     if res.is_err() { |  | ||||||
|         show_error::<SinkBox>(output_box, "Failed to set default sink"); |  | ||||||
|         return None; |  | ||||||
|     } |  | ||||||
|     Some(res.unwrap().0) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -5,10 +5,13 @@ use std::cell::RefCell; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| use std::time::SystemTime; | use std::time::SystemTime; | ||||||
| 
 | 
 | ||||||
|  | use crate::components::audio::audio_entry::{AudioIcons, DBusFunction, TAudioEntryImpl}; | ||||||
| use crate::components::audio::output::sink_entry; | use crate::components::audio::output::sink_entry; | ||||||
| use gtk::subclass::prelude::*; | use gtk::subclass::prelude::*; | ||||||
| use gtk::{Button, CheckButton, CompositeTemplate, Label, Scale}; | use gtk::{Button, CheckButton, CompositeTemplate, Label, Scale}; | ||||||
| 
 | 
 | ||||||
|  | use super::sink_const::{ICONS, SETDEFAULT, SETMUTE, SETVOLUME}; | ||||||
|  | 
 | ||||||
| #[derive(Default, CompositeTemplate)] | #[derive(Default, CompositeTemplate)] | ||||||
| #[template(resource = "/org/Xetibo/ReSet/resetSinkEntry.ui")] | #[template(resource = "/org/Xetibo/ReSet/resetSinkEntry.ui")] | ||||||
| pub struct SinkEntry { | pub struct SinkEntry { | ||||||
|  | @ -22,7 +25,7 @@ pub struct SinkEntry { | ||||||
|     pub reset_volume_slider: TemplateChild<Scale>, |     pub reset_volume_slider: TemplateChild<Scale>, | ||||||
|     #[template_child] |     #[template_child] | ||||||
|     pub reset_volume_percentage: TemplateChild<Label>, |     pub reset_volume_percentage: TemplateChild<Label>, | ||||||
|     pub stream: Arc<RefCell<Sink>>, |     pub sink: Arc<RefCell<Sink>>, | ||||||
|     pub volume_time_stamp: RefCell<Option<SystemTime>>, |     pub volume_time_stamp: RefCell<Option<SystemTime>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -47,3 +50,49 @@ impl PreferencesGroupImpl for SinkEntry {} | ||||||
| impl ObjectImpl for SinkEntry {} | impl ObjectImpl for SinkEntry {} | ||||||
| 
 | 
 | ||||||
| impl WidgetImpl for SinkEntry {} | impl WidgetImpl for SinkEntry {} | ||||||
|  | 
 | ||||||
|  | impl TAudioEntryImpl<Sink> for SinkEntry { | ||||||
|  |     fn name(&self) -> &TemplateChild<ActionRow> { | ||||||
|  |         &self.reset_sink_name | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn selected_audio_object(&self) -> &TemplateChild<CheckButton> { | ||||||
|  |         &self.reset_selected_sink | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn mute(&self) -> &TemplateChild<Button> { | ||||||
|  |         &self.reset_sink_mute | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_slider(&self) -> &TemplateChild<Scale> { | ||||||
|  |         &self.reset_volume_slider | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_percentage(&self) -> &TemplateChild<Label> { | ||||||
|  |         &self.reset_volume_percentage | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn audio_object(&self) -> Arc<RefCell<Sink>> { | ||||||
|  |         self.sink.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>> { | ||||||
|  |         &self.volume_time_stamp | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn set_volume_fn(&self) -> &'static DBusFunction { | ||||||
|  |         &SETVOLUME | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn set_audio_object_fn(&self) -> &'static DBusFunction { | ||||||
|  |         &SETDEFAULT | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn set_mute_fn(&self) -> &'static DBusFunction { | ||||||
|  |         &SETMUTE | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn icons(&self) -> &AudioIcons { | ||||||
|  |         &ICONS | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,12 +1,10 @@ | ||||||
| 
 | use crate::components::plugin::function::TSideBarInfo; | ||||||
| use crate::components::plugin::function::{TSideBarInfo}; |  | ||||||
| use crate::components::window::sidebar_entry_impl; | use crate::components::window::sidebar_entry_impl; | ||||||
| use crate::components::window::sidebar_entry_impl::SidebarAction; | use crate::components::window::sidebar_entry_impl::SidebarAction; | ||||||
| use adw::subclass::prelude::ObjectSubclassIsExt; | use adw::subclass::prelude::ObjectSubclassIsExt; | ||||||
| use glib::Object; | use glib::Object; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| glib::wrapper! { | glib::wrapper! { | ||||||
|     pub struct SidebarEntry(ObjectSubclass<sidebar_entry_impl::SidebarEntry>) |     pub struct SidebarEntry(ObjectSubclass<sidebar_entry_impl::SidebarEntry>) | ||||||
|         @extends gtk::ListBoxRow, gtk::Widget, |         @extends gtk::ListBoxRow, gtk::Widget, | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.12"/> |   <requires lib="gtk" version="4.12"/> | ||||||
|   <requires lib="libadwaita" version="1.4"/> |   <requires lib="libadwaita" version="1.4"/> | ||||||
|  | @ -22,7 +22,7 @@ | ||||||
|       <object class="AdwNavigationView"> |       <object class="AdwNavigationView"> | ||||||
|         <child> |         <child> | ||||||
|           <object class="AdwNavigationPage"> |           <object class="AdwNavigationPage"> | ||||||
|             <property name="tag">output</property> |             <property name="tag">audiostreams</property> | ||||||
|             <property name="title">output</property> |             <property name="title">output</property> | ||||||
|             <child> |             <child> | ||||||
|               <object class="GtkBox"> |               <object class="GtkBox"> | ||||||
|  | @ -128,7 +128,7 @@ | ||||||
|         </child> |         </child> | ||||||
|         <child> |         <child> | ||||||
|           <object class="AdwNavigationPage"> |           <object class="AdwNavigationPage"> | ||||||
|             <property name="tag">sources</property> |             <property name="tag">devices</property> | ||||||
|             <property name="title">sources</property> |             <property name="title">sources</property> | ||||||
|             <child> |             <child> | ||||||
|               <object class="GtkBox"> |               <object class="GtkBox"> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.12"/> |   <requires lib="gtk" version="4.12"/> | ||||||
|   <requires lib="libadwaita" version="1.4"/> |   <requires lib="libadwaita" version="1.4"/> | ||||||
|  | @ -19,7 +19,7 @@ | ||||||
|       <object class="AdwNavigationView"> |       <object class="AdwNavigationView"> | ||||||
|         <child> |         <child> | ||||||
|           <object class="AdwNavigationPage"> |           <object class="AdwNavigationPage"> | ||||||
|             <property name="tag">output</property> |             <property name="tag">audiostreams</property> | ||||||
|             <property name="title">output</property> |             <property name="title">output</property> | ||||||
|             <child> |             <child> | ||||||
|               <object class="GtkBox"> |               <object class="GtkBox"> | ||||||
|  | @ -126,7 +126,7 @@ | ||||||
|         </child> |         </child> | ||||||
|         <child> |         <child> | ||||||
|           <object class="AdwNavigationPage"> |           <object class="AdwNavigationPage"> | ||||||
|             <property name="tag">outputDevices</property> |             <property name="tag">devices</property> | ||||||
|             <property name="title">outputDevices</property> |             <property name="title">outputDevices</property> | ||||||
|             <child> |             <child> | ||||||
|               <object class="GtkBox"> |               <object class="GtkBox"> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.12"/> |   <requires lib="gtk" version="4.12"/> | ||||||
|   <requires lib="libadwaita" version="1.4"/> |   <requires lib="libadwaita" version="1.4"/> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.12"/> |   <requires lib="gtk" version="4.12"/> | ||||||
|   <requires lib="libadwaita" version="1.0"/> |   <requires lib="libadwaita" version="1.0"/> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="libadwaita" version="1.4"/> |   <requires lib="libadwaita" version="1.4"/> | ||||||
|   <object class="AdwComboRow" id="reset_card_entry"> |   <object class="AdwComboRow" id="reset_card_entry"> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.12"/> |   <requires lib="gtk" version="4.12"/> | ||||||
|   <template class="resetError" parent="GtkPopover"> |   <template class="resetError" parent="GtkPopover"> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.12"/> |   <requires lib="gtk" version="4.12"/> | ||||||
|   <requires lib="libadwaita" version="1.4"/> |   <requires lib="libadwaita" version="1.4"/> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.0"/> |   <requires lib="gtk" version="4.0"/> | ||||||
|   <template class="resetListBoxRow" parent="GtkListBoxRow"> |   <template class="resetListBoxRow" parent="GtkListBoxRow"> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gio" version="2.0"/> |   <requires lib="gio" version="2.0"/> | ||||||
|   <requires lib="gtk" version="4.12"/> |   <requires lib="gtk" version="4.12"/> | ||||||
|  | @ -119,10 +119,10 @@ | ||||||
|             </child> |             </child> | ||||||
|           </object> |           </object> | ||||||
|         </property> |         </property> | ||||||
|         <!-- Custom fragments --> |         <!-- Custom object fragments --> | ||||||
|       </object> |       </object> | ||||||
|     </child> |     </child> | ||||||
|     <!-- Custom fragments --> |     <!-- Custom template fragments --> | ||||||
|     <child> |     <child> | ||||||
|       <object id="reset_sidebar_breakpoint" class="AdwBreakpoint"> |       <object id="reset_sidebar_breakpoint" class="AdwBreakpoint"> | ||||||
|     </object> |     </object> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.12"/> |   <requires lib="gtk" version="4.12"/> | ||||||
|   <requires lib="libadwaita" version="1.4"/> |   <requires lib="libadwaita" version="1.4"/> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.12"/> |   <requires lib="gtk" version="4.12"/> | ||||||
|   <template class="resetPopup" parent="GtkPopover"> |   <template class="resetPopup" parent="GtkPopover"> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="libadwaita" version="1.0"/> |   <requires lib="libadwaita" version="1.0"/> | ||||||
|   <template class="resetSavedWifiEntry" parent="AdwActionRow"> |   <template class="resetSavedWifiEntry" parent="AdwActionRow"> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.0"/> |   <requires lib="gtk" version="4.0"/> | ||||||
|   <template class="resetSettingBox" parent="GtkBox"> |   <template class="resetSettingBox" parent="GtkBox"> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.0"/> |   <requires lib="gtk" version="4.0"/> | ||||||
|   <object class="GtkShortcutsWindow" id="help_overlay"> |   <object class="GtkShortcutsWindow" id="help_overlay"> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.0"/> |   <requires lib="gtk" version="4.0"/> | ||||||
|   <template class="resetSidebarEntry" parent="GtkListBoxRow"> |   <template class="resetSidebarEntry" parent="GtkListBoxRow"> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.12"/> |   <requires lib="gtk" version="4.12"/> | ||||||
|   <requires lib="libadwaita" version="1.3"/> |   <requires lib="libadwaita" version="1.3"/> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.12"/> |   <requires lib="gtk" version="4.12"/> | ||||||
|   <requires lib="libadwaita" version="1.3"/> |   <requires lib="libadwaita" version="1.3"/> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8' standalone='no'?> | <?xml version='1.0' encoding='UTF-8' standalone='no'?> | ||||||
| <!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd"> | <!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd"> | ||||||
| <cambalache-project version="0.17.1" target_tk="gtk-4.0"> | <cambalache-project version="0.17.3" target_tk="gtk-4.0"> | ||||||
|   <ui> |   <ui> | ||||||
| 	(3,1,None,"resetMainWindow.ui",None,None,None,None,None,None,None), | 	(3,1,None,"resetMainWindow.ui",None,None,None,None,None,None,None), | ||||||
| 	(4,7,None,"resetWiFi.ui",None,None,None,None,None,None,None), | 	(4,7,None,"resetWiFi.ui",None,None,None,None,None,None,None), | ||||||
|  | @ -30,289 +30,289 @@ | ||||||
| 	(21,"libadwaita","1.4",None) | 	(21,"libadwaita","1.4",None) | ||||||
|   </ui_library> |   </ui_library> | ||||||
|   <object> |   <object> | ||||||
| 	(3,1,"AdwApplicationWindow","resetUI",None,None,None,None,-1,"  <child>\n    <object id=\"reset_sidebar_breakpoint\" class=\"AdwBreakpoint\">\n    </object>\n  </child>"), | 	(3,1,"AdwApplicationWindow","resetUI",None,None,None,None,-1,"  <child>\n    <object id=\"reset_sidebar_breakpoint\" class=\"AdwBreakpoint\">\n    </object>\n  </child>",None), | ||||||
| 	(3,2,"AdwOverlaySplitView","reset_overlay_split_view",1,None,None,None,2,""), | 	(3,2,"AdwOverlaySplitView","reset_overlay_split_view",1,None,None,None,2,"",None), | ||||||
| 	(3,3,"GtkBox",None,2,None,None,None,None,None), | 	(3,3,"GtkBox",None,2,None,None,None,None,None,None), | ||||||
| 	(3,17,"GtkWindowHandle",None,1,None,None,None,1,None), | 	(3,17,"GtkWindowHandle",None,1,None,None,None,1,None,None), | ||||||
| 	(3,18,"GtkBox",None,2,None,None,None,-1,None), | 	(3,18,"GtkBox",None,2,None,None,None,-1,None,None), | ||||||
| 	(3,19,"GtkWindowHandle",None,18,None,None,None,None,None), | 	(3,19,"GtkWindowHandle",None,18,None,None,None,None,None,None), | ||||||
| 	(3,20,"GtkSearchEntry","reset_search_entry",19,None,None,None,None,None), | 	(3,20,"GtkSearchEntry","reset_search_entry",19,None,None,None,None,None,None), | ||||||
| 	(3,24,"GtkWindowHandle",None,3,None,None,None,None,None), | 	(3,24,"GtkWindowHandle",None,3,None,None,None,None,None,None), | ||||||
| 	(3,25,"GtkBox",None,24,None,None,None,None,None), | 	(3,25,"GtkBox",None,24,None,None,None,None,None,None), | ||||||
| 	(3,26,"GtkButton","reset_sidebar_toggle",25,None,None,None,None,None), | 	(3,26,"GtkButton","reset_sidebar_toggle",25,None,None,None,None,None,None), | ||||||
| 	(3,31,"GtkButton","reset_close",25,None,None,None,2,None), | 	(3,31,"GtkButton","reset_close",25,None,None,None,2,None,None), | ||||||
| 	(3,33,"GtkMenuButton",None,25,None,None,None,1,None), | 	(3,33,"GtkMenuButton",None,25,None,None,None,1,None,None), | ||||||
| 	(3,40,"GtkScrolledWindow",None,18,None,None,None,1,None), | 	(3,40,"GtkScrolledWindow",None,18,None,None,None,1,None,None), | ||||||
| 	(3,41,"GtkViewport",None,40,None,None,None,None,None), | 	(3,41,"GtkViewport",None,40,None,None,None,None,None,None), | ||||||
| 	(3,42,"GtkListBox","reset_sidebar_list",41,None,None,None,None,None), | 	(3,42,"GtkListBox","reset_sidebar_list",41,None,None,None,None,None,None), | ||||||
| 	(3,43,"GtkScrolledWindow",None,3,None,None,None,1,None), | 	(3,43,"GtkScrolledWindow",None,3,None,None,None,1,None,None), | ||||||
| 	(3,44,"GtkViewport",None,43,None,None,None,None,None), | 	(3,44,"GtkViewport",None,43,None,None,None,None,None,None), | ||||||
| 	(3,45,"GtkFlowBox","reset_main",44,None,None,None,None,None), | 	(3,45,"GtkFlowBox","reset_main",44,None,None,None,None,None,None), | ||||||
| 	(3,54,"(menu)","main_menu",None,None,None,None,None,None), | 	(3,54,"(menu)","main_menu",None,None,None,None,None,None,None), | ||||||
| 	(3,59,"(external)","54",None,None,None,None,None,None), | 	(3,59,"(external)","54",None,None,None,None,None,None,None), | ||||||
| 	(3,61,"(external)","59",None,None,None,None,None,None), | 	(3,61,"(external)","59",None,None,None,None,None,None,None), | ||||||
| 	(3,63,"(external)","61",None,None,None,None,None,None), | 	(3,63,"(external)","61",None,None,None,None,None,None,None), | ||||||
| 	(3,65,"(external)","63",None,None,None,None,None,None), | 	(3,65,"(external)","63",None,None,None,None,None,None,None), | ||||||
| 	(3,69,"(external)","67",None,None,None,None,None,None), | 	(3,69,"(external)","67",None,None,None,None,None,None,None), | ||||||
| 	(3,72,"(external)","71",None,None,None,None,None,None), | 	(3,72,"(external)","71",None,None,None,None,None,None,None), | ||||||
| 	(3,73,"(item)",None,54,None,None,None,1,None), | 	(3,73,"(item)",None,54,None,None,None,1,None,None), | ||||||
| 	(3,74,"(external)","72",None,None,None,None,None,None), | 	(3,74,"(external)","72",None,None,None,None,None,None,None), | ||||||
| 	(3,75,"(item)",None,54,None,None,None,2,None), | 	(3,75,"(item)",None,54,None,None,None,2,None,None), | ||||||
| 	(3,76,"(external)","74",None,None,None,None,None,None), | 	(3,76,"(external)","74",None,None,None,None,None,None,None), | ||||||
| 	(4,7,"GtkBox","resetWifi",None,None,None,None,None,None), | 	(4,7,"GtkBox","resetWifi",None,None,None,None,None,None,None), | ||||||
| 	(4,152,"AdwNavigationView","reset_wifi_navigation",7,None,None,None,1,None), | 	(4,152,"AdwNavigationView","reset_wifi_navigation",7,None,None,None,1,None,None), | ||||||
| 	(4,153,"AdwNavigationPage",None,152,None,None,None,None,None), | 	(4,153,"AdwNavigationPage",None,152,None,None,None,None,None,None), | ||||||
| 	(4,154,"GtkBox",None,153,None,None,None,1,None), | 	(4,154,"GtkBox",None,153,None,None,None,1,None,None), | ||||||
| 	(4,174,"AdwNavigationPage",None,152,None,None,None,2,None), | 	(4,174,"AdwNavigationPage",None,152,None,None,None,2,None,None), | ||||||
| 	(4,175,"GtkBox",None,174,None,None,None,None,None), | 	(4,175,"GtkBox",None,174,None,None,None,None,None,None), | ||||||
| 	(4,182,"GtkBox",None,7,None,None,None,None,None), | 	(4,182,"GtkBox",None,7,None,None,None,None,None,None), | ||||||
| 	(4,183,"GtkLabel",None,182,None,None,None,None,None), | 	(4,183,"GtkLabel",None,182,None,None,None,None,None,None), | ||||||
| 	(4,184,"GtkSwitch","reset_wifi_switch",182,None,None,None,1,None), | 	(4,184,"GtkSwitch","reset_wifi_switch",182,None,None,None,1,None,None), | ||||||
| 	(4,199,"AdwPreferencesGroup","reset_wifi_details",154,None,None,None,None,None), | 	(4,199,"AdwPreferencesGroup","reset_wifi_details",154,None,None,None,None,None,None), | ||||||
| 	(4,200,"AdwComboRow","reset_wifi_device",199,None,None,None,-1,None), | 	(4,200,"AdwComboRow","reset_wifi_device",199,None,None,None,-1,None,None), | ||||||
| 	(4,201,"AdwActionRow","reset_saved_networks",199,None,None,None,-1,None), | 	(4,201,"AdwActionRow","reset_saved_networks",199,None,None,None,-1,None,None), | ||||||
| 	(4,202,"GtkImage",None,201,None,None,None,None,None), | 	(4,202,"GtkImage",None,201,None,None,None,None,None,None), | ||||||
| 	(4,204,"AdwPreferencesGroup","reset_stored_wifi_list",175,None,None,None,2,None), | 	(4,204,"AdwPreferencesGroup","reset_stored_wifi_list",175,None,None,None,2,None,None), | ||||||
| 	(4,205,"AdwPreferencesGroup",None,175,None,None,None,None,None), | 	(4,205,"AdwPreferencesGroup",None,175,None,None,None,None,None,None), | ||||||
| 	(4,206,"AdwActionRow","reset_available_networks",205,None,None,None,-1,None), | 	(4,206,"AdwActionRow","reset_available_networks",205,None,None,None,-1,None,None), | ||||||
| 	(4,207,"GtkImage",None,206,None,None,None,None,None), | 	(4,207,"GtkImage",None,206,None,None,None,None,None,None), | ||||||
| 	(4,208,"AdwPreferencesGroup","reset_wifi_list",154,None,None,None,1,None), | 	(4,208,"AdwPreferencesGroup","reset_wifi_list",154,None,None,None,1,None,None), | ||||||
| 	(4,209,"resetError","error",7,None,None,None,2,None), | 	(4,209,"resetError","error",7,None,None,None,2,None,None), | ||||||
| 	(5,12,"AdwActionRow","resetWifiEntry",None,None,None,None,-1,None), | 	(5,12,"AdwActionRow","resetWifiEntry",None,None,None,None,-1,None,None), | ||||||
| 	(5,13,"resetPopup","reset_wifi_popup",12,None,None,None,None,None), | 	(5,13,"resetPopup","reset_wifi_popup",12,None,None,None,None,None,None), | ||||||
| 	(6,1,"GtkListBoxRow","resetSidebarEntry",None,None,None,None,None,None), | 	(6,1,"GtkListBoxRow","resetSidebarEntry",None,None,None,None,None,None,None), | ||||||
| 	(6,2,"GtkBox",None,1,None,None,None,-1,None), | 	(6,2,"GtkBox",None,1,None,None,None,-1,None,None), | ||||||
| 	(6,6,"GtkImage","reset_sidebar_image",2,None,None,None,None,None), | 	(6,6,"GtkImage","reset_sidebar_image",2,None,None,None,None,None,None), | ||||||
| 	(6,7,"GtkLabel","reset_sidebar_label",2,None,None,None,1,None), | 	(6,7,"GtkLabel","reset_sidebar_label",2,None,None,None,1,None,None), | ||||||
| 	(7,14,"AdwPreferencesGroup","resetInputStreamEntry",None,None,None,None,None,None), | 	(7,14,"AdwPreferencesGroup","resetInputStreamEntry",None,None,None,None,None,None,None), | ||||||
| 	(7,15,"AdwComboRow","reset_sink_selection",14,None,None,None,None,None), | 	(7,15,"AdwComboRow","reset_sink_selection",14,None,None,None,None,None,None), | ||||||
| 	(7,16,"AdwActionRow",None,14,None,None,None,1,None), | 	(7,16,"AdwActionRow",None,14,None,None,None,1,None,None), | ||||||
| 	(7,17,"GtkBox",None,16,None,None,None,None,None), | 	(7,17,"GtkBox",None,16,None,None,None,None,None,None), | ||||||
| 	(7,18,"GtkBox",None,17,None,None,None,None,None), | 	(7,18,"GtkBox",None,17,None,None,None,None,None,None), | ||||||
| 	(7,19,"GtkButton","reset_sink_mute",18,None,None,None,None,None), | 	(7,19,"GtkButton","reset_sink_mute",18,None,None,None,None,None,None), | ||||||
| 	(7,20,"GtkScale","reset_volume_slider",18,None,None,None,1,None), | 	(7,20,"GtkScale","reset_volume_slider",18,None,None,None,1,None,None), | ||||||
| 	(7,21,"GtkAdjustment",None,20,None,None,None,None,None), | 	(7,21,"GtkAdjustment",None,20,None,None,None,None,None,None), | ||||||
| 	(7,22,"GtkLabel","reset_volume_percentage",18,None,None,None,2,None), | 	(7,22,"GtkLabel","reset_volume_percentage",18,None,None,None,2,None,None), | ||||||
| 	(7,24,"GtkSeparator",None,14,None,None,None,-1,None), | 	(7,24,"GtkSeparator",None,14,None,None,None,-1,None,None), | ||||||
| 	(8,1,"GtkBox","resetAudioOutput",None,None,None,None,None,None), | 	(8,1,"GtkBox","resetAudioOutput",None,None,None,None,None,None,None), | ||||||
| 	(8,53,"GtkLabel",None,1,None,None,None,None,None), | 	(8,53,"GtkLabel",None,1,None,None,None,None,None,None), | ||||||
| 	(8,54,"AdwNavigationView",None,1,None,None,None,1,None), | 	(8,54,"AdwNavigationView",None,1,None,None,None,1,None,None), | ||||||
| 	(8,55,"AdwNavigationPage",None,54,None,None,None,None,None), | 	(8,55,"AdwNavigationPage",None,54,None,None,None,None,None,None), | ||||||
| 	(8,56,"GtkBox",None,55,None,None,None,None,None), | 	(8,56,"GtkBox",None,55,None,None,None,None,None,None), | ||||||
| 	(8,71,"AdwNavigationPage",None,54,None,None,None,1,None), | 	(8,71,"AdwNavigationPage",None,54,None,None,None,1,None,None), | ||||||
| 	(8,72,"GtkBox",None,71,None,None,None,None,None), | 	(8,72,"GtkBox",None,71,None,None,None,None,None,None), | ||||||
| 	(8,85,"GtkBox","reset_input_streams",56,None,None,None,3,None), | 	(8,85,"GtkBox","reset_input_streams",56,None,None,None,3,None,None), | ||||||
| 	(8,86,"GtkLabel",None,85,None,None,None,None,None), | 	(8,86,"GtkLabel",None,85,None,None,None,None,None,None), | ||||||
| 	(8,89,"GtkBox","reset_sinks",72,None,None,None,3,None), | 	(8,89,"GtkBox","reset_sinks",72,None,None,None,3,None,None), | ||||||
| 	(8,90,"GtkLabel",None,89,None,None,None,None,None), | 	(8,90,"GtkLabel",None,89,None,None,None,None,None,None), | ||||||
| 	(8,103,"GtkSeparator",None,56,None,None,None,2,None), | 	(8,103,"GtkSeparator",None,56,None,None,None,2,None,None), | ||||||
| 	(8,116,"AdwNavigationPage",None,54,None,None,None,2,None), | 	(8,116,"AdwNavigationPage",None,54,None,None,None,2,None,None), | ||||||
| 	(8,117,"GtkBox",None,116,None,None,None,None,None), | 	(8,117,"GtkBox",None,116,None,None,None,None,None,None), | ||||||
| 	(8,123,"GtkLabel",None,117,None,None,None,2,None), | 	(8,123,"GtkLabel",None,117,None,None,None,2,None,None), | ||||||
| 	(8,130,"AdwPreferencesGroup",None,56,None,None,None,None,None), | 	(8,130,"AdwPreferencesGroup",None,56,None,None,None,None,None,None), | ||||||
| 	(8,132,"AdwActionRow","reset_sinks_row",130,None,None,None,None,None), | 	(8,132,"AdwActionRow","reset_sinks_row",130,None,None,None,None,None,None), | ||||||
| 	(8,133,"GtkImage",None,132,None,None,None,None,None), | 	(8,133,"GtkImage",None,132,None,None,None,None,None,None), | ||||||
| 	(8,134,"AdwActionRow","reset_cards_row",130,None,None,None,1,None), | 	(8,134,"AdwActionRow","reset_cards_row",130,None,None,None,1,None,None), | ||||||
| 	(8,135,"GtkImage",None,134,None,None,None,None,None), | 	(8,135,"GtkImage",None,134,None,None,None,None,None,None), | ||||||
| 	(8,137,"AdwComboRow","reset_sink_dropdown",130,None,None,None,2,None), | 	(8,137,"AdwComboRow","reset_sink_dropdown",130,None,None,None,2,None,None), | ||||||
| 	(8,138,"AdwPreferencesGroup",None,72,None,None,None,None,None), | 	(8,138,"AdwPreferencesGroup",None,72,None,None,None,None,None,None), | ||||||
| 	(8,139,"AdwActionRow","reset_input_stream_button",138,None,None,None,None,None), | 	(8,139,"AdwActionRow","reset_input_stream_button",138,None,None,None,None,None,None), | ||||||
| 	(8,140,"GtkImage",None,139,None,None,None,None,None), | 	(8,140,"GtkImage",None,139,None,None,None,None,None,None), | ||||||
| 	(8,141,"AdwPreferencesGroup",None,117,None,None,None,None,None), | 	(8,141,"AdwPreferencesGroup",None,117,None,None,None,None,None,None), | ||||||
| 	(8,142,"AdwActionRow","reset_input_cards_back_button",141,None,None,None,None,None), | 	(8,142,"AdwActionRow","reset_input_cards_back_button",141,None,None,None,None,None,None), | ||||||
| 	(8,143,"GtkImage",None,142,None,None,None,None,None), | 	(8,143,"GtkImage",None,142,None,None,None,None,None,None), | ||||||
| 	(8,144,"AdwPreferencesGroup","reset_cards",117,None,None,None,2,None), | 	(8,144,"AdwPreferencesGroup","reset_cards",117,None,None,None,2,None,None), | ||||||
| 	(8,145,"AdwActionRow",None,130,None,None,None,3,None), | 	(8,145,"AdwActionRow",None,130,None,None,None,3,None,None), | ||||||
| 	(8,146,"GtkBox",None,145,None,None,None,-1,None), | 	(8,146,"GtkBox",None,145,None,None,None,-1,None,None), | ||||||
| 	(8,147,"GtkButton","reset_sink_mute",146,None,None,None,None,None), | 	(8,147,"GtkButton","reset_sink_mute",146,None,None,None,None,None,None), | ||||||
| 	(8,148,"GtkScale","reset_volume_slider",146,None,None,None,1,None), | 	(8,148,"GtkScale","reset_volume_slider",146,None,None,None,1,None,None), | ||||||
| 	(8,149,"GtkAdjustment",None,148,None,None,None,None,None), | 	(8,149,"GtkAdjustment",None,148,None,None,None,None,None,None), | ||||||
| 	(8,150,"GtkLabel","reset_volume_percentage",146,None,None,None,2,None), | 	(8,150,"GtkLabel","reset_volume_percentage",146,None,None,None,2,None,None), | ||||||
| 	(8,151,"resetError","error",1,None,None,None,2,None), | 	(8,151,"resetError","error",1,None,None,None,2,None,None), | ||||||
| 	(10,1,"GtkBox","resetBluetooth",None,None,None,None,None,None), | 	(10,1,"GtkBox","resetBluetooth",None,None,None,None,None,None,None), | ||||||
| 	(10,119,"AdwNavigationView",None,1,None,None,None,1,None), | 	(10,119,"AdwNavigationView",None,1,None,None,None,1,None,None), | ||||||
| 	(10,120,"AdwNavigationPage",None,119,None,None,None,None,None), | 	(10,120,"AdwNavigationPage",None,119,None,None,None,None,None,None), | ||||||
| 	(10,121,"GtkBox",None,120,None,None,None,None,None), | 	(10,121,"GtkBox",None,120,None,None,None,None,None,None), | ||||||
| 	(10,138,"AdwNavigationPage",None,119,None,None,None,1,None), | 	(10,138,"AdwNavigationPage",None,119,None,None,None,1,None,None), | ||||||
| 	(10,142,"GtkBox",None,1,None,None,None,None,None), | 	(10,142,"GtkBox",None,1,None,None,None,None,None,None), | ||||||
| 	(10,143,"GtkLabel",None,142,None,None,None,None,None), | 	(10,143,"GtkLabel",None,142,None,None,None,None,None,None), | ||||||
| 	(10,144,"GtkSwitch","reset_bluetooth_switch",142,None,None,None,1,None), | 	(10,144,"GtkSwitch","reset_bluetooth_switch",142,None,None,None,1,None,None), | ||||||
| 	(10,153,"GtkBox",None,138,None,None,None,None,None), | 	(10,153,"GtkBox",None,138,None,None,None,None,None,None), | ||||||
| 	(10,172,"AdwPreferencesGroup","reset_bluetooth_details",121,None,None,None,None,None), | 	(10,172,"AdwPreferencesGroup","reset_bluetooth_details",121,None,None,None,None,None,None), | ||||||
| 	(10,173,"AdwComboRow","reset_bluetooth_adapter",172,None,None,None,None,None), | 	(10,173,"AdwComboRow","reset_bluetooth_adapter",172,None,None,None,None,None,None), | ||||||
| 	(10,174,"AdwActionRow","reset_visibility",172,None,None,None,1,None), | 	(10,174,"AdwActionRow","reset_visibility",172,None,None,None,1,None,None), | ||||||
| 	(10,175,"GtkImage",None,174,None,None,None,None,None), | 	(10,175,"GtkImage",None,174,None,None,None,None,None,None), | ||||||
| 	(10,204,"AdwPreferencesGroup","reset_bluetooth_available_devices",121,None,None,None,2,None), | 	(10,204,"AdwPreferencesGroup","reset_bluetooth_available_devices",121,None,None,None,2,None,None), | ||||||
| 	(10,206,"AdwPreferencesGroup","reset_bluetooth_connected_devices",121,None,None,None,1,None), | 	(10,206,"AdwPreferencesGroup","reset_bluetooth_connected_devices",121,None,None,None,1,None,None), | ||||||
| 	(10,207,"GtkButton","reset_bluetooth_refresh_button",204,None,None,None,-1,None), | 	(10,207,"GtkButton","reset_bluetooth_refresh_button",204,None,None,None,-1,None,None), | ||||||
| 	(10,208,"AdwPreferencesGroup",None,153,None,None,None,None,None), | 	(10,208,"AdwPreferencesGroup",None,153,None,None,None,None,None,None), | ||||||
| 	(10,209,"AdwPreferencesGroup",None,153,None,None,None,1,None), | 	(10,209,"AdwPreferencesGroup",None,153,None,None,None,1,None,None), | ||||||
| 	(10,210,"AdwSwitchRow","reset_bluetooth_pairable_switch",209,None,None,None,-1,None), | 	(10,210,"AdwSwitchRow","reset_bluetooth_pairable_switch",209,None,None,None,-1,None,None), | ||||||
| 	(10,211,"AdwSwitchRow","reset_bluetooth_discoverable_switch",209,None,None,None,-1,None), | 	(10,211,"AdwSwitchRow","reset_bluetooth_discoverable_switch",209,None,None,None,-1,None,None), | ||||||
| 	(10,212,"AdwActionRow","reset_bluetooth_main_tab",208,None,None,None,None,None), | 	(10,212,"AdwActionRow","reset_bluetooth_main_tab",208,None,None,None,None,None,None), | ||||||
| 	(10,213,"GtkImage",None,212,None,None,None,None,None), | 	(10,213,"GtkImage",None,212,None,None,None,None,None,None), | ||||||
| 	(10,214,"resetError","error",1,None,None,None,2,None), | 	(10,214,"resetError","error",1,None,None,None,2,None,None), | ||||||
| 	(11,1,"AdwActionRow","resetBluetoothEntry",None,None,None,None,None,None), | 	(11,1,"AdwActionRow","resetBluetoothEntry",None,None,None,None,None,None,None), | ||||||
| 	(12,11,"GtkBox","resetAudioInput",None,None,None,None,None,None), | 	(12,11,"GtkBox","resetAudioInput",None,None,None,None,None,None,None), | ||||||
| 	(12,12,"GtkLabel",None,11,None,None,None,None,None), | 	(12,12,"GtkLabel",None,11,None,None,None,None,None,None), | ||||||
| 	(12,13,"AdwNavigationView",None,11,None,None,None,1,None), | 	(12,13,"AdwNavigationView",None,11,None,None,None,1,None,None), | ||||||
| 	(12,14,"AdwNavigationPage",None,13,None,None,None,None,None), | 	(12,14,"AdwNavigationPage",None,13,None,None,None,None,None,None), | ||||||
| 	(12,15,"GtkBox",None,14,None,None,None,None,None), | 	(12,15,"GtkBox",None,14,None,None,None,None,None,None), | ||||||
| 	(12,29,"GtkSeparator",None,15,None,None,None,3,None), | 	(12,29,"GtkSeparator",None,15,None,None,None,3,None,None), | ||||||
| 	(12,30,"GtkBox","reset_output_streams",15,None,None,None,4,None), | 	(12,30,"GtkBox","reset_output_streams",15,None,None,None,4,None,None), | ||||||
| 	(12,31,"GtkLabel",None,30,None,None,None,None,None), | 	(12,31,"GtkLabel",None,30,None,None,None,None,None,None), | ||||||
| 	(12,34,"AdwNavigationPage",None,13,None,None,None,1,None), | 	(12,34,"AdwNavigationPage",None,13,None,None,None,1,None,None), | ||||||
| 	(12,42,"GtkBox",None,34,None,None,None,1,None), | 	(12,42,"GtkBox",None,34,None,None,None,1,None,None), | ||||||
| 	(12,49,"GtkBox","reset_sources",42,None,None,None,2,None), | 	(12,49,"GtkBox","reset_sources",42,None,None,None,2,None,None), | ||||||
| 	(12,50,"GtkLabel",None,49,None,None,None,None,None), | 	(12,50,"GtkLabel",None,49,None,None,None,None,None,None), | ||||||
| 	(12,52,"AdwNavigationPage",None,13,None,None,None,2,None), | 	(12,52,"AdwNavigationPage",None,13,None,None,None,2,None,None), | ||||||
| 	(12,53,"GtkBox",None,52,None,None,None,None,None), | 	(12,53,"GtkBox",None,52,None,None,None,None,None,None), | ||||||
| 	(12,59,"GtkLabel",None,53,None,None,None,2,None), | 	(12,59,"GtkLabel",None,53,None,None,None,2,None,None), | ||||||
| 	(12,66,"AdwPreferencesGroup","reset_cards",53,None,None,None,3,None), | 	(12,66,"AdwPreferencesGroup","reset_cards",53,None,None,None,3,None,None), | ||||||
| 	(12,67,"AdwPreferencesGroup",None,15,None,None,None,None,None), | 	(12,67,"AdwPreferencesGroup",None,15,None,None,None,None,None,None), | ||||||
| 	(12,69,"AdwActionRow","reset_source_row",67,None,None,None,None,None), | 	(12,69,"AdwActionRow","reset_source_row",67,None,None,None,None,None,None), | ||||||
| 	(12,70,"AdwActionRow","reset_cards_row",67,None,None,None,1,None), | 	(12,70,"AdwActionRow","reset_cards_row",67,None,None,None,1,None,None), | ||||||
| 	(12,71,"GtkImage",None,69,None,None,None,-1,None), | 	(12,71,"GtkImage",None,69,None,None,None,-1,None,None), | ||||||
| 	(12,72,"GtkImage",None,70,None,None,None,None,None), | 	(12,72,"GtkImage",None,70,None,None,None,None,None,None), | ||||||
| 	(12,73,"AdwComboRow","reset_source_dropdown",67,None,None,None,2,None), | 	(12,73,"AdwComboRow","reset_source_dropdown",67,None,None,None,2,None,None), | ||||||
| 	(12,74,"AdwPreferencesGroup",None,42,None,None,None,None,None), | 	(12,74,"AdwPreferencesGroup",None,42,None,None,None,None,None,None), | ||||||
| 	(12,75,"AdwActionRow","reset_output_stream_button",74,None,None,None,None,None), | 	(12,75,"AdwActionRow","reset_output_stream_button",74,None,None,None,None,None,None), | ||||||
| 	(12,76,"GtkImage",None,75,None,None,None,None,None), | 	(12,76,"GtkImage",None,75,None,None,None,None,None,None), | ||||||
| 	(12,77,"AdwPreferencesGroup",None,53,None,None,None,None,None), | 	(12,77,"AdwPreferencesGroup",None,53,None,None,None,None,None,None), | ||||||
| 	(12,78,"AdwActionRow","reset_input_cards_back_button",77,None,None,None,None,None), | 	(12,78,"AdwActionRow","reset_input_cards_back_button",77,None,None,None,None,None,None), | ||||||
| 	(12,79,"GtkImage",None,78,None,None,None,None,None), | 	(12,79,"GtkImage",None,78,None,None,None,None,None,None), | ||||||
| 	(12,80,"AdwActionRow",None,67,None,None,None,3,None), | 	(12,80,"AdwActionRow",None,67,None,None,None,3,None,None), | ||||||
| 	(12,81,"GtkBox",None,80,None,None,None,-1,None), | 	(12,81,"GtkBox",None,80,None,None,None,-1,None,None), | ||||||
| 	(12,82,"GtkButton","reset_source_mute",81,None,None,None,None,None), | 	(12,82,"GtkButton","reset_source_mute",81,None,None,None,None,None,None), | ||||||
| 	(12,83,"GtkScale","reset_volume_slider",81,None,None,None,1,None), | 	(12,83,"GtkScale","reset_volume_slider",81,None,None,None,1,None,None), | ||||||
| 	(12,84,"GtkAdjustment",None,83,None,None,None,None,None), | 	(12,84,"GtkAdjustment",None,83,None,None,None,None,None,None), | ||||||
| 	(12,85,"GtkLabel","reset_volume_percentage",81,None,None,None,2,None), | 	(12,85,"GtkLabel","reset_volume_percentage",81,None,None,None,2,None,None), | ||||||
| 	(12,86,"resetError","error",11,None,None,None,-1,None), | 	(12,86,"resetError","error",11,None,None,None,-1,None,None), | ||||||
| 	(13,22,"AdwPreferencesGroup","resetOutputStreamEntry",None,None,None,None,None,None), | 	(13,22,"AdwPreferencesGroup","resetOutputStreamEntry",None,None,None,None,None,None,None), | ||||||
| 	(13,23,"AdwComboRow","reset_source_selection",22,None,None,None,None,None), | 	(13,23,"AdwComboRow","reset_source_selection",22,None,None,None,None,None,None), | ||||||
| 	(13,26,"AdwActionRow",None,22,None,None,None,1,None), | 	(13,26,"AdwActionRow",None,22,None,None,None,1,None,None), | ||||||
| 	(13,27,"GtkBox",None,26,None,None,None,None,None), | 	(13,27,"GtkBox",None,26,None,None,None,None,None,None), | ||||||
| 	(13,36,"GtkBox",None,27,None,None,None,None,None), | 	(13,36,"GtkBox",None,27,None,None,None,None,None,None), | ||||||
| 	(13,37,"GtkButton","reset_source_mute",36,None,None,None,None,None), | 	(13,37,"GtkButton","reset_source_mute",36,None,None,None,None,None,None), | ||||||
| 	(13,38,"GtkScale","reset_volume_slider",36,None,None,None,1,None), | 	(13,38,"GtkScale","reset_volume_slider",36,None,None,None,1,None,None), | ||||||
| 	(13,39,"GtkAdjustment",None,38,None,None,None,None,None), | 	(13,39,"GtkAdjustment",None,38,None,None,None,None,None,None), | ||||||
| 	(13,40,"GtkLabel","reset_volume_percentage",36,None,None,None,2,None), | 	(13,40,"GtkLabel","reset_volume_percentage",36,None,None,None,2,None,None), | ||||||
| 	(13,42,"GtkSeparator",None,22,None,None,None,-1,None), | 	(13,42,"GtkSeparator",None,22,None,None,None,-1,None,None), | ||||||
| 	(14,3,"GtkBox","resetSettingBox",None,None,None,None,-1,None), | 	(14,3,"GtkBox","resetSettingBox",None,None,None,None,-1,None,None), | ||||||
| 	(15,1,"GtkListBoxRow","resetListBoxRow",None,None,None,None,None,None), | 	(15,1,"GtkListBoxRow","resetListBoxRow",None,None,None,None,None,None,None), | ||||||
| 	(16,6,"GtkPopover","resetPopup",None,None,None,None,-1,None), | 	(16,6,"GtkPopover","resetPopup",None,None,None,None,-1,None,None), | ||||||
| 	(16,7,"GtkBox",None,6,None,None,None,None,None), | 	(16,7,"GtkBox",None,6,None,None,None,None,None,None), | ||||||
| 	(16,10,"GtkBox",None,7,None,None,None,1,None), | 	(16,10,"GtkBox",None,7,None,None,None,1,None,None), | ||||||
| 	(16,11,"GtkPasswordEntry","reset_popup_entry",10,None,None,None,None,None), | 	(16,11,"GtkPasswordEntry","reset_popup_entry",10,None,None,None,None,None,None), | ||||||
| 	(16,12,"GtkButton","reset_popup_button",10,None,None,None,1,None), | 	(16,12,"GtkButton","reset_popup_button",10,None,None,None,1,None,None), | ||||||
| 	(16,13,"GtkLabel","reset_popup_label",7,None,None,None,None,None), | 	(16,13,"GtkLabel","reset_popup_label",7,None,None,None,None,None,None), | ||||||
| 	(17,5,"AdwActionRow","resetSavedWifiEntry",None,None,None,None,None,None), | 	(17,5,"AdwActionRow","resetSavedWifiEntry",None,None,None,None,None,None,None), | ||||||
| 	(18,13,"AdwPreferencesGroup","resetSinkEntry",None,None,None,None,None,None), | 	(18,13,"AdwPreferencesGroup","resetSinkEntry",None,None,None,None,None,None,None), | ||||||
| 	(18,14,"AdwActionRow","reset_sink_name",13,None,None,None,None,None), | 	(18,14,"AdwActionRow","reset_sink_name",13,None,None,None,None,None,None), | ||||||
| 	(18,15,"GtkCheckButton","reset_selected_sink",14,None,None,None,None,None), | 	(18,15,"GtkCheckButton","reset_selected_sink",14,None,None,None,None,None,None), | ||||||
| 	(18,16,"AdwActionRow",None,13,None,None,None,1,None), | 	(18,16,"AdwActionRow",None,13,None,None,None,1,None,None), | ||||||
| 	(18,17,"GtkBox",None,16,None,None,None,None,None), | 	(18,17,"GtkBox",None,16,None,None,None,None,None,None), | ||||||
| 	(18,18,"GtkBox",None,17,None,None,None,None,None), | 	(18,18,"GtkBox",None,17,None,None,None,None,None,None), | ||||||
| 	(18,19,"GtkButton","reset_sink_mute",18,None,None,None,None,None), | 	(18,19,"GtkButton","reset_sink_mute",18,None,None,None,None,None,None), | ||||||
| 	(18,20,"GtkScale","reset_volume_slider",18,None,None,None,1,None), | 	(18,20,"GtkScale","reset_volume_slider",18,None,None,None,1,None,None), | ||||||
| 	(18,21,"GtkAdjustment",None,20,None,None,None,None,None), | 	(18,21,"GtkAdjustment",None,20,None,None,None,None,None,None), | ||||||
| 	(18,22,"GtkLabel","reset_volume_percentage",18,None,None,None,2,None), | 	(18,22,"GtkLabel","reset_volume_percentage",18,None,None,None,2,None,None), | ||||||
| 	(18,24,"GtkSeparator",None,13,None,None,None,2,None), | 	(18,24,"GtkSeparator",None,13,None,None,None,2,None,None), | ||||||
| 	(19,13,"AdwPreferencesGroup","resetSourceEntry",None,None,None,None,None,None), | 	(19,13,"AdwPreferencesGroup","resetSourceEntry",None,None,None,None,None,None,None), | ||||||
| 	(19,14,"AdwActionRow","reset_source_name",13,None,None,None,None,None), | 	(19,14,"AdwActionRow","reset_source_name",13,None,None,None,None,None,None), | ||||||
| 	(19,15,"GtkCheckButton","reset_selected_source",14,None,None,None,None,None), | 	(19,15,"GtkCheckButton","reset_selected_source",14,None,None,None,None,None,None), | ||||||
| 	(19,16,"AdwActionRow",None,13,None,None,None,1,None), | 	(19,16,"AdwActionRow",None,13,None,None,None,1,None,None), | ||||||
| 	(19,17,"GtkBox",None,16,None,None,None,None,None), | 	(19,17,"GtkBox",None,16,None,None,None,None,None,None), | ||||||
| 	(19,18,"GtkBox",None,17,None,None,None,None,None), | 	(19,18,"GtkBox",None,17,None,None,None,None,None,None), | ||||||
| 	(19,19,"GtkButton","reset_source_mute",18,None,None,None,None,None), | 	(19,19,"GtkButton","reset_source_mute",18,None,None,None,None,None,None), | ||||||
| 	(19,20,"GtkScale","reset_volume_slider",18,None,None,None,1,None), | 	(19,20,"GtkScale","reset_volume_slider",18,None,None,None,1,None,None), | ||||||
| 	(19,21,"GtkAdjustment",None,20,None,None,None,None,None), | 	(19,21,"GtkAdjustment",None,20,None,None,None,None,None,None), | ||||||
| 	(19,22,"GtkLabel","reset_volume_percentage",18,None,None,None,2,None), | 	(19,22,"GtkLabel","reset_volume_percentage",18,None,None,None,2,None,None), | ||||||
| 	(19,24,"GtkSeparator",None,13,None,None,None,2,None), | 	(19,24,"GtkSeparator",None,13,None,None,None,2,None,None), | ||||||
| 	(20,2,"AdwComboRow","reset_card_entry",None,None,None,None,None,None), | 	(20,2,"AdwComboRow","reset_card_entry",None,None,None,None,None,None,None), | ||||||
| 	(21,1,"AdwNavigationPage","resetWifiOptions",None,None,None,None,-1,None), | 	(21,1,"AdwNavigationPage","resetWifiOptions",None,None,None,None,-1,None,None), | ||||||
| 	(21,52,"GtkBox",None,1,None,None,None,-1,None), | 	(21,52,"GtkBox",None,1,None,None,None,-1,None,None), | ||||||
| 	(21,53,"GtkNotebook",None,52,None,None,None,1,None), | 	(21,53,"GtkNotebook",None,52,None,None,None,1,None,None), | ||||||
| 	(21,54,"GtkBox",None,53,None,None,None,None,None), | 	(21,54,"GtkBox",None,53,None,None,None,None,None,None), | ||||||
| 	(21,55,"AdwPreferencesGroup",None,54,None,None,None,None,None), | 	(21,55,"AdwPreferencesGroup",None,54,None,None,None,None,None,None), | ||||||
| 	(21,56,"AdwActionRow","reset_wifi_name",55,None,None,None,None,None), | 	(21,56,"AdwActionRow","reset_wifi_name",55,None,None,None,None,None,None), | ||||||
| 	(21,57,"AdwActionRow","reset_wifi_mac",55,None,None,None,1,None), | 	(21,57,"AdwActionRow","reset_wifi_mac",55,None,None,None,1,None,None), | ||||||
| 	(21,58,"AdwActionRow","reset_wifi_link_speed",55,None,None,None,2,None), | 	(21,58,"AdwActionRow","reset_wifi_link_speed",55,None,None,None,2,None,None), | ||||||
| 	(21,59,"AdwActionRow","reset_wifi_ip4_addr",55,None,None,None,3,None), | 	(21,59,"AdwActionRow","reset_wifi_ip4_addr",55,None,None,None,3,None,None), | ||||||
| 	(21,60,"AdwActionRow","reset_wifi_ip6_addr",55,None,None,None,4,None), | 	(21,60,"AdwActionRow","reset_wifi_ip6_addr",55,None,None,None,4,None,None), | ||||||
| 	(21,61,"AdwActionRow","reset_wifi_gateway",55,None,None,None,5,None), | 	(21,61,"AdwActionRow","reset_wifi_gateway",55,None,None,None,5,None,None), | ||||||
| 	(21,62,"AdwActionRow","reset_wifi_dns",55,None,None,None,6,None), | 	(21,62,"AdwActionRow","reset_wifi_dns",55,None,None,None,6,None,None), | ||||||
| 	(21,63,"AdwActionRow","reset_wifi_last_used",55,None,None,None,7,None), | 	(21,63,"AdwActionRow","reset_wifi_last_used",55,None,None,None,7,None,None), | ||||||
| 	(21,64,"AdwSwitchRow","reset_wifi_auto_connect",55,None,None,None,8,None), | 	(21,64,"AdwSwitchRow","reset_wifi_auto_connect",55,None,None,None,8,None,None), | ||||||
| 	(21,65,"AdwSwitchRow","reset_wifi_metered",55,None,None,None,9,None), | 	(21,65,"AdwSwitchRow","reset_wifi_metered",55,None,None,None,9,None,None), | ||||||
| 	(21,66,"GtkLabel",None,53,None,"tab",None,1,None), | 	(21,66,"GtkLabel",None,53,None,"tab",None,1,None,None), | ||||||
| 	(21,67,"GtkBox",None,53,None,None,None,2,None), | 	(21,67,"GtkBox",None,53,None,None,None,2,None,None), | ||||||
| 	(21,68,"AdwPreferencesGroup",None,67,None,None,None,None,None), | 	(21,68,"AdwPreferencesGroup",None,67,None,None,None,None,None,None), | ||||||
| 	(21,69,"AdwComboRow","reset_ip4_method",68,None,None,None,None,None), | 	(21,69,"AdwComboRow","reset_ip4_method",68,None,None,None,None,None,None), | ||||||
| 	(21,70,"GtkStringList",None,69,None,None,None,None,None), | 	(21,70,"GtkStringList",None,69,None,None,None,None,None,None), | ||||||
| 	(21,71,"AdwEntryRow","reset_ip4_dns",68,None,None,None,1,None), | 	(21,71,"AdwEntryRow","reset_ip4_dns",68,None,None,None,1,None,None), | ||||||
| 	(21,73,"AdwPreferencesGroup","reset_ip4_address_group",67,None,None,None,1,None), | 	(21,73,"AdwPreferencesGroup","reset_ip4_address_group",67,None,None,None,1,None,None), | ||||||
| 	(21,74,"AdwPreferencesGroup","reset_ip4_routes_group",67,None,None,None,2,None), | 	(21,74,"AdwPreferencesGroup","reset_ip4_routes_group",67,None,None,None,2,None,None), | ||||||
| 	(21,75,"GtkBox",None,74,None,None,None,None,None), | 	(21,75,"GtkBox",None,74,None,None,None,None,None,None), | ||||||
| 	(21,76,"GtkLabel",None,75,None,None,None,None,None), | 	(21,76,"GtkLabel",None,75,None,None,None,None,None,None), | ||||||
| 	(21,77,"GtkSwitch",None,75,None,None,None,1,None), | 	(21,77,"GtkSwitch",None,75,None,None,None,1,None,None), | ||||||
| 	(21,78,"GtkLabel",None,53,None,"tab",None,3,None), | 	(21,78,"GtkLabel",None,53,None,"tab",None,3,None,None), | ||||||
| 	(21,79,"GtkBox",None,53,None,None,None,4,None), | 	(21,79,"GtkBox",None,53,None,None,None,4,None,None), | ||||||
| 	(21,80,"AdwPreferencesGroup",None,79,None,None,None,None,None), | 	(21,80,"AdwPreferencesGroup",None,79,None,None,None,None,None,None), | ||||||
| 	(21,81,"AdwComboRow","reset_ip6_method",80,None,None,None,None,None), | 	(21,81,"AdwComboRow","reset_ip6_method",80,None,None,None,None,None,None), | ||||||
| 	(21,82,"GtkStringList",None,81,None,None,None,None,None), | 	(21,82,"GtkStringList",None,81,None,None,None,None,None,None), | ||||||
| 	(21,83,"AdwEntryRow","reset_ip6_dns",80,None,None,None,1,None), | 	(21,83,"AdwEntryRow","reset_ip6_dns",80,None,None,None,1,None,None), | ||||||
| 	(21,84,"AdwEntryRow","reset_ip6_gateway",80,None,None,None,2,None), | 	(21,84,"AdwEntryRow","reset_ip6_gateway",80,None,None,None,2,None,None), | ||||||
| 	(21,85,"AdwPreferencesGroup","reset_ip6_address_group",79,None,None,None,1,None), | 	(21,85,"AdwPreferencesGroup","reset_ip6_address_group",79,None,None,None,1,None,None), | ||||||
| 	(21,86,"AdwPreferencesGroup","reset_ip6_routes_group",79,None,None,None,2,None), | 	(21,86,"AdwPreferencesGroup","reset_ip6_routes_group",79,None,None,None,2,None,None), | ||||||
| 	(21,87,"GtkBox",None,86,None,None,None,None,None), | 	(21,87,"GtkBox",None,86,None,None,None,None,None,None), | ||||||
| 	(21,88,"GtkLabel",None,87,None,None,None,None,None), | 	(21,88,"GtkLabel",None,87,None,None,None,None,None,None), | ||||||
| 	(21,89,"GtkSwitch",None,87,None,None,None,1,None), | 	(21,89,"GtkSwitch",None,87,None,None,None,1,None,None), | ||||||
| 	(21,90,"GtkLabel",None,53,None,"tab",None,5,None), | 	(21,90,"GtkLabel",None,53,None,"tab",None,5,None,None), | ||||||
| 	(21,91,"GtkBox",None,53,None,None,None,6,None), | 	(21,91,"GtkBox",None,53,None,None,None,6,None,None), | ||||||
| 	(21,92,"AdwPreferencesGroup",None,91,None,None,None,None,None), | 	(21,92,"AdwPreferencesGroup",None,91,None,None,None,None,None,None), | ||||||
| 	(21,93,"AdwComboRow","reset_wifi_security_dropdown",92,None,None,None,None,None), | 	(21,93,"AdwComboRow","reset_wifi_security_dropdown",92,None,None,None,None,None,None), | ||||||
| 	(21,96,"AdwPasswordEntryRow","reset_wifi_password",92,None,None,None,1,None), | 	(21,96,"AdwPasswordEntryRow","reset_wifi_password",92,None,None,None,1,None,None), | ||||||
| 	(21,97,"GtkLabel",None,53,None,"tab",None,7,None), | 	(21,97,"GtkLabel",None,53,None,"tab",None,7,None,None), | ||||||
| 	(21,99,"GtkButton","reset_ip4_address_add_button",73,None,None,None,-1,None), | 	(21,99,"GtkButton","reset_ip4_address_add_button",73,None,None,None,-1,None,None), | ||||||
| 	(21,100,"GtkButton","reset_ip4_route_add_button",75,None,None,None,2,None), | 	(21,100,"GtkButton","reset_ip4_route_add_button",75,None,None,None,2,None,None), | ||||||
| 	(21,101,"GtkButton","reset_ip6_address_add_button",85,None,None,None,-1,None), | 	(21,101,"GtkButton","reset_ip6_address_add_button",85,None,None,None,-1,None,None), | ||||||
| 	(21,102,"GtkButton","reset_ip6_route_add_button",87,None,None,None,2,None), | 	(21,102,"GtkButton","reset_ip6_route_add_button",87,None,None,None,2,None,None), | ||||||
| 	(21,103,"GtkStringList",None,93,None,None,None,-1,None), | 	(21,103,"GtkStringList",None,93,None,None,None,-1,None,None), | ||||||
| 	(21,104,"GtkBox",None,52,None,None,None,2,None), | 	(21,104,"GtkBox",None,52,None,None,None,2,None,None), | ||||||
| 	(21,105,"GtkButton","wifi_options_apply_button",104,None,None,None,1,None), | 	(21,105,"GtkButton","wifi_options_apply_button",104,None,None,None,1,None,None), | ||||||
| 	(21,106,"GtkLabel","wifi_options_error_msg",104,None,None,None,None,None), | 	(21,106,"GtkLabel","wifi_options_error_msg",104,None,None,None,None,None,None), | ||||||
| 	(21,107,"AdwEntryRow","reset_ip4_gateway",73,None,None,None,None,None), | 	(21,107,"AdwEntryRow","reset_ip4_gateway",73,None,None,None,None,None,None), | ||||||
| 	(21,108,"AdwPreferencesGroup",None,52,None,None,None,None,None), | 	(21,108,"AdwPreferencesGroup",None,52,None,None,None,None,None,None), | ||||||
| 	(21,109,"AdwActionRow","reset_available_networks",108,None,None,None,None,None), | 	(21,109,"AdwActionRow","reset_available_networks",108,None,None,None,None,None,None), | ||||||
| 	(21,110,"GtkImage",None,109,None,None,None,None,None), | 	(21,110,"GtkImage",None,109,None,None,None,None,None,None), | ||||||
| 	(22,1,"GtkBox","resetWifiAddressEntry",None,None,None,None,-1,None), | 	(22,1,"GtkBox","resetWifiAddressEntry",None,None,None,None,-1,None,None), | ||||||
| 	(22,5,"GtkButton","reset_address_remove",1,None,None,None,1,None), | 	(22,5,"GtkButton","reset_address_remove",1,None,None,None,1,None,None), | ||||||
| 	(22,9,"AdwPreferencesGroup","reset_address_group",1,None,None,None,None,None), | 	(22,9,"AdwPreferencesGroup","reset_address_group",1,None,None,None,None,None,None), | ||||||
| 	(22,10,"AdwExpanderRow","reset_address_row",9,None,None,None,None,None), | 	(22,10,"AdwExpanderRow","reset_address_row",9,None,None,None,None,None,None), | ||||||
| 	(22,11,"GtkBox",None,10,None,None,None,None,None), | 	(22,11,"GtkBox",None,10,None,None,None,None,None,None), | ||||||
| 	(22,12,"AdwEntryRow","reset_address_address",11,None,None,None,None,None), | 	(22,12,"AdwEntryRow","reset_address_address",11,None,None,None,None,None,None), | ||||||
| 	(22,13,"AdwEntryRow","reset_address_prefix",11,None,None,None,1,None), | 	(22,13,"AdwEntryRow","reset_address_prefix",11,None,None,None,1,None,None), | ||||||
| 	(23,1,"GtkBox","resetWifiRouteEntry",None,None,None,None,None,None), | 	(23,1,"GtkBox","resetWifiRouteEntry",None,None,None,None,None,None,None), | ||||||
| 	(23,5,"GtkButton","reset_route_remove",1,None,None,None,1,None), | 	(23,5,"GtkButton","reset_route_remove",1,None,None,None,1,None,None), | ||||||
| 	(23,12,"AdwPreferencesGroup","reset_route_group",1,None,None,None,None,None), | 	(23,12,"AdwPreferencesGroup","reset_route_group",1,None,None,None,None,None,None), | ||||||
| 	(23,13,"AdwExpanderRow","reset_route_row",12,None,None,None,None,None), | 	(23,13,"AdwExpanderRow","reset_route_row",12,None,None,None,None,None,None), | ||||||
| 	(23,14,"GtkBox",None,13,None,None,None,None,None), | 	(23,14,"GtkBox",None,13,None,None,None,None,None,None), | ||||||
| 	(23,15,"AdwEntryRow","reset_route_address",14,None,None,None,None,None), | 	(23,15,"AdwEntryRow","reset_route_address",14,None,None,None,None,None,None), | ||||||
| 	(23,16,"AdwEntryRow","reset_route_prefix",14,None,None,None,1,None), | 	(23,16,"AdwEntryRow","reset_route_prefix",14,None,None,None,1,None,None), | ||||||
| 	(23,17,"GtkBox",None,13,None,None,None,1,None), | 	(23,17,"GtkBox",None,13,None,None,None,1,None,None), | ||||||
| 	(23,18,"AdwEntryRow","reset_route_gateway",17,None,None,None,None,None), | 	(23,18,"AdwEntryRow","reset_route_gateway",17,None,None,None,None,None,None), | ||||||
| 	(23,19,"AdwEntryRow","reset_route_metric",17,None,None,None,1,None), | 	(23,19,"AdwEntryRow","reset_route_metric",17,None,None,None,1,None,None), | ||||||
| 	(24,1,"GtkShortcutsWindow","help_overlay",None,None,None,None,-1,None), | 	(24,1,"GtkShortcutsWindow","help_overlay",None,None,None,None,-1,None,None), | ||||||
| 	(24,2,"GtkShortcutsSection",None,1,None,None,None,-1,None), | 	(24,2,"GtkShortcutsSection",None,1,None,None,None,-1,None,None), | ||||||
| 	(24,3,"GtkShortcutsGroup",None,2,None,None,None,-1,None), | 	(24,3,"GtkShortcutsGroup",None,2,None,None,None,-1,None,None), | ||||||
| 	(24,4,"GtkShortcutsShortcut",None,3,None,None,None,-1,None), | 	(24,4,"GtkShortcutsShortcut",None,3,None,None,None,-1,None,None), | ||||||
| 	(24,5,"GtkShortcutsShortcut",None,3,None,None,None,-1,None), | 	(24,5,"GtkShortcutsShortcut",None,3,None,None,None,-1,None,None), | ||||||
| 	(24,6,"GtkShortcutsShortcut",None,3,None,None,None,2,None), | 	(24,6,"GtkShortcutsShortcut",None,3,None,None,None,2,None,None), | ||||||
| 	(25,1,"GtkPopover","resetError",None,None,None,None,None,None), | 	(25,1,"GtkPopover","resetError",None,None,None,None,None,None,None), | ||||||
| 	(25,2,"GtkBox",None,1,None,None,None,None,None), | 	(25,2,"GtkBox",None,1,None,None,None,None,None,None), | ||||||
| 	(25,3,"GtkLabel","reset_error_label",2,None,None,None,None,None), | 	(25,3,"GtkLabel","reset_error_label",2,None,None,None,None,None,None), | ||||||
| 	(25,5,"GtkButton","reset_error_button",2,None,None,None,2,None) | 	(25,5,"GtkButton","reset_error_button",2,None,None,None,2,None,None) | ||||||
|   </object> |   </object> | ||||||
|   <object_property> |   <object_property> | ||||||
| 	(3,1,"GtkWidget","height-request","200",None,None,None,None,None,None,None,None,None), | 	(3,1,"GtkWidget","height-request","200",None,None,None,None,None,None,None,None,None), | ||||||
|  | @ -458,10 +458,10 @@ | ||||||
| 	(8,53,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None), | 	(8,53,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None), | ||||||
| 	(8,53,"GtkWidget","margin-bottom","10",None,None,None,None,None,None,None,None,None), | 	(8,53,"GtkWidget","margin-bottom","10",None,None,None,None,None,None,None,None,None), | ||||||
| 	(8,53,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), | 	(8,53,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), | ||||||
| 	(8,55,"AdwNavigationPage","tag","output",None,None,None,None,None,None,None,None,None), | 	(8,55,"AdwNavigationPage","tag","audiostreams",None,None,None,None,None,None,None,None,None), | ||||||
| 	(8,55,"AdwNavigationPage","title","output",None,None,None,None,None,None,None,None,None), | 	(8,55,"AdwNavigationPage","title","output",None,None,None,None,None,None,None,None,None), | ||||||
| 	(8,56,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), | 	(8,56,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), | ||||||
| 	(8,71,"AdwNavigationPage","tag","outputDevices",None,None,None,None,None,None,None,None,None), | 	(8,71,"AdwNavigationPage","tag","devices",None,None,None,None,None,None,None,None,None), | ||||||
| 	(8,71,"AdwNavigationPage","title","outputDevices",None,None,None,None,None,None,None,None,None), | 	(8,71,"AdwNavigationPage","title","outputDevices",None,None,None,None,None,None,None,None,None), | ||||||
| 	(8,72,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), | 	(8,72,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), | ||||||
| 	(8,85,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), | 	(8,85,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), | ||||||
|  | @ -640,7 +640,7 @@ | ||||||
| 	(12,12,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None), | 	(12,12,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None), | ||||||
| 	(12,12,"GtkWidget","margin-bottom","10",None,None,None,None,None,None,None,None,None), | 	(12,12,"GtkWidget","margin-bottom","10",None,None,None,None,None,None,None,None,None), | ||||||
| 	(12,12,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), | 	(12,12,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), | ||||||
| 	(12,14,"AdwNavigationPage","tag","output",None,None,None,None,None,None,None,None,None), | 	(12,14,"AdwNavigationPage","tag","audiostreams",None,None,None,None,None,None,None,None,None), | ||||||
| 	(12,14,"AdwNavigationPage","title","output",None,None,None,None,None,None,None,None,None), | 	(12,14,"AdwNavigationPage","title","output",None,None,None,None,None,None,None,None,None), | ||||||
| 	(12,15,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), | 	(12,15,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), | ||||||
| 	(12,30,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), | 	(12,30,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), | ||||||
|  | @ -649,7 +649,7 @@ | ||||||
| 	(12,31,"GtkWidget","css-classes","heading",None,None,None,None,None,None,None,None,None), | 	(12,31,"GtkWidget","css-classes","heading",None,None,None,None,None,None,None,None,None), | ||||||
| 	(12,31,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None), | 	(12,31,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None), | ||||||
| 	(12,31,"GtkWidget","margin-bottom","15",None,None,None,None,None,None,None,None,None), | 	(12,31,"GtkWidget","margin-bottom","15",None,None,None,None,None,None,None,None,None), | ||||||
| 	(12,34,"AdwNavigationPage","tag","sources",None,None,None,None,None,None,None,None,None), | 	(12,34,"AdwNavigationPage","tag","devices",None,None,None,None,None,None,None,None,None), | ||||||
| 	(12,34,"AdwNavigationPage","title","sources",None,None,None,None,None,None,None,None,None), | 	(12,34,"AdwNavigationPage","title","sources",None,None,None,None,None,None,None,None,None), | ||||||
| 	(12,42,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), | 	(12,42,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), | ||||||
| 	(12,49,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), | 	(12,49,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.6"/> |   <requires lib="gtk" version="4.6"/> | ||||||
|   <requires lib="libadwaita" version="1.4"/> |   <requires lib="libadwaita" version="1.4"/> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.12"/> |   <requires lib="gtk" version="4.12"/> | ||||||
|   <requires lib="libadwaita" version="1.2"/> |   <requires lib="libadwaita" version="1.2"/> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="libadwaita" version="1.0"/> |   <requires lib="libadwaita" version="1.0"/> | ||||||
|   <template class="resetWifiEntry" parent="AdwActionRow"> |   <template class="resetWifiEntry" parent="AdwActionRow"> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.12"/> |   <requires lib="gtk" version="4.12"/> | ||||||
|   <requires lib="libadwaita" version="1.4"/> |   <requires lib="libadwaita" version="1.4"/> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <?xml version='1.0' encoding='UTF-8'?> | <?xml version='1.0' encoding='UTF-8'?> | ||||||
| <!-- Created with Cambalache 0.17.2 --> | <!-- Created with Cambalache 0.17.3 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk" version="4.12"/> |   <requires lib="gtk" version="4.12"/> | ||||||
|   <requires lib="libadwaita" version="1.2"/> |   <requires lib="libadwaita" version="1.2"/> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 GitHub
							GitHub