From 0c286af82cb13da9cbbb0994b9cc92bb852c0c17 Mon Sep 17 00:00:00 2001 From: Fabio Lenherr / DashieTM Date: Wed, 15 Nov 2023 14:50:00 +0100 Subject: [PATCH] feat: Add dropdowns --- src/components/input/outputStreamEntry.rs | 88 +++++++++++++++++- src/components/input/outputStreamEntryImpl.rs | 6 +- src/components/input/sourceBox.rs | 62 +++++++++++-- src/components/input/sourceBoxImpl.rs | 6 +- src/components/input/sourceEntry.rs | 8 +- src/components/output/inputStreamEntry.rs | 92 +++++++++++++++++-- src/components/output/inputStreamEntryImpl.rs | 1 + src/components/output/sinkBox.rs | 67 +++++++++++--- src/components/output/sinkBoxImpl.rs | 10 +- src/components/output/sinkEntry.rs | 5 +- src/components/output/sinkEntryImpl.rs | 2 +- src/resources/resetAudioOutput.ui | 2 +- src/resources/resetUI.cmb | 2 +- 13 files changed, 297 insertions(+), 54 deletions(-) diff --git a/src/components/input/outputStreamEntry.rs b/src/components/input/outputStreamEntry.rs index 4160f97..00a0d2a 100644 --- a/src/components/input/outputStreamEntry.rs +++ b/src/components/input/outputStreamEntry.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::sync::Arc; use std::time::Duration; @@ -8,10 +7,12 @@ use adw::prelude::{ButtonExt, RangeExt}; use dbus::blocking::Connection; use dbus::Error; use glib::subclass::types::ObjectSubclassIsExt; -use glib::{clone, Propagation}; +use glib::{clone, Cast, Propagation}; +use gtk::StringObject; use ReSet_Lib::audio::audio::OutputStream; use super::outputStreamEntryImpl; +use super::sourceBox::SourceBox; glib::wrapper! { pub struct OutputStreamEntry(ObjectSubclass) @@ -20,12 +21,13 @@ glib::wrapper! { } impl OutputStreamEntry { - pub fn new(stream: OutputStream) -> Self { + pub fn new(source_box: Arc, stream: OutputStream) -> Self { let obj: Self = Object::builder().build(); // TODO use event callback for progress bar -> this is the "im speaking" indicator // TODO map mute to callback // TODO map dropdown { + let box_imp = source_box.imp(); let imp = obj.imp(); let name = stream.application_name.clone() + ": " + stream.name.as_str(); imp.resetSourceName.set_text(name.as_str()); @@ -40,17 +42,75 @@ impl OutputStreamEntry { let fraction = (value / 655.36).round(); let percentage = (fraction).to_string() + "%"; imp.resetVolumePercentage.set_text(&percentage); - let stream = imp.stream.borrow(); + 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; set_outputstream_volume(value, index, channels); Propagation::Proceed }), ); + { + let mut list = box_imp.resetModelList.try_borrow(); + while list.is_err() { + list = box_imp.resetModelList.try_borrow(); + } + let list = list.unwrap(); + imp.resetSelectedSource.set_model(Some(&*list)); + let mut map = box_imp.resetSourceMap.try_borrow(); + while map.is_err() { + map = box_imp.resetSourceMap.try_borrow(); + } + let map = map.unwrap(); + let mut name = box_imp.resetDefaultSource.try_borrow(); + while name.is_err() { + name = box_imp.resetDefaultSource.try_borrow(); + } + let name = name.unwrap(); + let name = &name.alias; + let index = map.get(name); + if index.is_some() { + imp.resetSelectedSource.set_selected(index.unwrap().1); + } + } + imp.resetSelectedSource.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::().unwrap(); + let selected = selected.string().to_string(); + let mut source = box_imp.resetSourceMap.try_borrow(); + while source.is_err() { + source = box_imp.resetSourceMap.try_borrow(); + } + let source = source.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); + }), + ); imp.resetSourceMute .connect_clicked(clone!(@weak imp => move |_| { let stream = imp.stream.clone(); - let mut stream = stream.borrow_mut(); + 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; @@ -100,3 +160,21 @@ fn toggle_output_stream_mute(index: u32, muted: bool) -> bool { } res.unwrap().0 } + +fn set_source_of_output_stream(stream: u32, source: u32) -> bool { + let conn = Connection::new_session().unwrap(); + let proxy = conn.with_proxy( + "org.xetibo.ReSet", + "/org/xetibo/ReSet", + Duration::from_millis(1000), + ); + let res: Result<(bool,), Error> = proxy.method_call( + "org.xetibo.ReSet", + "SetSourceOfOutputStream", + (stream, source), + ); + if res.is_err() { + return false; + } + res.unwrap().0 +} diff --git a/src/components/input/outputStreamEntryImpl.rs b/src/components/input/outputStreamEntryImpl.rs index 88dccfe..59122e8 100644 --- a/src/components/input/outputStreamEntryImpl.rs +++ b/src/components/input/outputStreamEntryImpl.rs @@ -2,9 +2,9 @@ use std::cell::RefCell; use std::sync::Arc; use crate::components::input::outputStreamEntry; -use ReSet_Lib::audio::audio::OutputStream; use gtk::subclass::prelude::*; -use gtk::{glib, Button, CompositeTemplate, Label, ProgressBar, Scale, DropDown}; +use gtk::{glib, Button, CompositeTemplate, DropDown, Label, ProgressBar, Scale}; +use ReSet_Lib::audio::audio::OutputStream; #[allow(non_snake_case)] #[derive(Default, CompositeTemplate)] @@ -23,6 +23,7 @@ pub struct OutputStreamEntry { #[template_child] pub resetVolumeMeter: TemplateChild, pub stream: Arc>, + pub associatedSource: Arc>, } #[glib::object_subclass] @@ -45,4 +46,3 @@ impl BoxImpl for OutputStreamEntry {} impl ObjectImpl for OutputStreamEntry {} impl WidgetImpl for OutputStreamEntry {} - diff --git a/src/components/input/sourceBox.rs b/src/components/input/sourceBox.rs index 4fd1ff4..87773b3 100644 --- a/src/components/input/sourceBox.rs +++ b/src/components/input/sourceBox.rs @@ -11,13 +11,13 @@ use adw::prelude::{BoxExt, ButtonExt, ListBoxRowExt, RangeExt}; use dbus::blocking::Connection; use dbus::Error; use glib::subclass::prelude::ObjectSubclassIsExt; -use glib::{Propagation, Variant}; -use gtk::gio; +use glib::{clone, Cast, Propagation, Variant}; use gtk::prelude::ActionableExt; +use gtk::{gio, StringObject}; use ReSet_Lib::audio::audio::{OutputStream, Source}; use super::outputStreamEntry::OutputStreamEntry; -use super::sourceEntry::{toggle_source_mute, SourceEntry}; +use super::sourceEntry::{set_default_source, toggle_source_mute, SourceEntry}; glib::wrapper! { pub struct SourceBox(ObjectSubclass) @@ -51,13 +51,23 @@ impl SourceBox { pub fn populate_sources(output_box: Arc) { gio::spawn_blocking(move || { let output_box_imp = output_box.imp(); - let sinks = get_sources(); + let sources = get_sources(); + { + let list = output_box_imp.resetModelList.borrow_mut(); + let mut map = output_box_imp.resetSourceMap.borrow_mut(); + let mut i: u32 = 0; + for source in sources.iter() { + list.append(&source.alias); + map.insert(source.alias.clone(), (source.index, i, source.name.clone())); + i += 1; + } + } output_box_imp .resetDefaultSource .replace(get_default_source()); glib::spawn_future(async move { glib::idle_add_once(move || { - // TODO handle default mapping + // TODO handle events let output_box_ref_slider = output_box.clone(); let output_box_ref_mute = output_box.clone(); let output_box_ref = output_box.clone(); @@ -71,15 +81,49 @@ pub fn populate_sources(output_box: Arc) { let percentage = (fraction).to_string() + "%"; output_box_imp.resetVolumePercentage.set_text(&percentage); output_box_imp.resetVolumeSlider.set_value(*volume as f64); - for stream in sinks { + for stream in sources { let mut is_default = false; if output_box_imp.resetDefaultSource.borrow().name == stream.name { is_default = true; } - let entry = ListEntry::new(&SourceEntry::new(is_default, output_box_imp.resetDefaultCheckButton.clone(), stream)); + let entry = ListEntry::new(&SourceEntry::new( + is_default, + output_box_imp.resetDefaultCheckButton.clone(), + stream, + )); entry.set_activatable(false); output_box_imp.resetSources.append(&entry); } + let list = output_box_imp.resetModelList.borrow(); + output_box_imp.resetSourceDropdown.set_model(Some(&*list)); + let map = output_box_imp.resetSourceMap.borrow(); + let name = output_box_imp.resetDefaultSource.borrow(); + let name = &name.alias; + let index = map.get(name); + if index.is_some() { + output_box_imp + .resetSourceDropdown + .set_selected(index.unwrap().1); + } + output_box_imp.resetSourceDropdown.connect_selected_notify( + clone!(@weak output_box_imp => move |dropdown| { + let selected = dropdown.selected_item(); + if selected.is_none() { + return; + } + let selected = selected.unwrap(); + let selected = selected.downcast_ref::().unwrap(); + let selected = selected.string().to_string(); + + let source = output_box_imp.resetSourceMap.borrow(); + let source = source.get(&selected); + if source.is_none() { + return; + } + let sink = Arc::new(source.unwrap().2.clone()); + set_default_source(sink); + }), + ); } output_box_ref .imp() @@ -121,7 +165,7 @@ pub fn populate_sources(output_box: Arc) { }); } -pub fn populate_outputstreams(listeners: Arc, output_box: Arc) { +pub fn populate_outputstreams(_listeners: Arc, output_box: Arc) { // TODO add listener let output_box_ref = output_box.clone(); @@ -131,7 +175,7 @@ pub fn populate_outputstreams(listeners: Arc, output_box: Arc>, pub resetSourceList: Arc>>, pub resetOutputStreamList: Arc>>, + pub resetModelList: Arc>, + // first u32 is the index of the source, the second the index in the model list + pub resetSourceMap: Arc>>, } #[glib::object_subclass] diff --git a/src/components/input/sourceEntry.rs b/src/components/input/sourceEntry.rs index d7d668a..2b286dc 100644 --- a/src/components/input/sourceEntry.rs +++ b/src/components/input/sourceEntry.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::sync::Arc; use std::thread; use std::time::Duration; @@ -25,13 +24,10 @@ impl SourceEntry { pub fn new(is_default: bool, check_group: Arc, stream: Source) -> Self { let obj: Self = Object::builder().build(); // TODO use event callback for progress bar -> this is the "im speaking" indicator - // TODO map the slider to volume - // TODO properly use volume fraction - // TODO map mute to callback - // TODO map dropdown + // TODO handle events { let imp = obj.imp(); - imp.resetSourceName.set_text(stream.name.clone().as_str()); + imp.resetSourceName.set_text(stream.alias.clone().as_str()); let name = Arc::new(stream.name.clone()); let volume = stream.volume.first().unwrap_or_else(|| &(0 as u32)); let fraction = (*volume as f64 / 655.36).round(); diff --git a/src/components/output/inputStreamEntry.rs b/src/components/output/inputStreamEntry.rs index 01917d9..6f591d1 100644 --- a/src/components/output/inputStreamEntry.rs +++ b/src/components/output/inputStreamEntry.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::sync::Arc; use std::time::Duration; @@ -8,11 +7,12 @@ use adw::prelude::{ButtonExt, RangeExt}; use dbus::blocking::Connection; use dbus::Error; use glib::subclass::types::ObjectSubclassIsExt; -use glib::subclass::ObjectImplRef; -use glib::{clone, Propagation}; +use glib::{clone, Cast, Propagation}; +use gtk::StringObject; use ReSet_Lib::audio::audio::InputStream; use super::inputStreamEntryImpl; +use super::sinkBox::SinkBox; glib::wrapper! { pub struct InputStreamEntry(ObjectSubclass) @@ -21,11 +21,12 @@ glib::wrapper! { } impl InputStreamEntry { - pub fn new(stream: InputStream) -> Self { + pub fn new(sink_box: Arc, stream: InputStream) -> Self { let obj: Self = Object::builder().build(); // TODO use event callback for progress bar -> this is the "im speaking" indicator - // TODO map dropdown + // TODO handle events { + let box_imp = sink_box.imp(); let imp = obj.imp(); if stream.muted { imp.resetSinkMute @@ -42,22 +43,84 @@ impl InputStreamEntry { imp.resetVolumePercentage.set_text(&percentage); imp.resetVolumeSlider.set_value(*volume as f64); imp.stream.replace(stream); + { + let sink = box_imp.resetDefaultSink.borrow(); + imp.associatedSink.replace((sink.index, sink.name.clone())); + } imp.resetVolumeSlider.connect_change_value( clone!(@weak imp => @default-return Propagation::Stop, move |_, _, value| { let fraction = (value / 655.36).round(); let percentage = (fraction).to_string() + "%"; imp.resetVolumePercentage.set_text(&percentage); - let stream = imp.stream.borrow(); + 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; set_inputstream_volume(value, index, channels); Propagation::Proceed }), ); + { + let mut list = box_imp.resetModelList.try_borrow(); + while list.is_err() { + list = box_imp.resetModelList.try_borrow(); + } + let list = list.unwrap(); + imp.resetSelectedSink.set_model(Some(&*list)); + let mut map = box_imp.resetSinkMap.try_borrow(); + while map.is_err() { + map = box_imp.resetSinkMap.try_borrow(); + } + let map = map.unwrap(); + let mut name = box_imp.resetDefaultSink.try_borrow(); + while name.is_err() { + name = box_imp.resetDefaultSink.try_borrow(); + } + let name = name.unwrap(); + let name = &name.alias; + let index = map.get(name); + if index.is_some() { + imp.resetSelectedSink.set_selected(index.unwrap().1); + } + } + imp.resetSelectedSink.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::().unwrap(); + let selected = selected.string().to_string(); + let mut sink = box_imp.resetSinkMap.try_borrow(); + while sink.is_err() { + sink = box_imp.resetSinkMap.try_borrow(); + } + 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); + }), + ); imp.resetSinkMute .connect_clicked(clone!(@weak imp => move |_| { let stream = imp.stream.clone(); - let mut stream = stream.borrow_mut(); + 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; @@ -107,3 +170,18 @@ fn toggle_input_stream_mute(index: u32, muted: bool) -> bool { } res.unwrap().0 } + +fn set_sink_of_input_stream(stream: u32, sink: u32) -> bool { + let conn = Connection::new_session().unwrap(); + let proxy = conn.with_proxy( + "org.xetibo.ReSet", + "/org/xetibo/ReSet", + Duration::from_millis(1000), + ); + let res: Result<(bool,), Error> = + proxy.method_call("org.xetibo.ReSet", "SetSinkOfInputStream", (stream, sink)); + if res.is_err() { + return false; + } + res.unwrap().0 +} diff --git a/src/components/output/inputStreamEntryImpl.rs b/src/components/output/inputStreamEntryImpl.rs index 17c46e2..29ec348 100644 --- a/src/components/output/inputStreamEntryImpl.rs +++ b/src/components/output/inputStreamEntryImpl.rs @@ -24,6 +24,7 @@ pub struct InputStreamEntry { #[template_child] pub resetVolumeMeter: TemplateChild, pub stream: Arc>, + pub associatedSink: Arc>, } #[glib::object_subclass] diff --git a/src/components/output/sinkBox.rs b/src/components/output/sinkBox.rs index e47e2f7..56e43e2 100644 --- a/src/components/output/sinkBox.rs +++ b/src/components/output/sinkBox.rs @@ -10,14 +10,14 @@ use adw::{glib, prelude::ListBoxRowExt}; use dbus::blocking::Connection; use dbus::Error; use glib::subclass::prelude::ObjectSubclassIsExt; -use glib::{clone, Propagation, Variant}; -use gtk::gio; +use glib::{clone, Cast, Propagation, Variant}; use gtk::prelude::ActionableExt; +use gtk::{gio, StringObject}; use ReSet_Lib::audio::audio::{InputStream, Sink}; use super::inputStreamEntry::InputStreamEntry; use super::sinkBoxImpl; -use super::sinkEntry::{toggle_sink_mute, SinkEntry}; +use super::sinkEntry::{set_default_sink, toggle_sink_mute, SinkEntry}; glib::wrapper! { pub struct SinkBox(ObjectSubclass) @@ -51,20 +51,28 @@ impl SinkBox { pub fn populate_sinks(output_box: Arc) { gio::spawn_blocking(move || { let output_box_ref = output_box.clone(); + let sinks = get_sinks(); { let output_box_imp = output_box.imp(); output_box_imp.resetDefaultSink.replace(get_default_sink()); + let list = output_box_imp.resetModelList.borrow_mut(); + let mut map = output_box_imp.resetSinkMap.borrow_mut(); + let mut i: u32 = 0; + for sink in sinks.iter() { + dbg!(sink.clone()); + list.append(&sink.alias); + map.insert(sink.alias.clone(), (sink.index, i, sink.name.clone())); + i += 1; + } } - let sinks = get_sinks(); glib::spawn_future(async move { glib::idle_add_once(move || { - // TODO handle default mapping let output_box_ref_slider = output_box.clone(); let output_box_ref_mute = output_box.clone(); { let output_box_imp = output_box_ref.imp(); - let default_sink = output_box_imp.resetDefaultSink.clone(); // Clone outside closure - let sink = default_sink.borrow(); // + let default_sink = output_box_imp.resetDefaultSink.clone(); + let sink = default_sink.borrow(); let volume = sink.volume.first().unwrap_or_else(|| &(0 as u32)); let fraction = (*volume as f64 / 655.36).round(); @@ -72,15 +80,48 @@ pub fn populate_sinks(output_box: Arc) { output_box_imp.resetVolumePercentage.set_text(&percentage); output_box_imp.resetVolumeSlider.set_value(*volume as f64); for stream in sinks { - // TODO create sink handler -> currently only allows input streams let mut is_default = false; if output_box_imp.resetDefaultSink.borrow().name == stream.name { is_default = true; } - let entry = ListEntry::new(&SinkEntry::new(is_default, output_box_imp.resetDefaultCheckButton.clone(), stream)); + let entry = ListEntry::new(&SinkEntry::new( + is_default, + output_box_imp.resetDefaultCheckButton.clone(), + stream, + )); entry.set_activatable(false); output_box_imp.resetSinks.append(&entry); } + let list = output_box_imp.resetModelList.borrow(); + output_box_imp.resetSinkDropdown.set_model(Some(&*list)); + let map = output_box_imp.resetSinkMap.borrow(); + let name = output_box_imp.resetDefaultSink.borrow(); + let name = &name.alias; + let index = map.get(name); + if index.is_some() { + output_box_imp + .resetSinkDropdown + .set_selected(index.unwrap().1); + } + output_box_imp.resetSinkDropdown.connect_selected_notify( + clone!(@weak output_box_imp => move |dropdown| { + let selected = dropdown.selected_item(); + if selected.is_none() { + return; + } + let selected = selected.unwrap(); + let selected = selected.downcast_ref::().unwrap(); + let selected = selected.string().to_string(); + + let sink = output_box_imp.resetSinkMap.borrow(); + let sink = sink.get(&selected); + if sink.is_none() { + return; + } + let sink = Arc::new(sink.unwrap().2.clone()); + set_default_sink(sink); + }), + ); } output_box_ref .imp() @@ -121,13 +162,9 @@ pub fn populate_sinks(output_box: Arc) { }); } -pub fn populate_inputstreams(listeners: Arc, output_box: Arc) { +pub fn populate_inputstreams(_listeners: Arc, output_box: Arc) { // TODO add listener let output_box_ref = output_box.clone(); - // let output_box_ref_listener = output_box.clone(); - let output_box_imp = output_box.imp(); - // let sources = output_box_imp.resetSinks.clone(); - // let output_streams = output_box_imp.resetInputStreams.clone(); gio::spawn_blocking(move || { let streams = get_input_streams(); @@ -135,7 +172,7 @@ pub fn populate_inputstreams(listeners: Arc, output_box: Arc glib::idle_add_once(move || { let output_box_imp = output_box_ref.imp(); for stream in streams { - let entry = ListEntry::new(&InputStreamEntry::new(stream)); + let entry = ListEntry::new(&InputStreamEntry::new(output_box.clone(), stream)); entry.set_activatable(false); output_box_imp.resetInputStreams.append(&entry); } diff --git a/src/components/output/sinkBoxImpl.rs b/src/components/output/sinkBoxImpl.rs index 3b6eac7..c25548e 100644 --- a/src/components/output/sinkBoxImpl.rs +++ b/src/components/output/sinkBoxImpl.rs @@ -1,10 +1,13 @@ use std::cell::RefCell; +use std::collections::HashMap; use std::sync::{Arc, Mutex}; use crate::components::base::listEntry::ListEntry; use crate::components::output::inputStreamEntry::InputStreamEntry; use gtk::subclass::prelude::*; -use gtk::{glib, Box, Button, CompositeTemplate, DropDown, Label, TemplateChild, CheckButton}; +use gtk::{ + glib, Box, Button, CheckButton, CompositeTemplate, DropDown, Label, StringList, TemplateChild, +}; use gtk::{prelude::*, ProgressBar, Scale}; use ReSet_Lib::audio::audio::{InputStream, Sink}; @@ -18,7 +21,7 @@ pub struct SinkBox { #[template_child] pub resetSinksRow: TemplateChild, #[template_child] - pub resetInputDevice: TemplateChild, + pub resetSinkDropdown: TemplateChild, #[template_child] pub resetSinkMute: TemplateChild