Skip to main content

bluetooth_rust/
linux.rs

1//! Linux specific bluetooth code
2
3use std::collections::{BTreeMap, HashMap};
4
5use bluer::AdapterEvent;
6use futures::FutureExt;
7use futures::StreamExt;
8
9// ────────────────────────────────────────────────────────────────────────────
10// BluetoothRfcommConnectableAsyncTrait for bluer::rfcomm::ConnectRequest
11// ────────────────────────────────────────────────────────────────────────────
12
13impl super::BluetoothRfcommConnectableAsyncTrait for bluer::rfcomm::ConnectRequest {
14    async fn accept(self) -> Result<crate::BluetoothStream, String> {
15        bluer::rfcomm::ConnectRequest::accept(self)
16            .map(|a| crate::BluetoothStream::Bluez(Box::pin(a)))
17            .map_err(|e| e.to_string())
18    }
19}
20
21// ────────────────────────────────────────────────────────────────────────────
22// BluetoothRfcommProfileAsyncTrait for bluer::rfcomm::ProfileHandle
23// ────────────────────────────────────────────────────────────────────────────
24
25impl super::BluetoothRfcommProfileAsyncTrait for bluer::rfcomm::ProfileHandle {
26    async fn connectable(&mut self) -> Result<crate::BluetoothRfcommConnectableAsync, String> {
27        self.next()
28            .await
29            .map(|a| crate::BluetoothRfcommConnectableAsync::Bluez(a))
30            .ok_or_else(|| "Failed to get bluetooth connection".to_string())
31    }
32}
33
34// ────────────────────────────────────────────────────────────────────────────
35// Internal active-connection holder (RFCOMM or L2CAP stream)
36// ────────────────────────────────────────────────────────────────────────────
37
38/// Holds the active stream for either an RFCOMM or L2CAP connection.
39enum BluetoothConnection {
40    /// An active RFCOMM stream
41    Rfcomm(bluer::rfcomm::Stream),
42    /// An active L2CAP stream
43    L2cap(bluer::l2cap::Stream),
44}
45
46impl std::io::Read for BluetoothConnection {
47    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
48        tokio::task::block_in_place(|| {
49            tokio::runtime::Handle::current().block_on(async {
50                use tokio::io::AsyncReadExt;
51                match self {
52                    BluetoothConnection::Rfcomm(s) => s.read(buf).await,
53                    BluetoothConnection::L2cap(s) => s.read(buf).await,
54                }
55            })
56        })
57    }
58}
59
60impl std::io::Write for BluetoothConnection {
61    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
62        tokio::task::block_in_place(|| {
63            tokio::runtime::Handle::current().block_on(async {
64                use tokio::io::AsyncWriteExt;
65                match self {
66                    BluetoothConnection::Rfcomm(s) => s.write(buf).await,
67                    BluetoothConnection::L2cap(s) => s.write(buf).await,
68                }
69            })
70        })
71    }
72
73    fn flush(&mut self) -> std::io::Result<()> {
74        tokio::task::block_in_place(|| {
75            tokio::runtime::Handle::current().block_on(async {
76                use tokio::io::AsyncWriteExt;
77                match self {
78                    BluetoothConnection::Rfcomm(s) => s.flush().await,
79                    BluetoothConnection::L2cap(s) => s.flush().await,
80                }
81            })
82        })
83    }
84}
85
86// ────────────────────────────────────────────────────────────────────────────
87// BluetoothRfcommSocket – used for both RFCOMM and L2CAP outgoing connections
88// ────────────────────────────────────────────────────────────────────────────
89
90/// An outgoing bluetooth socket that may carry either an RFCOMM or L2CAP
91/// connection.  The socket is created lazily; call `connect()` before doing
92/// any I/O.
93pub struct BluetoothRfcommSocket {
94    /// Address of the remote device
95    device_addr: bluer::Address,
96    /// RFCOMM channel to connect on (mutually exclusive with `l2cap_psm`).
97    ///
98    /// BlueZ does not expose a direct SDP query API through the bluer crate, so
99    /// callers are responsible for resolving the correct channel via
100    /// `run_sdp` / `get_uuids` before creating the socket, or by relying on
101    /// a well-known channel number for the service.  Channel 1 is used as a
102    /// default placeholder when the channel is not otherwise known.
103    rfcomm_channel: Option<u8>,
104    /// L2CAP PSM to connect on (mutually exclusive with `rfcomm_channel`).
105    l2cap_psm: Option<u16>,
106    /// Whether to request an encrypted / authenticated link
107    is_secure: bool,
108    /// The live connection, present after a successful `connect()` call
109    connection: Option<BluetoothConnection>,
110}
111
112impl BluetoothRfcommSocket {
113    /// Create a new (unconnected) RFCOMM socket.
114    fn new_rfcomm(device_addr: bluer::Address, channel: u8, is_secure: bool) -> Self {
115        Self {
116            device_addr,
117            rfcomm_channel: Some(channel),
118            l2cap_psm: None,
119            is_secure,
120            connection: None,
121        }
122    }
123
124    /// Create a new (unconnected) L2CAP socket.
125    fn new_l2cap(device_addr: bluer::Address, psm: u16, is_secure: bool) -> Self {
126        Self {
127            device_addr,
128            rfcomm_channel: None,
129            l2cap_psm: Some(psm),
130            is_secure,
131            connection: None,
132        }
133    }
134}
135
136impl crate::BluetoothSocketTrait for &mut BluetoothRfcommSocket {
137    fn is_connected(&self) -> Result<bool, std::io::Error> {
138        Ok(self.connection.is_some())
139    }
140
141    fn connect(&mut self) -> Result<(), std::io::Error> {
142        if self.connection.is_some() {
143            return Ok(());
144        }
145        tokio::task::block_in_place(|| {
146            tokio::runtime::Handle::current().block_on(async {
147                if let Some(channel) = self.rfcomm_channel {
148                    let addr = bluer::rfcomm::SocketAddr::new(self.device_addr, channel);
149                    let socket = bluer::rfcomm::Socket::new()
150                        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
151                    if self.is_secure {
152                        socket
153                            .set_security(bluer::rfcomm::Security {
154                                level: bluer::rfcomm::SecurityLevel::Medium,
155                                key_size: 0,
156                            })
157                            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
158                    }
159                    let stream = socket
160                        .connect(addr)
161                        .await
162                        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
163                    self.connection = Some(BluetoothConnection::Rfcomm(stream));
164                } else if let Some(psm) = self.l2cap_psm {
165                    let addr = bluer::l2cap::SocketAddr::new(
166                        self.device_addr,
167                        bluer::AddressType::BrEdr,
168                        psm,
169                    );
170                    let socket = bluer::l2cap::Socket::<bluer::l2cap::Stream>::new_stream()
171                        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
172                    if self.is_secure {
173                        socket
174                            .set_security(bluer::l2cap::Security {
175                                level: bluer::l2cap::SecurityLevel::Medium,
176                                key_size: 0,
177                            })
178                            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
179                    }
180                    let stream = socket
181                        .connect(addr)
182                        .await
183                        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
184                    self.connection = Some(BluetoothConnection::L2cap(stream));
185                } else {
186                    return Err(std::io::Error::new(
187                        std::io::ErrorKind::InvalidInput,
188                        "BluetoothRfcommSocket has neither an RFCOMM channel nor an L2CAP PSM configured",
189                    ));
190                }
191                Ok(())
192            })
193        })
194    }
195}
196
197impl std::io::Read for BluetoothRfcommSocket {
198    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
199        match &mut self.connection {
200            Some(conn) => conn.read(buf),
201            None => Err(std::io::Error::from(std::io::ErrorKind::NotConnected)),
202        }
203    }
204}
205
206impl std::io::Write for BluetoothRfcommSocket {
207    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
208        match &mut self.connection {
209            Some(conn) => conn.write(buf),
210            None => Err(std::io::Error::from(std::io::ErrorKind::NotConnected)),
211        }
212    }
213
214    fn flush(&mut self) -> std::io::Result<()> {
215        match &mut self.connection {
216            Some(conn) => conn.flush(),
217            None => Err(std::io::Error::from(std::io::ErrorKind::NotConnected)),
218        }
219    }
220}
221
222// ────────────────────────────────────────────────────────────────────────────
223// LinuxBluetoothDevice – wraps bluer::Device and owns its open sockets
224// ────────────────────────────────────────────────────────────────────────────
225
226/// A Linux bluetooth device backed by bluer.  Wraps a `bluer::Device` and
227/// stores open RFCOMM / L2CAP sockets so that returned `BluetoothSocket`
228/// references remain valid for the lifetime of this struct.
229pub struct LinuxBluetoothDevice {
230    /// The underlying bluer device handle
231    device: bluer::Device,
232    /// Open sockets keyed by the UUID string of the service they connect to
233    sockets: BTreeMap<String, BluetoothRfcommSocket>,
234}
235
236impl LinuxBluetoothDevice {
237    /// Wrap a `bluer::Device`.
238    pub fn new(device: bluer::Device) -> Self {
239        Self {
240            device,
241            sockets: BTreeMap::new(),
242        }
243    }
244}
245
246impl super::BluetoothDeviceTrait for LinuxBluetoothDevice {
247    /// Trigger service-discovery for this device.
248    ///
249    /// BlueZ resolves SDP service records automatically when a device is
250    /// connected.  Calling this method ensures the device is connected so that
251    /// `get_uuids` returns fresh data.  Any connection errors are silently
252    /// ignored because the device may already be connected.
253    fn run_sdp(&mut self) {
254        let device = self.device.clone();
255        let _ = tokio::task::block_in_place(|| {
256            tokio::runtime::Handle::current().block_on(async move { device.connect().await })
257        });
258    }
259
260    fn get_uuids(&mut self) -> Result<Vec<crate::BluetoothUuid>, std::io::Error> {
261        tokio::task::block_in_place(|| {
262            tokio::runtime::Handle::current().block_on(async {
263                let uuids = self
264                    .device
265                    .uuids()
266                    .await
267                    .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
268                Ok(uuids
269                    .unwrap_or_default()
270                    .into_iter()
271                    .map(|u| {
272                        use std::str::FromStr;
273                        crate::BluetoothUuid::from_str(&u.to_string())
274                            .unwrap_or_else(|_| crate::BluetoothUuid::Unknown(u.to_string()))
275                    })
276                    .collect())
277            })
278        })
279    }
280
281    /// Returns the alias (display name) of the device, falling back to the
282    /// hardware name when no alias is set.
283    fn get_name(&self) -> Result<String, std::io::Error> {
284        let device = self.device.clone();
285        tokio::task::block_in_place(|| {
286            tokio::runtime::Handle::current().block_on(async move {
287                device
288                    .alias()
289                    .await
290                    .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))
291            })
292        })
293    }
294
295    fn get_address(&mut self) -> Result<String, std::io::Error> {
296        Ok(self.device.address().to_string())
297    }
298
299    fn get_pair_state(&self) -> Result<crate::PairingStatus, std::io::Error> {
300        let device = self.device.clone();
301        tokio::task::block_in_place(|| {
302            tokio::runtime::Handle::current().block_on(async move {
303                let paired = device
304                    .is_paired()
305                    .await
306                    .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
307                Ok(if paired {
308                    crate::PairingStatus::Paired
309                } else {
310                    crate::PairingStatus::NotPaired
311                })
312            })
313        })
314    }
315
316    /// Return a socket suitable for an outgoing L2CAP connection to the given
317    /// service UUID.
318    ///
319    /// The PSM for the service must be known in advance.  Because bluer does
320    /// not expose a raw SDP query API, a proper PSM lookup is left as a
321    /// `// TODO` below; the default PSM of 1 (SDP channel itself) is a
322    /// placeholder and will need to be replaced with the actual dynamic PSM
323    /// discovered via SDP for real services.
324    fn get_l2cap_socket(
325        &mut self,
326        uuid: crate::BluetoothUuid,
327        is_secure: bool,
328    ) -> Result<crate::BluetoothSocket<'_>, String> {
329        let uuid_str = uuid.as_str().to_string();
330        if !self.sockets.contains_key(&uuid_str) {
331            // TODO: perform a proper SDP query to resolve the PSM for `uuid`.
332            // For now a PSM of 1 is used as a placeholder.
333            let psm: u16 = 1;
334            let addr = self.device.address();
335            let socket = BluetoothRfcommSocket::new_l2cap(addr, psm, is_secure);
336            self.sockets.insert(uuid_str.clone(), socket);
337        }
338        self.sockets
339            .get_mut(&uuid_str)
340            .map(|s| crate::BluetoothSocket::Bluez(s))
341            .ok_or_else(|| "Socket does not exist".to_string())
342    }
343
344    /// Return a socket suitable for an outgoing RFCOMM connection to the given
345    /// service UUID.
346    ///
347    /// The RFCOMM channel for the service must be known in advance.  Because
348    /// bluer does not expose a raw SDP query API, a proper channel lookup is
349    /// left as a `// TODO` below; channel 1 is used as a placeholder and will
350    /// need to be replaced with the channel discovered via SDP for real
351    /// services (e.g. by using `sdptool` or an external SDP library).
352    fn get_rfcomm_socket(
353        &mut self,
354        uuid: crate::BluetoothUuid,
355        is_secure: bool,
356    ) -> Result<crate::BluetoothSocket<'_>, String> {
357        let uuid_str = uuid.as_str().to_string();
358        if !self.sockets.contains_key(&uuid_str) {
359            // TODO: perform a proper SDP query to resolve the RFCOMM channel
360            // for `uuid`.  Channel 1 is used here as a placeholder.
361            let channel: u8 = 1;
362            let addr = self.device.address();
363            let socket = BluetoothRfcommSocket::new_rfcomm(addr, channel, is_secure);
364            self.sockets.insert(uuid_str.clone(), socket);
365        }
366        self.sockets
367            .get_mut(&uuid_str)
368            .map(|s| crate::BluetoothSocket::Bluez(s))
369            .ok_or_else(|| "Socket does not exist".to_string())
370    }
371}
372
373// ────────────────────────────────────────────────────────────────────────────
374// BluetoothDiscovery
375// ────────────────────────────────────────────────────────────────────────────
376
377/// A struct for managing discovery of bluetooth devices
378pub struct BluetoothDiscovery {}
379
380impl BluetoothDiscovery {
381    /// Construct a new self
382    fn new() -> Self {
383        Self {}
384    }
385}
386
387impl super::BluetoothDiscoveryTrait for BluetoothDiscovery {}
388
389impl Drop for BluetoothDiscovery {
390    fn drop(&mut self) {}
391}
392
393// ────────────────────────────────────────────────────────────────────────────
394// TryFrom conversions for profile settings → bluer::rfcomm::Profile
395// ────────────────────────────────────────────────────────────────────────────
396
397impl TryFrom<super::BluetoothRfcommProfileSettings> for bluer::rfcomm::Profile {
398    type Error = String;
399    fn try_from(value: super::BluetoothRfcommProfileSettings) -> Result<Self, Self::Error> {
400        let service = if let Some(v) = value.service_uuid {
401            Some(bluer::Uuid::parse_str(&v).map_err(|e| e.to_string())?)
402        } else {
403            None
404        };
405        Ok(Self {
406            uuid: bluer::Uuid::parse_str(&value.uuid).map_err(|e| e.to_string())?,
407            name: value.name,
408            service,
409            role: None,
410            channel: value.channel,
411            psm: value.psm,
412            require_authentication: value.authenticate,
413            require_authorization: value.authorize,
414            auto_connect: value.auto_connect,
415            service_record: value.sdp_record,
416            version: value.sdp_version,
417            features: value.sdp_features,
418            ..Default::default()
419        })
420    }
421}
422
423/// L2CAP profiles are registered through the same BlueZ D-Bus
424/// ProfileManager1 mechanism as RFCOMM profiles, but with the `psm` field
425/// set instead of `channel`.
426impl TryFrom<super::BluetoothL2capProfileSettings> for bluer::rfcomm::Profile {
427    type Error = String;
428    fn try_from(value: super::BluetoothL2capProfileSettings) -> Result<Self, Self::Error> {
429        let service = if let Some(v) = value.service_uuid {
430            Some(bluer::Uuid::parse_str(&v).map_err(|e| e.to_string())?)
431        } else {
432            None
433        };
434        Ok(Self {
435            uuid: bluer::Uuid::parse_str(&value.uuid).map_err(|e| e.to_string())?,
436            name: value.name,
437            service,
438            role: None,
439            channel: None,
440            psm: value.psm,
441            require_authentication: value.authenticate,
442            require_authorization: value.authorize,
443            auto_connect: value.auto_connect,
444            service_record: value.sdp_record,
445            version: value.sdp_version,
446            features: value.sdp_features,
447            ..Default::default()
448        })
449    }
450}
451
452// ────────────────────────────────────────────────────────────────────────────
453// BluetoothHandler – main adapter / session manager
454// ────────────────────────────────────────────────────────────────────────────
455
456/// The general bluetooth handler for the library. There should be only one per application on linux.
457pub struct BluetoothHandler {
458    /// The current bluetooth session
459    session: bluer::Session,
460    /// The list of bluetooth adapters for the system
461    adapters: Vec<bluer::Adapter>,
462    /// The agent for the handler
463    _blue_agent_handle: bluer::agent::AgentHandle,
464}
465
466impl super::BluetoothAdapterTrait for BluetoothHandler {
467    fn supports_async(&mut self) -> Option<&mut dyn super::AsyncBluetoothAdapterTrait> {
468        Some(self)
469    }
470
471    fn supports_sync(&mut self) -> Option<&mut dyn super::SyncBluetoothAdapterTrait> {
472        None
473    }
474}
475
476#[async_trait::async_trait]
477impl super::AsyncBluetoothAdapterTrait for BluetoothHandler {
478    async fn register_rfcomm_profile(
479        &self,
480        settings: super::BluetoothRfcommProfileSettings,
481    ) -> Result<crate::BluetoothRfcommProfileAsync, String> {
482        self.session
483            .register_profile(settings.try_into()?)
484            .await
485            .map(|a| super::BluetoothRfcommProfileAsync::Bluez(a.into()))
486            .map_err(|e| e.to_string())
487    }
488
489    /// Register an L2CAP profile with BlueZ.
490    ///
491    /// BlueZ exposes a single `RegisterProfile` D-Bus method (ProfileManager1)
492    /// that handles both RFCOMM and L2CAP profiles.  The `psm` field in the
493    /// settings selects L2CAP; the RFCOMM `channel` field is left as `None`.
494    async fn register_l2cap_profile(
495        &self,
496        settings: super::BluetoothL2capProfileSettings,
497    ) -> Result<crate::BluetoothL2capProfileAsync, String> {
498        self.session
499            .register_profile(settings.try_into()?)
500            .await
501            .map(|a| super::BluetoothL2capProfileAsync::Bluez(a.into()))
502            .map_err(|e| e.to_string())
503    }
504
505    fn start_discovery(&self) -> crate::BluetoothDiscovery {
506        BluetoothDiscovery::new().into()
507    }
508
509    /// Return all paired devices across every adapter.
510    fn get_paired_devices(&self) -> Option<Vec<crate::BluetoothDevice>> {
511        let mut list = Vec::new();
512        for adapter in &self.adapters {
513            let result = tokio::task::block_in_place(|| {
514                tokio::runtime::Handle::current().block_on(async {
515                    let addrs = adapter.device_addresses().await?;
516                    let mut paired = Vec::new();
517                    for addr in addrs {
518                        if let Ok(dev) = adapter.device(addr) {
519                            if dev.is_paired().await.unwrap_or(false) {
520                                paired.push(dev);
521                            }
522                        }
523                    }
524                    Ok::<Vec<bluer::Device>, bluer::Error>(paired)
525                })
526            });
527            if let Ok(devices) = result {
528                for dev in devices {
529                    list.push(crate::BluetoothDevice::Bluez(LinuxBluetoothDevice::new(dev)));
530                }
531            }
532        }
533        Some(list)
534    }
535
536    async fn addresses(&self) -> Vec<super::BluetoothAdapterAddress> {
537        let mut a = Vec::new();
538        for adapter in &self.adapters {
539            if let Ok(adr) = adapter.address().await {
540                a.push(super::BluetoothAdapterAddress::Byte(adr.0));
541            }
542        }
543        a
544    }
545
546    async fn set_discoverable(&self, d: bool) -> Result<(), ()> {
547        for adapter in &self.adapters {
548            adapter.set_discoverable(d).await.map_err(|_| ())?;
549        }
550        Ok(())
551    }
552}
553
554impl BluetoothHandler {
555    /// Retrieve the bluetooth addresses for all bluetooth adapters present
556    pub async fn addresses(&self) -> Vec<bluer::Address> {
557        let mut addrs = Vec::new();
558        for a in &self.adapters {
559            if let Ok(addr) = a.address().await {
560                addrs.push(addr);
561            }
562        }
563        addrs
564    }
565
566    /// Construct a new self
567    pub async fn new(
568        s: tokio::sync::mpsc::Sender<super::MessageToBluetoothHost>,
569    ) -> Result<Self, String> {
570        let session = bluer::Session::new().await.map_err(|e| e.to_string())?;
571
572        let adapter_names = session.adapter_names().await.map_err(|e| e.to_string())?;
573        let adapters: Vec<bluer::Adapter> = adapter_names
574            .iter()
575            .filter_map(|n| session.adapter(n).ok())
576            .collect();
577
578        let blue_agent = Self::build_agent(s);
579        let blue_agent_handle = session.register_agent(blue_agent).await;
580        println!("Registered a bluetooth agent {}", blue_agent_handle.is_ok());
581        Ok(Self {
582            session,
583            adapters,
584            _blue_agent_handle: blue_agent_handle.map_err(|e| e.to_string())?,
585        })
586    }
587
588    /// Enable all bluetooth adapters
589    async fn enable(&mut self) {
590        for adapter in &self.adapters {
591            adapter.set_powered(true).await.unwrap();
592            adapter.set_pairable(true).await.unwrap();
593        }
594    }
595
596    /// Disable all bluetooth adapters
597    async fn disable(&mut self) {
598        self.set_discoverable(false).await;
599        for adapter in &self.adapters {
600            adapter.set_powered(false).await.unwrap();
601            adapter.set_pairable(false).await.unwrap();
602        }
603    }
604
605    /// Enable or disable discoverable mode on all bluetooth adapters
606    pub async fn set_discoverable(&mut self, d: bool) {
607        for adapter in &self.adapters {
608            adapter.set_discoverable(d).await.unwrap();
609        }
610    }
611
612    /// Register an RFCOMM profile with the bluetooth session
613    pub async fn register_rfcomm_profile(
614        &mut self,
615        profile: bluer::rfcomm::Profile,
616    ) -> Result<bluer::rfcomm::ProfileHandle, bluer::Error> {
617        self.session.register_profile(profile).await
618    }
619
620    /// Build a bluetooth agent for the handler
621    fn build_agent(
622        s: tokio::sync::mpsc::Sender<super::MessageToBluetoothHost>,
623    ) -> bluer::agent::Agent {
624        let mut blue_agent = bluer::agent::Agent::default();
625        blue_agent.request_default = true;
626        blue_agent.request_pin_code = None;
627        blue_agent.request_passkey = None;
628        let s2 = s.clone();
629        blue_agent.display_passkey = Some(Box::new(move |mut a| {
630            println!("Running process for display_passkey: {:?}", a);
631            let s3 = s2.clone();
632            async move {
633                let mut chan = tokio::sync::mpsc::channel(5);
634                let _ = s3
635                    .send(super::MessageToBluetoothHost::DisplayPasskey(a.passkey, chan.0))
636                    .await;
637                loop {
638                    let f = tokio::time::timeout(std::time::Duration::from_secs(5), chan.1.recv());
639                    tokio::select! {
640                        asdf = f => {
641                            match asdf {
642                                Ok(Some(m)) => match m {
643                                    super::ResponseToPasskey::Yes => {
644                                        let _ = s3
645                                            .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
646                                            .await;
647                                        return Ok(());
648                                    }
649                                    super::ResponseToPasskey::No => {
650                                        let _ = s3
651                                            .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
652                                            .await;
653                                        return Err(bluer::agent::ReqError::Rejected);
654                                    }
655                                    super::ResponseToPasskey::Cancel => {
656                                        let _ = s3
657                                            .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
658                                            .await;
659                                        return Err(bluer::agent::ReqError::Canceled);
660                                    }
661                                    super::ResponseToPasskey::Waiting => {}
662                                },
663                                Ok(None) => {}
664                                _ => {
665                                    let _ = s3
666                                        .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
667                                        .await;
668                                    return Err(bluer::agent::ReqError::Canceled);
669                                }
670                            }
671                        }
672                        _ = &mut a.cancel => {
673                            let _ = s3
674                                .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
675                                .await;
676                            break Err(bluer::agent::ReqError::Canceled);
677                        }
678                    }
679                }
680            }
681            .boxed()
682        }));
683        blue_agent.display_pin_code = Some(Box::new(|a| {
684            async move {
685                println!("Need to display pin code {:?}", a);
686                a.cancel.await.unwrap();
687                Ok(())
688            }
689            .boxed()
690        }));
691        let s2 = s.clone();
692        blue_agent.request_confirmation = Some(Box::new(move |a| {
693            println!("Need to confirm {:?}", a);
694            let s3 = s2.clone();
695            async move {
696                let mut chan = tokio::sync::mpsc::channel(5);
697                let _ = s3
698                    .send(super::MessageToBluetoothHost::ConfirmPasskey(
699                        a.passkey, chan.0,
700                    ))
701                    .await;
702                loop {
703                    let f = tokio::time::timeout(std::time::Duration::from_secs(5), chan.1.recv());
704                    let asdf = f.await;
705                    match asdf {
706                        Ok(Some(m)) => match m {
707                            super::ResponseToPasskey::Yes => {
708                                let _ = s3
709                                    .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
710                                    .await;
711                                return Ok(());
712                            }
713                            super::ResponseToPasskey::No => {
714                                let _ = s3
715                                    .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
716                                    .await;
717                                return Err(bluer::agent::ReqError::Rejected);
718                            }
719                            super::ResponseToPasskey::Cancel => {
720                                let _ = s3
721                                    .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
722                                    .await;
723                                return Err(bluer::agent::ReqError::Canceled);
724                            }
725                            super::ResponseToPasskey::Waiting => {}
726                        },
727                        Ok(None) => {}
728                        _ => {
729                            let _ = s3
730                                .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
731                                .await;
732                            return Err(bluer::agent::ReqError::Canceled);
733                        }
734                    }
735                }
736            }
737            .boxed()
738        }));
739        blue_agent.request_authorization = Some(Box::new(|a| {
740            async move {
741                println!("Need to authorize {:?}", a);
742                Ok(())
743            }
744            .boxed()
745        }));
746        blue_agent.authorize_service = Some(Box::new(|a| {
747            async move {
748                println!("Need to authorize service {:?}", a);
749                Ok(())
750            }
751            .boxed()
752        }));
753        blue_agent
754    }
755
756    /// Issues the specified bluetooth command, with an optional response for the command
757    pub async fn issue_command(
758        &mut self,
759        cmd: super::BluetoothCommand,
760    ) -> Option<super::BluetoothResponse> {
761        match cmd {
762            super::BluetoothCommand::QueryNumAdapters => {
763                Some(super::BluetoothResponse::Adapters(self.adapters.len()))
764            }
765            _ => None,
766        }
767    }
768
769    /// Run a scan on all the bluetooth adapters, updating `bluetooth_devices`
770    /// with newly discovered or removed devices.
771    pub async fn scan<'a>(
772        &'a mut self,
773        bluetooth_devices: &mut HashMap<
774            bluer::Address,
775            (&'a bluer::Adapter, Option<bluer::Device>),
776        >,
777    ) {
778        let mut adapter_scanner = Vec::new();
779        for a in &self.adapters {
780            let da = a.discover_devices_with_changes().await.unwrap();
781            adapter_scanner.push((a, da));
782        }
783
784        for (adapt, da) in &mut adapter_scanner {
785            if let Some(e) = da.next().await {
786                match e {
787                    AdapterEvent::DeviceAdded(addr) => {
788                        log::debug!("Device added {:?}", addr);
789                        bluetooth_devices.insert(addr, (adapt, None));
790                    }
791                    AdapterEvent::DeviceRemoved(addr) => {
792                        log::debug!("Device removed {:?}", addr);
793                        bluetooth_devices.remove_entry(&addr);
794                    }
795                    AdapterEvent::PropertyChanged(prop) => {
796                        log::debug!("Adapter property changed {:?}", prop);
797                    }
798                }
799            }
800        }
801
802        for (addr, (adapter, dev)) in bluetooth_devices.iter_mut() {
803            if dev.is_none() {
804                if let Ok(d) = adapter.device(*addr) {
805                    if let Ok(ps) = d.all_properties().await {
806                        for p in ps {
807                            log::debug!("Device {:?} property: {:?}", addr, p);
808                        }
809                    }
810                    *dev = Some(d);
811                }
812            }
813        }
814    }
815}