wayle-bluetooth 0.1.2

Bluetooth device management and discovery
Documentation
use std::{collections::HashMap, sync::Arc};

use tokio::sync::broadcast;
use tokio_util::sync::CancellationToken;
use tracing::warn;
use wayle_core::ROOT_PATH;
use wayle_traits::Reactive;
use zbus::{
    Connection,
    fdo::ObjectManagerProxy,
    names::OwnedInterfaceName,
    zvariant::{OwnedObjectPath, OwnedValue},
};

use super::{
    core::{
        adapter::{Adapter, LiveAdapterParams},
        device::{Device, LiveDeviceParams},
    },
    error::Error,
    types::{ADAPTER_INTERFACE, BLUEZ_SERVICE, DEVICE_INTERFACE, ServiceNotification},
};

pub(crate) struct BluetoothDiscovery {
    pub adapters: Vec<Arc<Adapter>>,
    pub primary_adapter: Option<Arc<Adapter>>,
    pub devices: Vec<Arc<Device>>,
    pub available: bool,
    pub enabled: bool,
    pub connected: Vec<String>,
}

impl BluetoothDiscovery {
    pub async fn new(
        connection: &Connection,
        cancellation_token: CancellationToken,
        notifier_tx: &broadcast::Sender<ServiceNotification>,
    ) -> Result<Self, Error> {
        let object_manager = ObjectManagerProxy::new(connection, BLUEZ_SERVICE, ROOT_PATH).await?;
        let managed_objects = object_manager
            .get_managed_objects()
            .await
            .map_err(Error::Discovery)?;

        let mut adapters = Vec::new();
        let mut devices = Vec::new();

        for (object_path, interfaces) in managed_objects {
            Self::extract_adapter(
                &mut adapters,
                connection,
                cancellation_token.child_token(),
                object_path.clone(),
                interfaces.clone(),
            )
            .await;
            Self::extract_device(
                &mut devices,
                connection,
                cancellation_token.child_token(),
                object_path,
                interfaces,
                notifier_tx,
            )
            .await;
        }

        let primary_adapter = adapters
            .iter()
            .find(|adapter| adapter.powered.get())
            .or_else(|| adapters.first())
            .cloned();
        let available = primary_adapter.as_ref().is_some();
        let enabled = primary_adapter
            .as_ref()
            .is_some_and(|adapter| adapter.powered.get());
        let connected = devices
            .iter()
            .filter_map(|device| {
                if device.connected.get() {
                    Some(device.address.get())
                } else {
                    None
                }
            })
            .collect();

        Ok(Self {
            adapters,
            devices,
            primary_adapter,
            available,
            enabled,
            connected,
        })
    }

    async fn extract_adapter(
        adapters: &mut Vec<Arc<Adapter>>,
        connection: &Connection,
        cancellation_token: CancellationToken,
        object_path: OwnedObjectPath,
        interfaces: HashMap<OwnedInterfaceName, HashMap<String, OwnedValue>>,
    ) -> () {
        if !interfaces.contains_key(ADAPTER_INTERFACE) {
            return;
        }

        match Adapter::get_live(LiveAdapterParams {
            connection,
            path: object_path.clone(),
            cancellation_token: &cancellation_token,
        })
        .await
        {
            Ok(adapter) => adapters.push(adapter),
            Err(error) => {
                warn!(error = %error, path = %object_path, "cannot create adapter");
            }
        }
    }

    async fn extract_device(
        devices: &mut Vec<Arc<Device>>,
        connection: &Connection,
        cancellation_token: CancellationToken,
        object_path: OwnedObjectPath,
        interfaces: HashMap<OwnedInterfaceName, HashMap<String, OwnedValue>>,
        notifier_tx: &broadcast::Sender<ServiceNotification>,
    ) -> () {
        if !interfaces.contains_key(DEVICE_INTERFACE) {
            return;
        }

        match Device::get_live(LiveDeviceParams {
            connection,
            path: object_path.clone(),
            cancellation_token: &cancellation_token,
            notifier_tx,
        })
        .await
        {
            Ok(device) => devices.push(device),
            Err(error) => {
                warn!(error = %error, path = %object_path, "cannot create device");
            }
        }
    }
}