fix(bluetooth): avoid device discovery when applet is not open
some devices' audio stutters while discovery is active
This commit is contained in:
parent
086f5016cc
commit
4dae45733e
3 changed files with 575 additions and 534 deletions
935
Cargo.lock
generated
935
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::bluetooth::{BluerDeviceStatus, BluerRequest, BluerState, set_tick};
|
||||
use crate::bluetooth::{BluerDeviceStatus, BluerRequest, BluerState, set_discovery, set_tick};
|
||||
use cosmic::{
|
||||
app,
|
||||
applet::token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription},
|
||||
|
|
@ -112,6 +112,8 @@ impl cosmic::Application for CosmicBluetoothApplet {
|
|||
match message {
|
||||
Message::TogglePopup => {
|
||||
if let Some(p) = self.popup.take() {
|
||||
set_discovery(false);
|
||||
|
||||
return Task::batch([
|
||||
destroy_popup(p),
|
||||
cosmic::task::future(
|
||||
|
|
@ -120,6 +122,8 @@ impl cosmic::Application for CosmicBluetoothApplet {
|
|||
),
|
||||
]);
|
||||
} else {
|
||||
set_discovery(true);
|
||||
|
||||
// TODO request update of state maybe
|
||||
let new_id = window::Id::unique();
|
||||
self.popup.replace(new_id);
|
||||
|
|
@ -133,16 +137,7 @@ impl cosmic::Application for CosmicBluetoothApplet {
|
|||
None,
|
||||
);
|
||||
|
||||
let tx = self.bluer_sender.clone();
|
||||
return Task::batch([
|
||||
iced::Task::perform(
|
||||
async {
|
||||
if let Some(tx) = tx {
|
||||
let _ = tx.send(BluerRequest::StateUpdate).await;
|
||||
}
|
||||
},
|
||||
|()| cosmic::action::app(Message::Ignore),
|
||||
),
|
||||
get_popup(popup_settings),
|
||||
cosmic::task::future(set_tick(Duration::from_secs(3)))
|
||||
.map(|()| cosmic::Action::App(Message::Ignore)),
|
||||
|
|
@ -173,20 +168,6 @@ impl cosmic::Application for CosmicBluetoothApplet {
|
|||
}
|
||||
|
||||
self.bluer_state = state;
|
||||
// TODO special handling for some requests
|
||||
match req {
|
||||
BluerRequest::StateUpdate
|
||||
if self.popup.is_some() && self.bluer_sender.is_some() =>
|
||||
{
|
||||
let tx = self.bluer_sender.clone().unwrap();
|
||||
tokio::spawn(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;
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
BluerEvent::Init { sender, state } => {
|
||||
self.bluer_sender.replace(sender);
|
||||
|
|
|
|||
|
|
@ -2,16 +2,19 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
mem,
|
||||
sync::{Arc, LazyLock},
|
||||
sync::{
|
||||
Arc, LazyLock,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use bluer::{
|
||||
Adapter, Address, Session, Uuid,
|
||||
Adapter, AdapterProperty, Address, Session, Uuid,
|
||||
agent::{Agent, AgentHandle},
|
||||
};
|
||||
|
||||
|
|
@ -34,6 +37,11 @@ use tokio::{
|
|||
};
|
||||
|
||||
static TICK: LazyLock<RwLock<Duration>> = LazyLock::new(|| RwLock::new(Duration::from_secs(10)));
|
||||
static DISCOVERY: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn set_discovery(v: bool) {
|
||||
DISCOVERY.store(v, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub async fn set_tick(duration: Duration) {
|
||||
let mut guard = TICK.write().await;
|
||||
|
|
@ -106,7 +114,7 @@ pub fn bluetooth_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
|||
// reconnect to paired and trusted devices
|
||||
if state.bluetooth_enabled {
|
||||
for d in &state.devices {
|
||||
if d.paired_and_trusted() {
|
||||
if d.paired_and_trusted() && !matches!(d.status, BluerDeviceStatus::Connected) {
|
||||
_ = session_state
|
||||
.req_tx
|
||||
.send(BluerRequest::ConnectDevice(d.address))
|
||||
|
|
@ -184,7 +192,6 @@ pub enum BluerRequest {
|
|||
ConnectDevice(Address),
|
||||
DisconnectDevice(Address),
|
||||
CancelConnect(Address),
|
||||
StateUpdate,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -579,26 +586,28 @@ impl BluerSessionState {
|
|||
|
||||
#[inline]
|
||||
fn process_changes(&mut self) {
|
||||
let tx = self.tx.clone();
|
||||
let req_tx = self.req_tx.clone();
|
||||
let tx = self.tx.clone();
|
||||
let Some(mut wake_up) = self.wake_up_discover_rx.take() else {
|
||||
tracing::error!("Failed to take wake up channel");
|
||||
return;
|
||||
};
|
||||
let adapter_clone = self.adapter.clone();
|
||||
let _monitor_devices: tokio::task::JoinHandle<Result<(), anyhow::Error>> =
|
||||
spawn(async move {
|
||||
let mut devices: Vec<BluerDevice> = Vec::new();
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(1));
|
||||
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
let wakeup_fut = wake_up.recv();
|
||||
_ = spawn(async move {
|
||||
let mut is_powered = adapter_clone.is_powered().await.unwrap_or_default();
|
||||
|
||||
// Listens for process changes and builds edvice lists.
|
||||
let listener_fut = async {
|
||||
let mut new_devices = Vec::new();
|
||||
let mut devices: Vec<BluerDevice> = Vec::new();
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(1));
|
||||
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
let wakeup_fut = wake_up.recv();
|
||||
|
||||
let listener_fut = async {
|
||||
if DISCOVERY.load(Ordering::SeqCst) || devices.is_empty() {
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(10));
|
||||
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
||||
let Ok(mut change_stream) =
|
||||
|
|
@ -608,41 +617,91 @@ impl BluerSessionState {
|
|||
return;
|
||||
};
|
||||
|
||||
while change_stream.next().await.is_some() {
|
||||
new_devices = build_device_list(new_devices, &adapter_clone).await;
|
||||
for d in new_devices
|
||||
.iter()
|
||||
.filter(|d| !devices.contains(d) && d.paired_and_trusted())
|
||||
{
|
||||
_ = req_tx.send(BluerRequest::ConnectDevice(d.address)).await;
|
||||
loop {
|
||||
let Some(adapter_event) = change_stream.next().await else {
|
||||
break;
|
||||
};
|
||||
match adapter_event {
|
||||
bluer::AdapterEvent::PropertyChanged(AdapterProperty::Powered(
|
||||
v,
|
||||
)) => {
|
||||
is_powered = v;
|
||||
}
|
||||
e => {
|
||||
match e {
|
||||
bluer::AdapterEvent::DeviceAdded(address)
|
||||
if !devices.iter().any(|d| d.address == address) =>
|
||||
{
|
||||
devices =
|
||||
build_device_list(Vec::new(), &adapter_clone).await;
|
||||
for d in devices.iter().filter(|d| {
|
||||
d.paired_and_trusted()
|
||||
&& !matches!(
|
||||
d.status,
|
||||
BluerDeviceStatus::Connected
|
||||
)
|
||||
}) {
|
||||
_ = req_tx
|
||||
.send(BluerRequest::ConnectDevice(d.address))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
bluer::AdapterEvent::DeviceRemoved(address)
|
||||
if devices.iter().any(|d| d.address == address) =>
|
||||
{
|
||||
// Remove the device from new_devices if it exists
|
||||
devices.retain(|d| d.address != address);
|
||||
}
|
||||
|
||||
bluer::AdapterEvent::PropertyChanged(p) => {
|
||||
tracing::info!("property change ignored {p:?}");
|
||||
interval.tick().await;
|
||||
continue;
|
||||
}
|
||||
bluer::AdapterEvent::DeviceAdded(address)
|
||||
| bluer::AdapterEvent::DeviceRemoved(address) => {
|
||||
tracing::info!(
|
||||
"device change that is already handled {address}"
|
||||
);
|
||||
interval.tick().await;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = tx
|
||||
.send(BluerSessionEvent::ChangesProcessed(BluerState {
|
||||
devices: new_devices.clone(),
|
||||
bluetooth_enabled: adapter_clone
|
||||
.is_powered()
|
||||
.await
|
||||
.unwrap_or_default(),
|
||||
devices: devices.clone(),
|
||||
bluetooth_enabled: is_powered,
|
||||
}))
|
||||
.await;
|
||||
|
||||
devices.clear();
|
||||
mem::swap(&mut new_devices, &mut devices);
|
||||
interval.tick().await;
|
||||
if !DISCOVERY.load(Ordering::SeqCst) && !devices.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
if DISCOVERY.load(Ordering::SeqCst) || devices.is_empty() {
|
||||
break;
|
||||
}
|
||||
interval.tick().await;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
futures::pin_mut!(listener_fut);
|
||||
futures::pin_mut!(wakeup_fut);
|
||||
futures::pin_mut!(listener_fut);
|
||||
futures::pin_mut!(wakeup_fut);
|
||||
|
||||
futures::future::select(listener_fut, wakeup_fut).await;
|
||||
}
|
||||
});
|
||||
futures::future::select(listener_fut, wakeup_fut).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn process_requests(&self, request_rx: Receiver<BluerRequest>) {
|
||||
fn process_requests(&mut self, request_rx: Receiver<BluerRequest>) {
|
||||
let active_requests = self.active_requests.clone();
|
||||
let adapter = self.adapter.clone();
|
||||
let tx = self.tx.clone();
|
||||
|
|
@ -752,7 +811,6 @@ impl BluerSessionState {
|
|||
err_msg = Some("No active connection request found".to_string());
|
||||
}
|
||||
}
|
||||
BluerRequest::StateUpdate => {}
|
||||
}
|
||||
|
||||
let _ = tx_clone
|
||||
|
|
@ -791,7 +849,14 @@ async fn bluer_state(adapter: &Adapter) -> BluerState {
|
|||
|
||||
#[inline(never)]
|
||||
async fn build_device_list(mut devices: Vec<BluerDevice>, adapter: &Adapter) -> Vec<BluerDevice> {
|
||||
let addrs = adapter.device_addresses().await.unwrap_or_default();
|
||||
let addrs: Vec<Address> = adapter
|
||||
.device_addresses()
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter(|addr| !devices.iter().any(|d| d.address == *addr))
|
||||
.collect();
|
||||
|
||||
devices.clear();
|
||||
if addrs.len() > devices.capacity() {
|
||||
devices.reserve(addrs.len() - devices.capacity());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue