From 7a003389b8c191c7ca3751ef6f86fae0fd9ff2f0 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 9 Feb 2023 20:09:31 -0500 Subject: [PATCH] feat: pairing via applet's agent --- Cargo.lock | 1 + cosmic-applet-bluetooth/Cargo.toml | 3 +- ...com.system76.CosmicAppletBluetooth.desktop | 2 +- .../data/resources/resources.gresource.xml | 6 - cosmic-applet-bluetooth/src/app.rs | 217 +++++++++++--- cosmic-applet-bluetooth/src/bluetooth.rs | 281 +++++++++++++++--- debian/control | 1 + justfile | 7 + 8 files changed, 421 insertions(+), 97 deletions(-) delete mode 100644 cosmic-applet-bluetooth/data/resources/resources.gresource.xml diff --git a/Cargo.lock b/Cargo.lock index c4956160..9a830b64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -591,6 +591,7 @@ dependencies = [ "log", "once_cell", "pretty_env_logger", + "rand", "rust-embed", "slotmap", "smithay-client-toolkit", diff --git a/cosmic-applet-bluetooth/Cargo.toml b/cosmic-applet-bluetooth/Cargo.toml index c382ee7f..63453a0c 100644 --- a/cosmic-applet-bluetooth/Cargo.toml +++ b/cosmic-applet-bluetooth/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0-or-later" [dependencies] once_cell = "1.16.0" -bluer = { version = "0.15", features = ["bluetoothd"] } +bluer = { version = "0.15", features = ["bluetoothd", "id"] } futures-util = "0.3.21" libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "master", default-features = false, features = ["wayland", "applet", "tokio"] } sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "3776d4a" } @@ -21,3 +21,4 @@ anyhow = "1.0" i18n-embed = { version = "0.13.4", features = ["fluent-system", "desktop-requester"] } i18n-embed-fl = "0.6.4" rust-embed = "6.3.0" +rand = "0.8" \ No newline at end of file diff --git a/cosmic-applet-bluetooth/data/com.system76.CosmicAppletBluetooth.desktop b/cosmic-applet-bluetooth/data/com.system76.CosmicAppletBluetooth.desktop index e65db372..677183e1 100644 --- a/cosmic-applet-bluetooth/data/com.system76.CosmicAppletBluetooth.desktop +++ b/cosmic-applet-bluetooth/data/com.system76.CosmicAppletBluetooth.desktop @@ -2,7 +2,7 @@ Name=Cosmic Applet Network Comment=Write a GTK + Rust application Type=Application -Exec=cosmic-applet-network +Exec=cosmic-applet-bluetooth Terminal=false Categories=GNOME;GTK; Keywords=Gnome;GTK; diff --git a/cosmic-applet-bluetooth/data/resources/resources.gresource.xml b/cosmic-applet-bluetooth/data/resources/resources.gresource.xml deleted file mode 100644 index 2cf0970f..00000000 --- a/cosmic-applet-bluetooth/data/resources/resources.gresource.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/cosmic-applet-bluetooth/src/app.rs b/cosmic-applet-bluetooth/src/app.rs index dbb77d59..47612ecf 100644 --- a/cosmic-applet-bluetooth/src/app.rs +++ b/cosmic-applet-bluetooth/src/app.rs @@ -1,9 +1,6 @@ -use std::f32::consts::E; - use crate::bluetooth::{BluerDeviceStatus, BluerRequest, BluerState}; use cosmic::applet::APPLET_BUTTON_THEME; use cosmic::iced_style; -use cosmic::widget::ListColumn; use cosmic::{ applet::CosmicAppletHelper, iced::{ @@ -11,7 +8,7 @@ use cosmic::{ popup::{destroy_popup, get_popup}, SurfaceIdWrapper, }, - widget::{column, container, row, scrollable, text, text_input, Column}, + widget::{column, container, row, scrollable, text, Column}, Alignment, Application, Color, Command, Length, Subscription, }, iced_native::{ @@ -25,9 +22,11 @@ use cosmic::{ widget::{button, divider, icon, toggler}, Element, Theme, }; +use std::collections::HashMap; +use std::time::Duration; use tokio::sync::mpsc::Sender; -use crate::bluetooth::{bluetooth_subscription, BluerEvent}; +use crate::bluetooth::{bluetooth_subscription, BluerDevice, BluerEvent}; use crate::{config, fl}; pub fn run() -> cosmic::iced::Result { @@ -35,13 +34,6 @@ pub fn run() -> cosmic::iced::Result { CosmicBluetoothApplet::run(helper.window_settings()) } -#[derive(Debug)] -enum NewConnectionState { - EnterPassword { device: (), password: String }, - Waiting(()), - Failure(()), -} - // impl Into<()> for NewConnectionState { // fn into(self) -> AccessPoint { // match self { @@ -66,7 +58,7 @@ struct CosmicBluetoothApplet { bluer_sender: Option>, // UI state show_visible_devices: bool, - new_connection: Option, + request_confirmation: Option<(BluerDevice, String, Sender)>, } #[derive(Debug, Clone)] @@ -77,6 +69,8 @@ enum Message { Ignore, BluetoothEvent(BluerEvent), Request(BluerRequest), + Cancel, + Confirm, } impl Application for CosmicBluetoothApplet { @@ -123,56 +117,113 @@ impl Application for CosmicBluetoothApplet { .min_width(1) .max_height(800) .max_width(400); - return get_popup(popup_settings); + let tx = self.bluer_sender.as_ref().cloned(); + return Command::batch(vec![ + Command::perform( + async { + if let Some(tx) = tx { + let _ = tx.send(BluerRequest::StateUpdate).await; + } + }, + |_| Message::Ignore, + ), + get_popup(popup_settings), + ]); } } Message::Errored(_) => todo!(), Message::Ignore => {} - // Message::SelectDevice(device) => { - // let tx = if let Some(tx) = self.nm_sender.as_ref() { - // tx - // } else { - // return Command::none(); - // }; - - // let _ = tx.unbounded_send(NetworkManagerRequest::SelectAccessPoint( - // access_point.ssid.clone(), - // )); - - // self.new_connection - // .replace(NewConnectionState::EnterPassword { - // access_point, - // password: String::new(), - // }); - // } Message::ToggleVisibleDevices(enabled) => { - self.new_connection.take(); self.show_visible_devices = enabled; } Message::BluetoothEvent(e) => match e { BluerEvent::RequestResponse { - req: _req, + req, state, err_msg, } => { if let Some(err_msg) = err_msg { eprintln!("bluetooth request error: {}", err_msg); } - dbg!(&state); self.bluer_state = state; // TODO special handling for some requests + + match req { + BluerRequest::StateUpdate + if self.popup.is_some() && self.bluer_sender.is_some() => + { + for device in &self.bluer_state.devices { + dbg!((&device.name, &device.status)); + } + let tx = self.bluer_sender.as_ref().cloned().unwrap(); + return Command::perform( + async move { + // sleep for a bit before requesting state update again + tokio::time::sleep(Duration::from_millis(3000)).await; + let _ = tx.send(BluerRequest::StateUpdate).await; + }, + |_| Message::Ignore, + ); + } + _ => {} + }; } BluerEvent::Init { sender, state } => { self.bluer_sender.replace(sender); self.bluer_state = state; } BluerEvent::DevicesChanged { state } => { + for device in &state.devices { + dbg!(&device.name); + } self.bluer_state = state; } BluerEvent::Finished => { - // TODO exit? - todo!() + eprintln!("bluetooth subscription finished. exiting..."); + std::process::exit(0); } + // TODO handle agent events + BluerEvent::AgentEvent(event) => match event { + crate::bluetooth::BluerAgentEvent::DisplayPinCode(d, code) => { + dbg!((d.name, code)); + } + crate::bluetooth::BluerAgentEvent::DisplayPasskey(d, code) => { + dbg!((d.name, code)); + } + crate::bluetooth::BluerAgentEvent::RequestPinCode(d) => { + // TODO anything to be done here? + dbg!("request pin code", d.name); + } + crate::bluetooth::BluerAgentEvent::RequestPasskey(d) => { + // TODO anything to be done here? + dbg!("request passkey", d.name); + } + crate::bluetooth::BluerAgentEvent::RequestConfirmation(d, code, tx) => { + dbg!("request confirmation", &d.name, &code); + self.request_confirmation.replace((d, code, tx)); + // let _ = tx.send(false); + } + crate::bluetooth::BluerAgentEvent::RequestDeviceAuthorization(d, _tx) => { + // TODO anything to be done here? + dbg!("request device authorization", d.name); + // let_ = tx.send(false); + } + crate::bluetooth::BluerAgentEvent::RequestServiceAuthorization( + d, + service, + _tx, + ) => { + // my headphones seem to always request this + // doesn't seem to be defined in the UX mockups + dbg!( + "request service authorization", + d.name, + bluer::id::Service::try_from(service) + .map(|s| s.to_string()) + .unwrap_or_else(|_| "unknown".to_string()) + ); + } + }, }, Message::Request(r) => { match &r { @@ -220,6 +271,26 @@ impl Application for CosmicBluetoothApplet { ); } } + Message::Cancel => { + if let Some((_, _, tx)) = self.request_confirmation.take() { + return Command::perform( + async move { + let _ = tx.send(false).await; + }, + |_| Message::Ignore, + ); + } + } + Message::Confirm => { + if let Some((_, _, tx)) = self.request_confirmation.take() { + return Command::perform( + async move { + let _ = tx.send(true).await; + }, + |_| Message::Ignore, + ); + } + } } Command::none() } @@ -344,9 +415,62 @@ impl Application for CosmicBluetoothApplet { .style(button_style.clone()) .on_press(Message::ToggleVisibleDevices(!self.show_visible_devices)); content = content.push(available_connections_btn); - if self.show_visible_devices { - let mut list_column = Vec::with_capacity(self.bluer_state.devices.len()); + let mut list_column = Vec::with_capacity(self.bluer_state.devices.len()); + if let Some((device, pin, _)) = self.request_confirmation.as_ref() { + let row = column![ + text(&device.name) + .horizontal_alignment(Horizontal::Left) + .vertical_alignment(Vertical::Center) + .width(Length::Fill), + text(fl!( + "confirm-pin", + HashMap::from_iter(vec![("deviceName", device.name.clone())]) + )) + .horizontal_alignment(Horizontal::Left) + .vertical_alignment(Vertical::Center) + .width(Length::Fill), + text(pin) + .horizontal_alignment(Horizontal::Center) + .vertical_alignment(Vertical::Center) + .width(Length::Fill) + .size(24), + row![ + button(Button::Secondary) + .custom( + vec![text(fl!("cancel")) + .size(14) + .width(Length::Fill) + .height(Length::Units(24)) + .vertical_alignment(Vertical::Center) + .into(),] + .into(), + ) + .padding([8, 24]) + .style(button_style.clone()) + .on_press(Message::Cancel) + .width(Length::Fill), + button(Button::Secondary) + .custom( + vec![text(fl!("confirm")) + .size(14) + .width(Length::Fill) + .height(Length::Units(24)) + .vertical_alignment(Vertical::Center) + .into(),] + .into(), + ) + .padding([8, 24]) + .style(button_style.clone()) + .on_press(Message::Confirm) + .width(Length::Fill), + ] + ] + .padding([0, 24]) + .spacing(8); + list_column.push(row.into()); + } + if self.show_visible_devices { if self.bluer_state.bluetooth_enabled { let mut visible_devices = column![]; for dev in self.bluer_state.devices.iter().filter(|d| { @@ -370,15 +494,14 @@ impl Application for CosmicBluetoothApplet { } list_column.push(visible_devices.into()); } - let num_dev = list_column.len(); - if num_dev > 5 { - content = content.push( - scrollable(Column::with_children(list_column)) - .height(Length::Units(300)), - ); - } else { - content = content.push(Column::with_children(list_column)); - } + } + let num_dev = list_column.len(); + if num_dev > 5 { + content = content.push( + scrollable(Column::with_children(list_column)).height(Length::Units(300)), + ); + } else { + content = content.push(Column::with_children(list_column)); } self.applet_helper.popup_container(content).into() } diff --git a/cosmic-applet-bluetooth/src/bluetooth.rs b/cosmic-applet-bluetooth/src/bluetooth.rs index 9c7b366f..bfdb445c 100644 --- a/cosmic-applet-bluetooth/src/bluetooth.rs +++ b/cosmic-applet-bluetooth/src/bluetooth.rs @@ -1,9 +1,13 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, sync::Arc, time::Duration}; -use bluer::{Adapter, AdapterProperty, Address, DeviceProperty, Session}; +use bluer::{ + agent::{Agent, AgentHandle}, + Adapter, Address, DeviceProperty, Session, Uuid, +}; use cosmic::iced::{self, subscription}; use futures::StreamExt; +use rand::Rng; use tokio::{ spawn, sync::{ @@ -20,7 +24,6 @@ pub fn bluetooth_subscription( subscription::unfold(id, State::Ready, move |state| start_listening(id, state)) } -#[derive(Debug)] pub enum State { Ready, Waiting { session_state: BluerSessionState }, @@ -88,6 +91,7 @@ async fn start_listening(id: I, state: State) -> (Option<(I, Bl err_msg, }, )), + BluerSessionEvent::AgentEvent(e) => Some((id, BluerEvent::AgentEvent(e))), _ => None, } } else { @@ -108,6 +112,7 @@ pub enum BluerRequest { ConnectDevice(Address), DisconnectDevice(Address), CancelConnect(Address), + StateUpdate, } #[derive(Debug, Clone)] @@ -124,6 +129,7 @@ pub enum BluerEvent { DevicesChanged { state: BluerState, }, + AgentEvent(BluerAgentEvent), Finished, } @@ -140,6 +146,8 @@ pub enum BluerDeviceStatus { Paired, Connecting, Disconnecting, + /// Pairing is in progress, maybe with a passkey or pincode + /// passkey or pincode will be 000000 - 999999 Pairing, } @@ -151,6 +159,32 @@ pub struct BluerDevice { pub properties: Vec, } +impl BluerDevice { + pub async fn from_device(device: &bluer::Device) -> Self { + let name = device + .name() + .await + .unwrap_or_default() + .unwrap_or_else(|| "Unknown".to_string()); + let is_paired = device.is_paired().await.unwrap_or_default(); + let is_connected = device.is_connected().await.unwrap_or_default(); + let properties = device.all_properties().await.unwrap_or_default(); + let status = if is_connected { + BluerDeviceStatus::Connected + } else if is_paired { + BluerDeviceStatus::Paired + } else { + BluerDeviceStatus::Disconnected + }; + Self { + name, + address: device.address(), + status, + properties, + } + } +} + pub enum BluerSessionEvent { RequestResponse { req: BluerRequest, @@ -159,15 +193,27 @@ pub enum BluerSessionEvent { }, ChangesProcessed(BluerState), ChangeStreamEnded, // TODO can we just restart the stream in a new task? + AgentEvent(BluerAgentEvent), +} + +#[derive(Debug, Clone)] +pub enum BluerAgentEvent { + DisplayPinCode(BluerDevice, String), + DisplayPasskey(BluerDevice, String), + RequestPinCode(BluerDevice), + RequestPasskey(BluerDevice), + RequestConfirmation(BluerDevice, String, Sender), // Note mpsc channel is used bc the sender must be cloned in the iced Message machinery + RequestDeviceAuthorization(BluerDevice, Sender), + RequestServiceAuthorization(BluerDevice, Uuid, Sender), } -#[derive(Debug)] pub struct BluerSessionState { - session: Session, + _session: Session, + _agent_handle: AgentHandle, pub adapter: Adapter, pub devices: Arc>>, pub rx: Option>, - tx: Option>, + tx: Sender, active_requests: Arc>>>>, } @@ -178,43 +224,205 @@ impl BluerSessionState { ) -> anyhow::Result { let adapter = session.default_adapter().await?; let devices = build_device_list(&adapter).await; + let (tx, rx) = tokio::sync::mpsc::channel(100); + let tx_clone_1 = tx.clone(); + let tx_clone_2 = tx.clone(); + let tx_clone_3 = tx.clone(); + let tx_clone_4 = tx.clone(); + let tx_clone_5 = tx.clone(); + let tx_clone_6 = tx.clone(); + let tx_clone_7 = tx.clone(); + let adapter_clone_1 = adapter.clone(); + let adapter_clone_2 = adapter.clone(); + let adapter_clone_3 = adapter.clone(); + let adapter_clone_4 = adapter.clone(); + let adapter_clone_5 = adapter.clone(); + let adapter_clone_6 = adapter.clone(); + let adapter_clone_7 = adapter.clone(); + let _agent = Agent { + request_default: true, // TODO which agent should eventually become the default? Maybe the one in the settings app? + request_pin_code: Some(Box::new(move |req| { + let agent_clone = adapter_clone_1.clone(); + let tx_clone = tx_clone_1.clone(); + Box::pin(async move { + let device = match agent_clone.device(req.device) { + Ok(d) => d, + Err(_) => return Err(bluer::agent::ReqError::Rejected), + }; + let _ = tx_clone + .send(BluerSessionEvent::AgentEvent( + BluerAgentEvent::RequestPinCode( + BluerDevice::from_device(&device).await, + ), + )) + .await; + let mut rng = rand::thread_rng(); + let pin_code = rng.gen_range(0..999999); + Ok(format!("{:06}", pin_code)) + }) + })), + display_pin_code: Some(Box::new(move |req| { + let agent_clone = adapter_clone_2.clone(); + let tx_clone = tx_clone_2.clone(); + Box::pin(async move { + let device = match agent_clone.device(req.device) { + Ok(d) => d, + Err(_) => return Err(bluer::agent::ReqError::Rejected), + }; + let _ = tx_clone + .send(BluerSessionEvent::AgentEvent( + BluerAgentEvent::DisplayPinCode( + BluerDevice::from_device(&device).await, + req.pincode, + ), + )) + .await; + + Ok(()) + }) + })), + request_passkey: Some(Box::new(move |req| { + let agent_clone = adapter_clone_3.clone(); + let tx_clone = tx_clone_3.clone(); + Box::pin(async move { + let device = match agent_clone.device(req.device) { + Ok(d) => d, + Err(_) => return Err(bluer::agent::ReqError::Rejected), + }; + let _ = tx_clone + .send(BluerSessionEvent::AgentEvent( + BluerAgentEvent::RequestPasskey( + BluerDevice::from_device(&device).await, + ), + )) + .await; + let mut rng = rand::thread_rng(); + let pin_code = rng.gen_range(0..999999); + Ok(pin_code) + }) + })), + display_passkey: Some(Box::new(move |req| { + let agent_clone = adapter_clone_4.clone(); + let tx_clone = tx_clone_4.clone(); + Box::pin(async move { + let device = match agent_clone.device(req.device) { + Ok(d) => d, + Err(_) => return Err(bluer::agent::ReqError::Rejected), + }; + let _ = tx_clone + .send(BluerSessionEvent::AgentEvent( + BluerAgentEvent::DisplayPasskey( + BluerDevice::from_device(&device).await, + format!("{:06}", req.passkey), + ), + )) + .await; + Ok(()) + }) + })), + request_confirmation: Some(Box::new(move |req| { + let agent_clone = adapter_clone_5.clone(); + let tx_clone = tx_clone_5.clone(); + Box::pin(async move { + let device = match agent_clone.device(req.device) { + Ok(d) => d, + Err(_) => return Err(bluer::agent::ReqError::Rejected), + }; + let (tx, mut rx) = channel(1); + let _ = tx_clone + .send(BluerSessionEvent::AgentEvent( + BluerAgentEvent::RequestConfirmation( + BluerDevice::from_device(&device).await, + format!("{:06}", req.passkey), + tx, + ), + )) + .await; + let res = rx.recv().await; + dbg!(res); + match res { + Some(res) if res => Ok(()), + _ => Err(bluer::agent::ReqError::Rejected), + } + }) + })), + request_authorization: Some(Box::new(move |req| { + let agent_clone = adapter_clone_6.clone(); + let tx_clone = tx_clone_6.clone(); + Box::pin(async move { + let device = match agent_clone.device(req.device) { + Ok(d) => d, + Err(_) => return Err(bluer::agent::ReqError::Rejected), + }; + let (tx, mut rx) = channel(1); + let _ = tx_clone + .send(BluerSessionEvent::AgentEvent( + BluerAgentEvent::RequestDeviceAuthorization( + BluerDevice::from_device(&device).await, + tx, + ), + )) + .await; + let res = rx.recv().await; + match res { + Some(res) if res => Ok(()), + _ => Err(bluer::agent::ReqError::Rejected), + } + }) + })), + authorize_service: Some(Box::new(move |req| { + let agent_clone = adapter_clone_7.clone(); + let tx_clone = tx_clone_7.clone(); + Box::pin(async move { + let device = match agent_clone.device(req.device) { + Ok(d) => d, + Err(_) => return Err(bluer::agent::ReqError::Rejected), + }; + let (tx, mut rx) = channel(1); + // TODO better describe the service to the user + let _ = tx_clone + .send(BluerSessionEvent::AgentEvent( + BluerAgentEvent::RequestServiceAuthorization( + BluerDevice::from_device(&device).await, + req.service, + tx, + ), + )) + .await; + let res = rx.recv().await; + match res { + Some(res) if res => Ok(()), + _ => Err(bluer::agent::ReqError::Rejected), + } + }) + })), + _non_exhaustive: (), + }; + let _agent_handle = session.register_agent(_agent).await?; let mut self_ = Self { - session, - adapter: adapter, + _agent_handle, + _session: session, + adapter, devices: Arc::new(Mutex::new(devices)), - rx: None, - tx: None, + rx: Some(rx), + tx, active_requests: Arc::new(Mutex::new(HashMap::new())), }; self_.process_changes(); self_.process_requests(request_rx); - Ok(self_) } - pub(crate) async fn devices(&self) -> Vec { - self.devices.lock().await.clone() - } - - pub(crate) async fn clear(&mut self) { - self.devices.lock().await.clear(); - } - - pub(crate) fn start_monitoring(&mut self) { - self.process_changes(); - } - + // Note: For some reason, this doesn't actually seem to work so well. it seems unreliable... pub(crate) fn process_changes(&mut self) { - let (tx, rx) = tokio::sync::mpsc::channel(100); - self.tx = Some(tx.clone()); + let tx = self.tx.clone(); let devices_clone = self.devices.clone(); let adapter_clone = self.adapter.clone(); let _monitor_devices: tokio::task::JoinHandle> = spawn(async move { let mut change_stream = adapter_clone.discover_devices_with_changes().await?; - let mut cur = None; let mut devices_changed = false; let mut milli_timeout = 10; 'outer: loop { @@ -233,7 +441,7 @@ impl BluerSessionState { Err(_) => continue, }; - let mut status = if device.is_connected().await? { + let status = if device.is_connected().await? { BluerDeviceStatus::Connected } else if device.is_paired().await? { BluerDeviceStatus::Paired @@ -241,10 +449,7 @@ impl BluerSessionState { BluerDeviceStatus::Disconnected }; - if let Some(pos) = - devices.iter().position(|device| device.address == address) - { - cur = Some(pos); + if devices.iter().any(|device| device.address == address) { continue; }; // only send a DevicesChanged event if we have actually added a device @@ -260,29 +465,22 @@ impl BluerSessionState { status, properties: Vec::new(), }); - cur = Some(devices.len() - 1); } bluer::AdapterEvent::DeviceRemoved(address) => { if let Some(pos) = devices.iter().position(|device| device.address == address) { devices_changed = true; - cur = None; devices.remove(pos); }; } - bluer::AdapterEvent::PropertyChanged(prop) => { - let bluer_device = match cur.and_then(|i| devices.get_mut(i)) { - Some(d) => d, - None => continue, - }; + bluer::AdapterEvent::PropertyChanged(_) => { devices_changed = true; } } } if devices_changed { devices_changed = false; - dbg!(&devices_clone); let _ = tx .send(BluerSessionEvent::ChangesProcessed(BluerState { devices: build_device_list(&adapter_clone).await, @@ -299,14 +497,13 @@ impl BluerSessionState { eprintln!("Change stream ended"); Ok(()) }); - self.rx.replace(rx); } pub(crate) fn process_requests(&self, request_rx: Receiver) { let active_requests = self.active_requests.clone(); let adapter = self.adapter.clone(); - let devices = self.devices.clone(); - let tx = self.tx.clone().unwrap(); // TODO error handling + let tx = self.tx.clone(); + let _handle: JoinHandle> = spawn(async move { let mut request_rx = request_rx; @@ -314,7 +511,6 @@ impl BluerSessionState { let req_clone = req.clone(); let req_clone_2 = req.clone(); let active_requests_clone = active_requests.clone(); - let devices_clone = devices.clone(); let tx_clone = tx.clone(); let adapter_clone = adapter.clone(); let handle = spawn(async move { @@ -373,6 +569,7 @@ impl BluerSessionState { err_msg = Some("No active connection request found".to_string()); } } + BluerRequest::StateUpdate => {} }; let state = BluerState { diff --git a/debian/control b/debian/control index afa01454..43b945c0 100644 --- a/debian/control +++ b/debian/control @@ -9,6 +9,7 @@ Build-Depends: cargo, libgtk-4-dev, libadwaita-1-dev, + libdbus-1-dev, libpulse-dev, just, pkg-config, diff --git a/justfile b/justfile index 9c24b7db..eb393e52 100644 --- a/justfile +++ b/justfile @@ -16,6 +16,7 @@ bindir := rootdir + prefix + '/bin' app_list_id := 'com.system76.CosmicAppList' audio_id := 'com.system76.CosmicAppletAudio' battery_id := 'com.system76.CosmicAppletBattery' +bluetooth_id := 'com.system76.CosmicAppletBluetooth' graphics_id := 'com.system76.CosmicAppletGraphics' network_id := 'com.system76.CosmicAppletNetwork' notifications_id := 'com.system76.CosmicAppletNotifications' @@ -88,6 +89,12 @@ install: install -Dm0644 cosmic-applet-battery/data/{{battery_id}}.desktop {{sharedir}}/applications/{{battery_id}}.desktop install -Dm0755 target/release/cosmic-applet-battery {{bindir}}/cosmic-applet-battery + # battery + install -Dm0644 cosmic-applet-bluetooth/data/icons/{{bluetooth_id}}.svg {{iconsdir}}/{{bluetooth_id}}.svg + install -Dm0644 cosmic-applet-bluetooth/data/{{bluetooth_id}}.desktop {{sharedir}}/applications/{{bluetooth_id}}.desktop + install -Dm0755 target/release/cosmic-applet-bluetooth {{bindir}}/cosmic-applet-bluetooth + + # Extracts vendored dependencies if vendor=1 _extract_vendor: #!/usr/bin/env sh