feat: Add initial Bluetooth functionality

This commit is contained in:
Fabio Lenherr / DashieTM 2023-11-18 22:15:09 +01:00
parent 03fc3790c0
commit 9108ab0d74
14 changed files with 514 additions and 20 deletions

View file

@ -0,0 +1,74 @@
use std::time::Duration;
use adw::glib;
use adw::glib::Object;
use dbus::blocking::Connection;
use dbus::Error;
use glib::subclass::types::ObjectSubclassIsExt;
use glib::{clone, Cast};
use gtk::{gio, StringObject};
use ReSet_Lib::audio::audio::Card;
use super::cardEntryImpl;
glib::wrapper! {
pub struct CardEntry(ObjectSubclass<cardEntryImpl::CardEntry>)
@extends gtk::Box, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
impl CardEntry {
pub fn new(card: Card) -> Self {
let entry: Self = Object::builder().build();
{
let imp = entry.imp();
let mut map = imp.resetCardMap.borrow_mut();
imp.resetCardName.set_text(&card.name);
let mut i: u32 = 0;
let mut index: u32 = 0;
for profile in card.profiles.iter() {
if profile.name == card.active_profile {
index = i;
}
imp.resetCardList.append(&profile.description);
map.insert(
profile.description.clone(),
(card.index, profile.name.clone()),
);
i += 1;
}
imp.resetCardDropdown.set_selected(index);
imp.resetCardDropdown
.connect_selected_notify(clone!(@weak 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 map = imp.resetCardMap.borrow();
let (device_index, profile_name) = map.get(&selected).unwrap();
set_card_profile_of_device(*device_index, profile_name.clone());
}));
}
entry
}
}
fn set_card_profile_of_device(device_index: u32, profile_name: String) -> bool {
gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
"org.xetibo.ReSet",
"/org/xetibo/ReSet",
Duration::from_millis(1000),
);
let _: Result<(), Error> = proxy.method_call(
"org.xetibo.ReSet",
"SetCardProfileOfDevice",
(device_index, profile_name),
);
});
true
}

View file

@ -0,0 +1,52 @@
use std::cell::RefCell;
use std::collections::HashMap;
use crate::components::base::listEntry::ListEntry;
use gtk::subclass::prelude::*;
use gtk::{glib, CompositeTemplate, TemplateChild, Label, DropDown, StringList};
use super::cardEntry;
#[allow(non_snake_case)]
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetCardEntry.ui")]
pub struct CardEntry {
#[template_child]
pub resetCardName: TemplateChild<Label>,
#[template_child]
pub resetCardDropdown: TemplateChild<DropDown>,
#[template_child]
pub resetCardList: TemplateChild<StringList>,
// first string is the alias name, the first return string is the index of the adapter and the
// second the name of the profile
pub resetCardMap: RefCell<HashMap<String, (u32, String)>>
}
#[glib::object_subclass]
impl ObjectSubclass for CardEntry {
const NAME: &'static str = "resetCardEntry";
type Type = cardEntry::CardEntry;
type ParentType = gtk::Box;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl BoxImpl for CardEntry {}
impl ObjectImpl for CardEntry {
fn constructed(&self) {}
}
impl ListBoxRowImpl for CardEntry {}
impl WidgetImpl for CardEntry {}
impl WindowImpl for CardEntry {}
impl ApplicationWindowImpl for CardEntry {}

View file

@ -1,6 +1,8 @@
use std::collections::HashMap;
use crate::components::base::listEntry;
use gtk::subclass::prelude::*;
use gtk::{glib, CompositeTemplate};
use gtk::{glib, CompositeTemplate, DropDown, Label, StringList};
#[allow(non_snake_case)]
#[derive(Default, CompositeTemplate)]

View file

@ -5,3 +5,5 @@ pub mod listEntryImpl;
pub mod popup;
pub mod popupImpl;
pub mod utils;
pub mod cardEntry;
pub mod cardEntryImpl;

View file

@ -1,13 +1,24 @@
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use adw::glib;
use adw::glib::Object;
use adw::subclass::prelude::ObjectSubclassIsExt;
use dbus::blocking::Connection;
use dbus::message::SignalArgs;
use dbus::{Error, Path};
use gtk::gio;
use gtk::glib::Variant;
use gtk::prelude::ActionableExt;
use ReSet_Lib::signals::{BluetoothDeviceAdded, BluetoothDeviceRemoved};
use crate::components::base::listEntry::ListEntry;
use crate::components::base::utils::Listeners;
use crate::components::bluetooth::bluetoothBoxImpl;
use crate::components::bluetooth::bluetoothEntry::BluetoothEntry;
use crate::components::bluetooth::bluetoothEntryImpl::DeviceTypes;
use crate::components::base::listEntry::ListEntry;
glib::wrapper! {
pub struct BluetoothBox(ObjectSubclass<bluetoothBoxImpl::BluetoothBox>)
@ -15,6 +26,9 @@ glib::wrapper! {
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
unsafe impl Send for BluetoothBox {}
unsafe impl Sync for BluetoothBox {}
impl BluetoothBox {
pub fn new() -> Self {
Object::builder().build()
@ -22,19 +36,37 @@ impl BluetoothBox {
pub fn setupCallbacks(&self) {
let selfImp = self.imp();
selfImp.resetVisibility.set_action_name(Some("navigation.push"));
selfImp.resetVisibility.set_action_target_value(Some(&Variant::from("visibility")));
selfImp
.resetVisibility
.set_action_name(Some("navigation.push"));
selfImp
.resetVisibility
.set_action_target_value(Some(&Variant::from("visibility")));
selfImp.resetBluetoothMainTab.set_action_name(Some("navigation.pop"));
selfImp
.resetBluetoothMainTab
.set_action_name(Some("navigation.pop"));
}
pub fn scanForDevices(&self) {
let selfImp = self.imp();
let mut wifiEntries = selfImp.availableDevices.borrow_mut();
wifiEntries.push(ListEntry::new(&BluetoothEntry::new(DeviceTypes::Mouse, "ina mouse")));
wifiEntries.push(ListEntry::new(&BluetoothEntry::new(DeviceTypes::Keyboard, "inaboard")));
wifiEntries.push(ListEntry::new(&BluetoothEntry::new(DeviceTypes::Controller, "ina controller")));
wifiEntries.push(ListEntry::new(&BluetoothEntry::new(DeviceTypes::Controller, "ina best waifu")));
wifiEntries.push(ListEntry::new(&BluetoothEntry::new(
DeviceTypes::Mouse,
"ina mouse",
)));
wifiEntries.push(ListEntry::new(&BluetoothEntry::new(
DeviceTypes::Keyboard,
"inaboard",
)));
wifiEntries.push(ListEntry::new(&BluetoothEntry::new(
DeviceTypes::Controller,
"ina controller",
)));
wifiEntries.push(ListEntry::new(&BluetoothEntry::new(
DeviceTypes::Controller,
"ina best waifu",
)));
for wifiEntry in wifiEntries.iter() {
selfImp.resetBluetoothAvailableDevices.append(wifiEntry);
@ -44,11 +76,90 @@ impl BluetoothBox {
pub fn addConnectedDevices(&self) {
let selfImp = self.imp();
let mut wifiEntries = selfImp.connectedDevices.borrow_mut();
wifiEntries.push(ListEntry::new(&BluetoothEntry::new(DeviceTypes::Mouse, "why are we still here?")));
wifiEntries.push(ListEntry::new(&BluetoothEntry::new(DeviceTypes::Keyboard, "just to suffer?")));
wifiEntries.push(ListEntry::new(&BluetoothEntry::new(
DeviceTypes::Mouse,
"why are we still here?",
)));
wifiEntries.push(ListEntry::new(&BluetoothEntry::new(
DeviceTypes::Keyboard,
"just to suffer?",
)));
for wifiEntry in wifiEntries.iter() {
selfImp.resetBluetoothConnectedDevices.append(wifiEntry);
}
}
}
}
pub fn start_bluetooth_listener(listeners: Arc<Listeners>, bluetooth_box: Arc<BluetoothBox>) {
gio::spawn_blocking(move || {
if listeners.bluetooth_listener.load(Ordering::SeqCst) {
return;
}
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
"org.xetibo.ReSet",
"/org/xetibo/ReSet",
Duration::from_millis(1000),
);
let _: Result<(), Error> =
proxy.method_call("org.xetibo.ReSet", "StartBluetoothSearch", (5000,));
let device_added = BluetoothDeviceAdded::match_rule(
Some(&"org.xetibo.ReSet".into()),
Some(&Path::from("/org/xetibo/ReSet")),
)
.static_clone();
let device_removed = BluetoothDeviceRemoved::match_rule(
Some(&"org.xetibo.ReSet".into()),
Some(&Path::from("/org/xetibo/ReSet")),
)
.static_clone();
let device_added_box = bluetooth_box.clone();
let device_removed_box = bluetooth_box.clone();
let res = conn.add_match(device_added, move |ir: BluetoothDeviceAdded, _, _| {
let bluetooth_box = device_added_box.clone();
println!("added");
glib::spawn_future(async move {
glib::idle_add_once(move || {
//
//
});
});
true
});
if res.is_err() {
println!("fail on bluetooth device add");
return;
}
let res = conn.add_match(device_removed, move |ir: BluetoothDeviceRemoved, _, _| {
let bluetooth_box = device_removed_box.clone();
println!("removed");
glib::spawn_future(async move {
glib::idle_add_once(move || {
//
//
});
});
true
});
if res.is_err() {
println!("fail on bluetooth device remove");
return;
}
listeners.bluetooth_listener.store(true, Ordering::SeqCst);
loop {
let _ = conn.process(Duration::from_millis(1000));
if !listeners.bluetooth_listener.load(Ordering::SeqCst) {
println!("stopping bluetooth listener");
break;
}
thread::sleep(Duration::from_millis(100));
}
});
}

View file

@ -2,6 +2,7 @@ use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use crate::components::base::cardEntry::CardEntry;
use crate::components::base::listEntry::ListEntry;
use crate::components::base::utils::{
InputStreamAdded, InputStreamChanged, InputStreamRemoved, Listeners, SinkAdded, SinkChanged,
@ -18,7 +19,7 @@ use glib::subclass::prelude::ObjectSubclassIsExt;
use glib::{clone, Cast, Propagation, Variant};
use gtk::prelude::ActionableExt;
use gtk::{gio, StringObject};
use ReSet_Lib::audio::audio::{InputStream, Sink};
use ReSet_Lib::audio::audio::{Card, InputStream, Sink};
use super::inputStreamEntry::InputStreamEntry;
use super::sinkBoxImpl;
@ -52,10 +53,20 @@ impl SinkBox {
selfImp
.resetSinksRow
.set_action_target_value(Some(&Variant::from("outputDevices")));
selfImp
.resetCardsRow
.set_action_name(Some("navigation.push"));
selfImp
.resetCardsRow
.set_action_target_value(Some(&Variant::from("profileConfiguration")));
selfImp.resetCardsRow.connect_action_name_notify(|_| {});
selfImp
.resetInputStreamButton
.set_action_name(Some("navigation.pop"));
selfImp
.resetInputCardsBackButton
.set_action_name(Some("navigation.pop"));
}
}
@ -84,6 +95,7 @@ pub fn populate_sinks(output_box: Arc<SinkBox>) {
}
}
populate_inputstreams(output_box.clone());
populate_cards(output_box.clone());
glib::spawn_future(async move {
glib::idle_add_once(move || {
let output_box_ref_slider = output_box.clone();
@ -217,6 +229,22 @@ pub fn populate_inputstreams(output_box: Arc<SinkBox>) {
});
}
pub fn populate_cards(output_box: Arc<SinkBox>) {
gio::spawn_blocking(move || {
let output_box_ref = output_box.clone();
let cards = get_cards();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = output_box_ref.imp();
for card in cards {
imp.resetCards
.append(&ListEntry::new(&CardEntry::new(card)));
}
});
});
});
}
fn get_input_streams() -> Vec<InputStream> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
@ -260,6 +288,20 @@ fn get_default_sink() -> Sink {
res.unwrap().0
}
fn get_cards() -> Vec<Card> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
"org.xetibo.ReSet",
"/org/xetibo/ReSet",
Duration::from_millis(1000),
);
let res: Result<(Vec<Card>,), Error> = proxy.method_call("org.xetibo.ReSet", "ListCards", ());
if res.is_err() {
return Vec::new();
}
res.unwrap().0
}
pub fn start_output_box_listener(
conn: Connection,
listeners: Arc<Listeners>,

View file

@ -22,6 +22,8 @@ pub struct SinkBox {
#[template_child]
pub resetSinksRow: TemplateChild<ListEntry>,
#[template_child]
pub resetCardsRow: TemplateChild<ListEntry>,
#[template_child]
pub resetSinkDropdown: TemplateChild<DropDown>,
#[template_child]
pub resetSinkMute: TemplateChild<Button>,
@ -37,6 +39,10 @@ pub struct SinkBox {
pub resetInputStreamButton: TemplateChild<ListEntry>,
#[template_child]
pub resetInputStreams: TemplateChild<Box>,
#[template_child]
pub resetInputCardsBackButton: TemplateChild<ListEntry>,
#[template_child]
pub resetCards: TemplateChild<Box>,
pub resetDefaultCheckButton: Arc<CheckButton>,
pub resetDefaultSink: Arc<RefCell<Sink>>,
pub resetSinkList: Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<SinkEntry>, String)>>>,

View file

@ -4,7 +4,7 @@ use std::sync::Arc;
use crate::components::base::settingBox::SettingBox;
use crate::components::base::utils::{start_event_listener, Listeners};
use crate::components::bluetooth::bluetoothBox::BluetoothBox;
use crate::components::bluetooth::bluetoothBox::{start_bluetooth_listener, BluetoothBox};
use crate::components::input::sourceBox::{populate_sources, SourceBox};
use crate::components::output::sinkBox::{populate_sinks, SinkBox};
use crate::components::wifi::wifiBox::{scanForWifi, show_stored_connections, WifiBox};
@ -18,7 +18,9 @@ pub const HANDLE_CONNECTIVITY_CLICK: fn(Arc<Listeners>, FlowBox) =
show_stored_connections(wifiBox.clone());
scanForWifi(listeners.clone(), wifiBox.clone());
let wifiFrame = wrapInFrame(SettingBox::new(&*wifiBox));
let bluetoothFrame = wrapInFrame(SettingBox::new(&BluetoothBox::new()));
let bluetooth_box = Arc::new(BluetoothBox::new());
start_bluetooth_listener(listeners.clone(), bluetooth_box.clone());
let bluetoothFrame = wrapInFrame(SettingBox::new(&*bluetooth_box));
resetMain.remove_all();
resetMain.insert(&wifiFrame, -1);
resetMain.insert(&bluetoothFrame, -1);
@ -41,7 +43,9 @@ pub const HANDLE_BLUETOOTH_CLICK: fn(Arc<Listeners>, FlowBox) =
|listeners: Arc<Listeners>, resetMain: FlowBox| {
listeners.stop_network_listener();
listeners.pulse_listener.store(false, Ordering::SeqCst);
let bluetoothFrame = wrapInFrame(SettingBox::new(&BluetoothBox::new()));
let bluetooth_box = Arc::new(BluetoothBox::new());
start_bluetooth_listener(listeners.clone(), bluetooth_box.clone());
let bluetoothFrame = wrapInFrame(SettingBox::new(&*bluetooth_box));
resetMain.remove_all();
resetMain.insert(&bluetoothFrame, -1);
resetMain.set_max_children_per_line(1);