azalea_shell/service/dbus/bluez/
mod.rs

1use std::collections::HashMap;
2
3use bluer::DeviceEvent;
4use futures_lite::StreamExt;
5use tokio::sync::broadcast;
6
7#[derive(azalea_derive::StaticServiceManager)]
8pub struct Service {
9    session: bluer::Session,
10    adapter: bluer::Adapter,
11    devices: HashMap<String, bluer::Device>,
12}
13
14#[derive(Default, Clone, Debug)]
15pub struct Device {
16    pub address: String,
17    pub name: Option<String>,
18    pub is_connected: bool,
19    pub icon: Option<String>,
20}
21
22#[derive(Default, Clone)]
23pub struct Init {}
24
25pub type AdapterName = String;
26pub type DeviceAdress = String;
27
28#[derive(Clone, Debug)]
29pub enum Input {
30    Power(bool),
31    Adapters(flume::Sender<Vec<AdapterName>>),
32    Devices(flume::Sender<HashMap<String, Device>>),
33    Connect(DeviceAdress, bool),
34}
35
36pub enum Event {}
37
38#[derive(Clone, Debug)]
39pub enum Output {
40    Connected(DeviceAdress, bool),
41    Powered(bool),
42}
43
44impl azalea_service::Service for Service {
45    type Init = Init;
46    type Input = Input;
47    type Event = ();
48    type Output = Output;
49    const DISABLE_EVENTS: bool = true;
50
51    async fn new(
52        _init: Self::Init,
53        _input: flume::Sender<Self::Input>,
54        output_sender: broadcast::Sender<Self::Output>,
55    ) -> Self {
56        let session = bluer::Session::new().await.unwrap();
57        let adapter = session.default_adapter().await.unwrap();
58        let devices =
59            futures_lite::stream::iter(adapter.device_addresses().await.unwrap_or_default())
60                .then(|addr| {
61                    let adapter = adapter.clone();
62                    let device = adapter.device(addr).unwrap();
63                    let device_clone = device.clone();
64                    let sender = output_sender.clone();
65                    relm4::spawn(async move {
66                        listen_to_player(device_clone, sender).await;
67                    });
68                    async move { (addr.to_string(), device) }
69                })
70                .collect::<HashMap<String, bluer::Device>>()
71                .await;
72
73        Self {
74            session,
75            adapter,
76            devices,
77        }
78    }
79
80    async fn message(
81        &mut self,
82        input: Self::Input,
83        output_sender: &broadcast::Sender<Self::Output>,
84    ) {
85        match input {
86            Input::Power(on) => {
87                if let Err(e) = self.adapter.set_powered(on).await {
88                    azalea_log::warning!(
89                        "Could not power off bluetooth adapter {}: {}",
90                        self.adapter.name(),
91                        e
92                    );
93                } else {
94                    drop(output_sender.send(Output::Powered(on)));
95                }
96            }
97            Input::Adapters(sender) => {
98                let names = self.session.adapter_names().await.unwrap_or_default();
99                drop(sender.send(names));
100            }
101            Input::Devices(sender) => drop(
102                sender.send(
103                    futures_lite::stream::iter(self.devices.iter())
104                        .then(|(address, device)| async move {
105                            let address = address.to_string();
106                            (
107                                address.clone(),
108                                Device {
109                                    address,
110                                    name: device.name().await.unwrap_or(None),
111                                    is_connected: device.is_connected().await.unwrap_or(false),
112                                    icon: device.icon().await.unwrap_or(None),
113                                },
114                            )
115                        })
116                        .collect::<HashMap<String, Device>>()
117                        .await,
118                ),
119            ),
120            Input::Connect(device_address, connect) => match self.devices.get(&device_address) {
121                Some(device) => {
122                    if connect {
123                        if let Ok(_) = device.connect().await {
124                            drop(output_sender.send(Output::Connected(device_address, connect)));
125                        }
126                    } else {
127                        if let Ok(_) = device.disconnect().await {
128                            drop(output_sender.send(Output::Connected(device_address, connect)));
129                        }
130                    }
131                }
132                None => todo!(),
133            },
134        }
135    }
136
137    async fn event_generator(&mut self) -> Self::Event {}
138
139    async fn event_handler(
140        &mut self,
141        _event: Self::Event,
142        _output_sender: &tokio::sync::broadcast::Sender<Self::Output>,
143    ) -> azalea_service::Result<()> {
144        Ok(())
145    }
146}
147
148async fn listen_to_player(device: bluer::Device, output_sender: broadcast::Sender<Output>) {
149    let address = device.address().to_string();
150    let mut event_stream = device.events().await.unwrap();
151    while let Some(event) = event_stream.next().await {
152        match event {
153            DeviceEvent::PropertyChanged(device_property) => match device_property {
154                bluer::DeviceProperty::Name(_) => {}
155                bluer::DeviceProperty::RemoteAddress(_address) => {}
156                bluer::DeviceProperty::AddressType(_address_type) => {}
157                bluer::DeviceProperty::Icon(_) => {}
158                bluer::DeviceProperty::Class(_) => {}
159                bluer::DeviceProperty::Appearance(_) => {}
160                bluer::DeviceProperty::Uuids(_hash_set) => {}
161                bluer::DeviceProperty::Paired(_) => {}
162                bluer::DeviceProperty::Connected(connected) => {
163                    drop(output_sender.send(Output::Connected(address.clone(), connected)));
164                }
165                bluer::DeviceProperty::Trusted(_) => {}
166                bluer::DeviceProperty::Blocked(_) => {}
167                bluer::DeviceProperty::WakeAllowed(_) => {}
168                bluer::DeviceProperty::Alias(_) => {}
169                bluer::DeviceProperty::LegacyPairing(_) => {}
170                bluer::DeviceProperty::Modalias(_modalias) => {}
171                bluer::DeviceProperty::Rssi(_) => {}
172                bluer::DeviceProperty::TxPower(_) => {}
173                bluer::DeviceProperty::ManufacturerData(_hash_map) => {}
174                bluer::DeviceProperty::ServiceData(_hash_map) => {}
175                bluer::DeviceProperty::ServicesResolved(_) => {}
176                bluer::DeviceProperty::AdvertisingFlags(_items) => {}
177                bluer::DeviceProperty::AdvertisingData(_hash_map) => {}
178                bluer::DeviceProperty::BatteryPercentage(_) => {}
179                _ => {}
180            },
181        }
182    }
183}