azalea_shell/service/dbus/bluez/
mod.rs1use 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}