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>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// 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::{
|
use cosmic::{
|
||||||
app,
|
app,
|
||||||
applet::token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription},
|
applet::token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription},
|
||||||
|
|
@ -112,6 +112,8 @@ impl cosmic::Application for CosmicBluetoothApplet {
|
||||||
match message {
|
match message {
|
||||||
Message::TogglePopup => {
|
Message::TogglePopup => {
|
||||||
if let Some(p) = self.popup.take() {
|
if let Some(p) = self.popup.take() {
|
||||||
|
set_discovery(false);
|
||||||
|
|
||||||
return Task::batch([
|
return Task::batch([
|
||||||
destroy_popup(p),
|
destroy_popup(p),
|
||||||
cosmic::task::future(
|
cosmic::task::future(
|
||||||
|
|
@ -120,6 +122,8 @@ impl cosmic::Application for CosmicBluetoothApplet {
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
|
set_discovery(true);
|
||||||
|
|
||||||
// TODO request update of state maybe
|
// TODO request update of state maybe
|
||||||
let new_id = window::Id::unique();
|
let new_id = window::Id::unique();
|
||||||
self.popup.replace(new_id);
|
self.popup.replace(new_id);
|
||||||
|
|
@ -133,16 +137,7 @@ impl cosmic::Application for CosmicBluetoothApplet {
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
let tx = self.bluer_sender.clone();
|
|
||||||
return Task::batch([
|
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),
|
get_popup(popup_settings),
|
||||||
cosmic::task::future(set_tick(Duration::from_secs(3)))
|
cosmic::task::future(set_tick(Duration::from_secs(3)))
|
||||||
.map(|()| cosmic::Action::App(Message::Ignore)),
|
.map(|()| cosmic::Action::App(Message::Ignore)),
|
||||||
|
|
@ -173,20 +168,6 @@ impl cosmic::Application for CosmicBluetoothApplet {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.bluer_state = 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() =>
|
|
||||||
{
|
|
||||||
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 } => {
|
BluerEvent::Init { sender, state } => {
|
||||||
self.bluer_sender.replace(sender);
|
self.bluer_sender.replace(sender);
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,19 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::{HashMap, HashSet},
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
mem,
|
mem,
|
||||||
sync::{Arc, LazyLock},
|
sync::{
|
||||||
|
Arc, LazyLock,
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bluer::{
|
use bluer::{
|
||||||
Adapter, Address, Session, Uuid,
|
Adapter, AdapterProperty, Address, Session, Uuid,
|
||||||
agent::{Agent, AgentHandle},
|
agent::{Agent, AgentHandle},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -34,6 +37,11 @@ use tokio::{
|
||||||
};
|
};
|
||||||
|
|
||||||
static TICK: LazyLock<RwLock<Duration>> = LazyLock::new(|| RwLock::new(Duration::from_secs(10)));
|
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) {
|
pub async fn set_tick(duration: Duration) {
|
||||||
let mut guard = TICK.write().await;
|
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
|
// reconnect to paired and trusted devices
|
||||||
if state.bluetooth_enabled {
|
if state.bluetooth_enabled {
|
||||||
for d in &state.devices {
|
for d in &state.devices {
|
||||||
if d.paired_and_trusted() {
|
if d.paired_and_trusted() && !matches!(d.status, BluerDeviceStatus::Connected) {
|
||||||
_ = session_state
|
_ = session_state
|
||||||
.req_tx
|
.req_tx
|
||||||
.send(BluerRequest::ConnectDevice(d.address))
|
.send(BluerRequest::ConnectDevice(d.address))
|
||||||
|
|
@ -184,7 +192,6 @@ pub enum BluerRequest {
|
||||||
ConnectDevice(Address),
|
ConnectDevice(Address),
|
||||||
DisconnectDevice(Address),
|
DisconnectDevice(Address),
|
||||||
CancelConnect(Address),
|
CancelConnect(Address),
|
||||||
StateUpdate,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -579,26 +586,28 @@ impl BluerSessionState {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn process_changes(&mut self) {
|
fn process_changes(&mut self) {
|
||||||
let tx = self.tx.clone();
|
|
||||||
let req_tx = self.req_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 {
|
let Some(mut wake_up) = self.wake_up_discover_rx.take() else {
|
||||||
tracing::error!("Failed to take wake up channel");
|
tracing::error!("Failed to take wake up channel");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let adapter_clone = self.adapter.clone();
|
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 {
|
_ = spawn(async move {
|
||||||
interval.tick().await;
|
let mut is_powered = adapter_clone.is_powered().await.unwrap_or_default();
|
||||||
let wakeup_fut = wake_up.recv();
|
|
||||||
|
|
||||||
// Listens for process changes and builds edvice lists.
|
let mut devices: Vec<BluerDevice> = Vec::new();
|
||||||
let listener_fut = async {
|
let mut interval = tokio::time::interval(Duration::from_secs(1));
|
||||||
let mut new_devices = Vec::new();
|
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));
|
let mut interval = tokio::time::interval(Duration::from_secs(10));
|
||||||
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
||||||
let Ok(mut change_stream) =
|
let Ok(mut change_stream) =
|
||||||
|
|
@ -608,41 +617,91 @@ impl BluerSessionState {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
while change_stream.next().await.is_some() {
|
loop {
|
||||||
new_devices = build_device_list(new_devices, &adapter_clone).await;
|
let Some(adapter_event) = change_stream.next().await else {
|
||||||
for d in new_devices
|
break;
|
||||||
.iter()
|
};
|
||||||
.filter(|d| !devices.contains(d) && d.paired_and_trusted())
|
match adapter_event {
|
||||||
{
|
bluer::AdapterEvent::PropertyChanged(AdapterProperty::Powered(
|
||||||
_ = req_tx.send(BluerRequest::ConnectDevice(d.address)).await;
|
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
|
let _ = tx
|
||||||
.send(BluerSessionEvent::ChangesProcessed(BluerState {
|
.send(BluerSessionEvent::ChangesProcessed(BluerState {
|
||||||
devices: new_devices.clone(),
|
devices: devices.clone(),
|
||||||
bluetooth_enabled: adapter_clone
|
bluetooth_enabled: is_powered,
|
||||||
.is_powered()
|
|
||||||
.await
|
|
||||||
.unwrap_or_default(),
|
|
||||||
}))
|
}))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
devices.clear();
|
interval.tick().await;
|
||||||
mem::swap(&mut new_devices, &mut devices);
|
if !DISCOVERY.load(Ordering::SeqCst) && !devices.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loop {
|
||||||
|
if DISCOVERY.load(Ordering::SeqCst) || devices.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
interval.tick().await;
|
interval.tick().await;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
futures::pin_mut!(listener_fut);
|
futures::pin_mut!(listener_fut);
|
||||||
futures::pin_mut!(wakeup_fut);
|
futures::pin_mut!(wakeup_fut);
|
||||||
|
|
||||||
futures::future::select(listener_fut, wakeup_fut).await;
|
futures::future::select(listener_fut, wakeup_fut).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[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 active_requests = self.active_requests.clone();
|
||||||
let adapter = self.adapter.clone();
|
let adapter = self.adapter.clone();
|
||||||
let tx = self.tx.clone();
|
let tx = self.tx.clone();
|
||||||
|
|
@ -752,7 +811,6 @@ impl BluerSessionState {
|
||||||
err_msg = Some("No active connection request found".to_string());
|
err_msg = Some("No active connection request found".to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BluerRequest::StateUpdate => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = tx_clone
|
let _ = tx_clone
|
||||||
|
|
@ -791,7 +849,14 @@ async fn bluer_state(adapter: &Adapter) -> BluerState {
|
||||||
|
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
async fn build_device_list(mut devices: Vec<BluerDevice>, adapter: &Adapter) -> Vec<BluerDevice> {
|
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();
|
devices.clear();
|
||||||
if addrs.len() > devices.capacity() {
|
if addrs.len() > devices.capacity() {
|
||||||
devices.reserve(addrs.len() - devices.capacity());
|
devices.reserve(addrs.len() - devices.capacity());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue