feat: Add wifi and bluetooth device switching

This commit is contained in:
Fabio Lenherr / DashieTM 2023-12-03 19:08:07 +01:00
parent b2b4ae0661
commit 959d1e735f
8 changed files with 281 additions and 101 deletions

View file

@ -6,7 +6,7 @@ description = "A wip universal Linux settings application."
[dependencies]
reset_daemon = "0.3.3"
ReSet-Lib = "0.5.5"
ReSet-Lib = "0.6.1"
adw = { version = "0.5.3", package = "libadwaita", features = ["v1_4"] }
dbus = "0.9.7"
gtk = { version = "0.7.3", package = "gtk4", features = ["v4_12"] }

View file

@ -5,14 +5,16 @@ use std::time::{Duration, SystemTime};
use adw::glib;
use adw::glib::Object;
use adw::prelude::ComboRowExt;
use adw::subclass::prelude::ObjectSubclassIsExt;
use dbus::blocking::Connection;
use dbus::message::SignalArgs;
use dbus::{Error, Path};
use gtk::gio;
use glib::{clone, Cast};
use gtk::glib::Variant;
use gtk::prelude::{ActionableExt, ListBoxRowExt, WidgetExt};
use ReSet_Lib::bluetooth::bluetooth::BluetoothDevice;
use gtk::{gio, StringObject};
use ReSet_Lib::bluetooth::bluetooth::{BluetoothAdapter, BluetoothDevice};
use ReSet_Lib::signals::{BluetoothDeviceAdded, BluetoothDeviceChanged, BluetoothDeviceRemoved};
use crate::components::base::listEntry::ListEntry;
@ -68,10 +70,50 @@ pub fn populate_conntected_bluetooth_devices(bluetooth_box: Arc<BluetoothBox>) {
gio::spawn_blocking(move || {
let ref_box = bluetooth_box.clone();
let devices = get_connected_devices();
let adapters = get_bluetooth_adapters();
{
let imp = bluetooth_box.imp();
let list = imp.resetModelList.write().unwrap();
let mut model_index = imp.resetModelIndex.write().unwrap();
let mut map = imp.resetBluetoothAdapters.write().unwrap();
imp.resetCurrentBluetoothAdapter
.replace(adapters.last().unwrap().clone());
for (index, adapter) in adapters.into_iter().enumerate() {
list.append(&adapter.alias);
map.insert(adapter.alias.clone(), (adapter, index as u32));
*model_index += 1;
}
}
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = ref_box.imp();
let list = imp.resetModelList.read().unwrap();
imp.resetBluetoothAdapter.set_model(Some(&*list));
let map = imp.resetBluetoothAdapters.read().unwrap();
let device = imp.resetCurrentBluetoothAdapter.borrow();
if let Some(index) = map.get(&device.alias) {
imp.resetBluetoothAdapter.set_selected(index.1);
}
imp.resetBluetoothAdapter.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 device = imp.resetBluetoothAdapters.read().unwrap();
let device = device.get(&selected);
if device.is_none() {
return;
}
set_bluetooth_adapter(device.unwrap().0.path.clone());
}),
);
for device in devices {
let path = device.path.clone();
let connected = device.connected;
@ -252,3 +294,29 @@ fn get_connected_devices() -> Vec<BluetoothDevice> {
}
res.unwrap().0
}
fn get_bluetooth_adapters() -> Vec<BluetoothAdapter> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
"org.Xetibo.ReSetDaemon",
"/org/Xetibo/ReSetDaemon",
Duration::from_millis(1000),
);
let res: Result<(Vec<BluetoothAdapter>,), Error> =
proxy.method_call("org.Xetibo.ReSetBluetooth", "GetBluetoothAdapters", ());
if res.is_err() {
return Vec::new();
}
res.unwrap().0
}
fn set_bluetooth_adapter(path: Path<'static>) {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
"org.Xetibo.ReSetDaemon",
"/org/Xetibo/ReSetDaemon",
Duration::from_millis(1000),
);
let _: Result<(Vec<BluetoothAdapter>,), Error> =
proxy.method_call("org.Xetibo.ReSetBluetooth", "SetBluetoothAdapter", (path,));
}

View file

@ -1,12 +1,12 @@
use adw::ActionRow;
use adw::{ActionRow, ComboRow};
use dbus::Path;
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::{glib, CompositeTemplate, ListBox, Switch};
use gtk::{prelude::*, StringList};
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::Arc;
use ReSet_Lib::bluetooth::bluetooth::BluetoothDevice;
use std::sync::{Arc, RwLock};
use ReSet_Lib::bluetooth::bluetooth::{BluetoothAdapter, BluetoothDevice};
use crate::components::base::listEntry::ListEntry;
use crate::components::bluetooth::bluetoothBox;
@ -24,6 +24,8 @@ pub struct BluetoothBox {
#[template_child]
pub resetBluetoothAvailableDevices: TemplateChild<ListBox>,
#[template_child]
pub resetBluetoothAdapter: TemplateChild<ComboRow>,
#[template_child]
pub resetBluetoothConnectedDevices: TemplateChild<ListBox>,
#[template_child]
pub resetVisibility: TemplateChild<ActionRow>,
@ -31,6 +33,10 @@ pub struct BluetoothBox {
pub resetBluetoothMainTab: TemplateChild<ListEntry>,
pub availableDevices: BluetoothMap,
pub connectedDevices: BluetoothMap,
pub resetBluetoothAdapters: Arc<RwLock<HashMap<String, (BluetoothAdapter, u32)>>>,
pub resetCurrentBluetoothAdapter: Arc<RefCell<BluetoothAdapter>>,
pub resetModelList: Arc<RwLock<StringList>>,
pub resetModelIndex: Arc<RwLock<u32>>,
}
#[glib::object_subclass]

View file

@ -8,18 +8,18 @@ use crate::components::base::utils::Listeners;
use crate::components::utils::setComboRowEllipsis;
use adw::glib;
use adw::glib::Object;
use adw::prelude::{ListBoxRowExt, PreferencesGroupExt};
use adw::prelude::{ComboRowExt, ListBoxRowExt, PreferencesGroupExt};
use adw::subclass::prelude::ObjectSubclassIsExt;
use dbus::blocking::Connection;
use dbus::message::SignalArgs;
use dbus::Error;
use dbus::Path;
use glib::{clone, PropertySet};
use gtk::gio;
use glib::{clone, Cast, PropertySet};
use gtk::glib::Variant;
use gtk::prelude::{ActionableExt, WidgetExt};
use ReSet_Lib::network::network::{AccessPoint, WifiStrength};
use ReSet_Lib::signals::AccessPointAdded;
use gtk::{gio, StringObject};
use ReSet_Lib::network::network::{AccessPoint, WifiDevice, WifiStrength};
use ReSet_Lib::signals::{AccessPointAdded, WifiDeviceChanged};
use ReSet_Lib::signals::{AccessPointChanged, AccessPointRemoved};
use crate::components::wifi::wifiBoxImpl;
@ -90,6 +90,20 @@ pub fn scanForWifi(wifiBox: Arc<WifiBox>) {
gio::spawn_blocking(move || {
let accessPoints = get_access_points();
let devices = get_wifi_devices();
{
let imp = wifibox_ref.imp();
let list = imp.resetModelList.write().unwrap();
let mut model_index = imp.resetModelIndex.write().unwrap();
let mut map = imp.resetWifiDevices.write().unwrap();
imp.resetCurrentWifiDevice
.replace(devices.last().unwrap().clone());
for (index, device) in devices.into_iter().enumerate() {
list.append(&device.name);
map.insert(device.name.clone(), (device, index as u32));
*model_index += 1;
}
}
let wifiEntries = wifiEntries.clone();
let wifiEntriesPath = wifiEntriesPath.clone();
dbus_start_network_events();
@ -97,14 +111,40 @@ pub fn scanForWifi(wifiBox: Arc<WifiBox>) {
glib::idle_add_once(move || {
let mut wifiEntries = wifiEntries.lock().unwrap();
let mut wifiEntriesPath = wifiEntriesPath.lock().unwrap();
let selfImp = wifibox_ref.imp();
let imp = wifibox_ref.imp();
let list = imp.resetModelList.read().unwrap();
imp.resetWiFiDevice.set_model(Some(&*list));
let map = imp.resetWifiDevices.read().unwrap();
let device = imp.resetCurrentWifiDevice.borrow();
if let Some(index) = map.get(&device.name) {
imp.resetWiFiDevice.set_selected(index.1);
}
imp.resetWiFiDevice
.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 device = imp.resetWifiDevices.read().unwrap();
let device = device.get(&selected);
if device.is_none() {
return;
}
set_wifi_device(device.unwrap().0.path.clone());
}));
for accessPoint in accessPoints {
let ssid = accessPoint.ssid.clone();
let path = accessPoint.dbus_path.clone();
let entry = WifiEntry::new(accessPoint, selfImp);
let connected = imp.resetCurrentWifiDevice.borrow().active_access_point == path;
let entry = WifiEntry::new(connected, accessPoint, imp);
wifiEntries.insert(ssid, entry.clone());
wifiEntriesPath.insert(path, entry.clone());
selfImp.resetWifiList.add(&*entry);
imp.resetWifiList.add(&*entry);
}
});
});
@ -157,6 +197,33 @@ pub fn get_access_points() -> Vec<AccessPoint> {
accessPoints
}
pub fn set_wifi_device(path: Path<'static>) {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
"org.Xetibo.ReSetDaemon",
"/org/Xetibo/ReSetDaemon",
Duration::from_millis(1000),
);
let _: Result<(bool,), Error> =
proxy.method_call("org.Xetibo.ReSetWireless", "SetWifiDevice", (path,));
}
pub fn get_wifi_devices() -> Vec<WifiDevice> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
"org.Xetibo.ReSetDaemon",
"/org/Xetibo/ReSetDaemon",
Duration::from_millis(1000),
);
let res: Result<(Vec<WifiDevice>,), Error> =
proxy.method_call("org.Xetibo.ReSetWireless", "GetAllWifiDevices", ());
if res.is_err() {
return Vec::new();
}
let (devices,) = res.unwrap();
devices
}
pub fn get_stored_connections() -> Vec<(Path<'static>, Vec<u8>)> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
@ -195,6 +262,7 @@ pub fn start_event_listener(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) {
let added_ref = wifi_box.clone();
let removed_ref = wifi_box.clone();
let changed_ref = wifi_box.clone();
let wifi_changed_ref = wifi_box.clone();
let access_point_added = AccessPointAdded::match_rule(
Some(&"org.Xetibo.ReSetDaemon".into()),
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
@ -210,6 +278,11 @@ pub fn start_event_listener(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) {
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
)
.static_clone();
let device_changed = WifiDeviceChanged::match_rule(
Some(&"org.Xetibo.ReSetDaemon".into()),
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
)
.static_clone();
let res = conn.add_match(access_point_added, move |ir: AccessPointAdded, _, _| {
println!("received added event");
let wifi_box = added_ref.clone();
@ -223,7 +296,9 @@ pub fn start_event_listener(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) {
if wifiEntries.get(&ssid).is_some() {
return;
}
let entry = WifiEntry::new(ir.access_point, imp);
let connected = imp.resetCurrentWifiDevice.borrow().active_access_point
== ir.access_point.dbus_path;
let entry = WifiEntry::new(connected, ir.access_point, imp);
wifiEntries.insert(ssid, entry.clone());
wifiEntriesPath.insert(path, entry.clone());
imp.resetWifiList.add(&*entry);
@ -295,7 +370,9 @@ pub fn start_event_listener(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) {
if !ir.access_point.stored {
entryImp.resetWifiEditButton.set_sensitive(false);
}
if ir.access_point.connected {
if ir.access_point.dbus_path
== imp.resetCurrentWifiDevice.borrow().active_access_point
{
entryImp
.resetWifiConnected
.get()
@ -315,6 +392,32 @@ pub fn start_event_listener(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) {
println!("fail on change");
return;
}
let res = conn.add_match(device_changed, move |ir: WifiDeviceChanged, _, _| {
println!("received wifidevice changed event");
let wifi_box = wifi_changed_ref.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = wifi_box.imp();
let mut current_device = imp.resetCurrentWifiDevice.borrow_mut();
if current_device.path == ir.wifi_device.path {
current_device.active_access_point = ir.wifi_device.active_access_point;
} else {
*current_device = ir.wifi_device;
}
let mut wifiEntries = imp.wifiEntries.lock().unwrap();
for entry in wifiEntries.iter_mut() {
let imp = entry.1.imp();
let mut connected = imp.connected.borrow_mut();
*connected = imp.accessPoint.borrow().dbus_path == current_device.path;
}
});
});
true
});
if res.is_err() {
println!("fail on add");
return;
}
println!("starting thread listener");
loop {
let _ = conn.process(Duration::from_millis(1000));

View file

@ -1,11 +1,13 @@
use crate::components::wifi::wifiBox;
use ReSet_Lib::network::network::WifiDevice;
use adw::{ActionRow, ComboRow, NavigationView, PreferencesGroup};
use dbus::Path;
use gtk::prelude::*;
use gtk::{prelude::*, StringList};
use gtk::subclass::prelude::*;
use gtk::{glib, CompositeTemplate, Switch};
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::sync::{Arc, Mutex, RwLock};
use crate::components::base::listEntry::ListEntry;
use crate::components::wifi::wifiEntry::WifiEntry;
@ -33,6 +35,10 @@ pub struct WifiBox {
pub wifiEntries: Arc<Mutex<HashMap<Vec<u8>, Arc<WifiEntry>>>>,
pub wifiEntriesPath: Arc<Mutex<HashMap<Path<'static>, Arc<WifiEntry>>>>,
pub savedWifiEntries: Arc<Mutex<Vec<ListEntry>>>,
pub resetWifiDevices: Arc<RwLock<HashMap<String, (WifiDevice,u32)>>>,
pub resetCurrentWifiDevice: Arc<RefCell<WifiDevice>>,
pub resetModelList: Arc<RwLock<StringList>>,
pub resetModelIndex: Arc<RwLock<u32>>,
}
unsafe impl Send for WifiBox {}

View file

@ -28,7 +28,7 @@ unsafe impl Send for WifiEntry {}
unsafe impl Sync for WifiEntry {}
impl WifiEntry {
pub fn new(access_point: AccessPoint, wifiBox: &WifiBox) -> Arc<Self> {
pub fn new(connected: bool, access_point: AccessPoint, wifiBox: &WifiBox) -> Arc<Self> {
let entry: Arc<WifiEntry> = Arc::new(Object::builder().build());
let stored_entry = entry.clone();
let new_entry = entry.clone();
@ -40,6 +40,7 @@ impl WifiEntry {
entryImp.wifiStrength.set(strength);
entryImp.resetWifiLabel.get().set_text(name);
entryImp.resetWifiEncrypted.set_visible(false);
entryImp.connected.set(connected);
// TODO handle encryption thing
entryImp
.resetWifiStrength
@ -53,7 +54,7 @@ impl WifiEntry {
if !access_point.stored {
entryImp.resetWifiEditButton.set_sensitive(false);
}
if access_point.connected {
if connected {
entryImp
.resetWifiConnected
.get()
@ -68,7 +69,7 @@ impl WifiEntry {
entry.set_activatable(true);
entry.connect_activated(clone!(@weak entryImp => move |_| {
let access_point = entryImp.accessPoint.borrow();
if access_point.connected {
if *entryImp.connected.borrow() {
click_disconnect(stored_entry.clone());
} else if access_point.stored {
click_stored_network(stored_entry.clone());
@ -92,7 +93,9 @@ impl WifiEntry {
pub fn click_disconnect(entry: Arc<WifiEntry>) {
println!("called disconnect");
let entry_ref = entry.clone();
entry.set_activatable(false);
gio::spawn_blocking(move || {
let imp = entry_ref.imp();
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
"org.Xetibo.ReSetDaemon",
@ -105,21 +108,24 @@ pub fn click_disconnect(entry: Arc<WifiEntry>) {
(),
);
if res.is_err() {
println!("res of disconnect was error bro");
imp.connected.replace(false);
return;
}
let imp = entry_ref.imp();
imp.resetWifiConnected.get().set_from_icon_name(None);
imp.accessPoint.borrow_mut().connected = false;
println!("disconnect worked");
imp.connected.replace(false);
glib::spawn_future(async move {
glib::idle_add_once(move || {
entry.set_activatable(true);
});
});
});
}
pub fn click_stored_network(entry: Arc<WifiEntry>) {
let result = Arc::new(AtomicBool::new(false));
let entryImp = entry.imp();
let access_point = entryImp.accessPoint.borrow().clone();
let entry_ref = entry.clone();
entry.set_activatable(false);
gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
@ -134,24 +140,23 @@ pub fn click_stored_network(entry: Arc<WifiEntry>) {
);
glib::spawn_future(async move {
glib::idle_add_once(move || {
let _imp = entry_ref.imp();
entry.set_activatable(true);
let imp = entry_ref.imp();
if res.is_err() {
println!("wat bro?");
result.store(false, std::sync::atomic::Ordering::SeqCst);
println!("wtf?");
imp.connected.replace(false);
return;
}
if res.unwrap() == (false,) {
println!("error bro?");
result.store(false, std::sync::atomic::Ordering::SeqCst);
println!("false on connecting");
imp.connected.replace(false);
return;
}
let imp = entry_ref.imp();
println!("wateroni");
imp.resetWifiConnected
.get()
.set_from_icon_name(Some("network-wireless-connected-symbolic"));
imp.accessPoint.borrow_mut().connected = true;
result.store(true, std::sync::atomic::Ordering::SeqCst);
imp.connected.replace(true);
});
});
});
@ -159,81 +164,72 @@ pub fn click_stored_network(entry: Arc<WifiEntry>) {
}
pub fn click_new_network(entry: Arc<WifiEntry>) {
let connect_new_network = |result: Arc<AtomicBool>,
entry: Arc<WifiEntry>,
access_point: AccessPoint,
password: String| {
let entry_ref = entry.clone();
let popup = entry.imp().resetWifiPopup.imp();
popup.resetPopupLabel.set_text("Connecting...");
popup.resetPopupLabel.set_visible(true);
popup.resetPopupEntry.set_sensitive(false);
popup.resetPopupButton.set_sensitive(false);
let connect_new_network =
|entry: Arc<WifiEntry>, access_point: AccessPoint, password: String| {
let entry_ref = entry.clone();
let popup = entry.imp().resetWifiPopup.imp();
popup.resetPopupLabel.set_text("Connecting...");
popup.resetPopupLabel.set_visible(true);
popup.resetPopupEntry.set_sensitive(false);
popup.resetPopupButton.set_sensitive(false);
gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
"org.Xetibo.ReSetDaemon",
"/org/Xetibo/ReSetDaemon",
Duration::from_millis(10000),
);
let res: Result<(bool,), Error> = proxy.method_call(
"org.Xetibo.ReSetWireless",
"ConnectToNewAccessPoint",
(access_point, password),
);
glib::spawn_future(async move {
glib::idle_add_once(move || {
if res.is_err() {
println!("error bro");
entry_ref
.imp()
.resetWifiPopup
.imp()
.resetPopupLabel
.set_text("Could not connect to dbus.");
result.store(false, std::sync::atomic::Ordering::SeqCst);
return;
}
if res.unwrap() == (false,) {
println!("wrong pw");
entry_ref
.imp()
.resetWifiPopup
.imp()
.resetPopupLabel
.set_text("Could not connect to access point.");
result.store(false, std::sync::atomic::Ordering::SeqCst);
return;
}
println!("worked?");
let imp = entry_ref.imp();
imp.resetWifiPopup.popdown();
imp.resetWifiEditButton.set_sensitive(true);
imp.resetWifiConnected
.get()
.set_from_icon_name(Some("network-wireless-connected-symbolic"));
result.store(true, std::sync::atomic::Ordering::SeqCst);
gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
"org.Xetibo.ReSetDaemon",
"/org/Xetibo/ReSetDaemon",
Duration::from_millis(10000),
);
let res: Result<(bool,), Error> = proxy.method_call(
"org.Xetibo.ReSetWireless",
"ConnectToNewAccessPoint",
(access_point, password),
);
glib::spawn_future(async move {
glib::idle_add_once(move || {
if res.is_err() {
let imp = entry_ref.imp();
imp.resetWifiPopup
.imp()
.resetPopupLabel
.set_text("Could not connect to dbus.");
imp.connected.replace(false);
return;
}
if res.unwrap() == (false,) {
let imp = entry_ref.imp();
imp.resetWifiPopup
.imp()
.resetPopupLabel
.set_text("Could not connect to access point.");
imp.connected.replace(false);
return;
}
println!("worked?");
let imp = entry_ref.imp();
imp.resetWifiPopup.popdown();
imp.resetWifiEditButton.set_sensitive(true);
imp.resetWifiConnected
.get()
.set_from_icon_name(Some("network-wireless-connected-symbolic"));
imp.connected.replace(true);
});
});
});
});
// TODO crate spinner animation and block UI
};
// TODO crate spinner animation and block UI
};
let result = Arc::new(AtomicBool::new(false));
let result_ref = result.clone();
let result_ref_button = result.clone();
let entryImp = entry.imp();
let popupImp = entryImp.resetWifiPopup.imp();
popupImp
.resetPopupEntry
.connect_activate(clone!(@weak entry as origEntry, @weak entryImp => move |entry| {
connect_new_network(result_ref.clone(), origEntry, entryImp.accessPoint.clone().take(), entry.text().to_string());
connect_new_network(origEntry, entryImp.accessPoint.clone().take(), entry.text().to_string());
}));
popupImp.resetPopupButton.connect_clicked(
clone!(@weak entry as origEntry,@weak entryImp, @weak popupImp => move |_| {
let entry = entryImp.resetWifiPopup.imp().resetPopupEntry.text().to_string();
connect_new_network(result_ref_button.clone(), origEntry, entryImp.accessPoint.clone().take(), entry);
connect_new_network(origEntry, entryImp.accessPoint.clone().take(), entry);
}),
);
entryImp.resetWifiPopup.popup();

View file

@ -5,7 +5,7 @@ use adw::subclass::prelude::ActionRowImpl;
use adw::ActionRow;
use gtk::subclass::prelude::*;
use gtk::{glib, Button, CompositeTemplate, Image, Label};
use std::cell::RefCell;
use std::cell::{RefCell, Cell};
use ReSet_Lib::network::network::{AccessPoint, WifiStrength};
#[allow(non_snake_case)]
@ -27,6 +27,7 @@ pub struct WifiEntry {
pub wifiName: RefCell<String>,
pub wifiStrength: RefCell<WifiStrength>,
pub accessPoint: RefCell<AccessPoint>,
pub connected: RefCell<bool>,
}
unsafe impl Send for WifiEntry {}

View file

@ -39,7 +39,7 @@
<child>
<object class="AdwPreferencesGroup" id="resetBluetoothDetails">
<child>
<object class="AdwComboRow" id="resetBluetoothDevice">
<object class="AdwComboRow" id="resetBluetoothAdapter">
<property name="title">Bluetooth Device</property>
</object>
</child>