async_hid/
device_info.rs

1use std::hash::Hash;
2use std::ops::Deref;
3use std::sync::Arc;
4
5use futures_lite::{Stream, StreamExt};
6use static_assertions::assert_impl_all;
7
8use crate::backend::{Backend, BackendType, DynBackend};
9use crate::{DeviceReader, DeviceReaderWriter, DeviceWriter, HidResult};
10
11/// A platform-specific identifier for a device.
12///
13/// Can be used as opaque type for equality checks or inspected with platform specific code:
14/// ```no_run
15/// # use async_hid::DeviceId;
16/// let id: DeviceId = /* ... */
17/// # panic!();
18/// match(id) {
19///    #[cfg(target_os = "windows")]
20///     DeviceId::UncPath(path) => { /* .. */ },
21///     #[cfg(target_os = "linux")]
22///     DeviceId::DevPath(path) => { /* .. */ },
23///     #[cfg(target_os = "macos")]
24///     DeviceId::RegistryEntryId(id) => { /* .. */ }
25///     _ => {}
26/// }
27/// ```
28#[non_exhaustive]
29#[derive(Debug, PartialEq, Eq, Clone, Hash)]
30pub enum DeviceId {
31    #[cfg(target_os = "windows")]
32    UncPath(windows::core::HSTRING),
33    #[cfg(target_os = "linux")]
34    DevPath(std::path::PathBuf),
35    #[cfg(target_os = "macos")]
36    RegistryEntryId(u64)
37}
38assert_impl_all!(DeviceId: Send, Sync, Unpin);
39
40/// A struct containing basic information about a device
41///
42/// This struct is part of [Device].
43#[derive(Debug, Clone, Hash, Eq, PartialEq)]
44pub struct DeviceInfo {
45    /// OS specific identifier
46    pub id: DeviceId,
47    /// The human-readable name
48    pub name: String,
49    /// The HID product id assigned to this device
50    pub product_id: u16,
51    /// The HID vendor id of the device's manufacturer (i.e Logitech = 0x46D)
52    pub vendor_id: u16,
53    /// The HID usage id
54    pub usage_id: u16,
55    /// The HID usage page
56    pub usage_page: u16,
57    /// The serial number of the device. Might be `None` if the device does not have a serial number or the platform/backend does not support retrieving the serial number.
58    pub serial_number: Option<String>
59}
60assert_impl_all!(DeviceInfo: Send, Sync, Unpin);
61
62impl DeviceInfo {
63    /// Convenience method for easily finding a specific device
64    pub fn matches(&self, usage_page: u16, usage_id: u16, vendor_id: u16, product_id: u16) -> bool {
65        self.usage_page == usage_page && self.usage_id == usage_id && self.vendor_id == vendor_id && self.product_id == product_id
66    }
67}
68
69/// The main entry point of this library
70#[derive(Default, Clone)]
71pub struct HidBackend(Arc<DynBackend>);
72
73impl HidBackend {
74    /// Create a specific backend.
75    /// If you don't care and want to just use the default backend for each platform consider calling [HidBackend::default] instead
76    pub fn new(backend: BackendType) -> Self {
77        Self(Arc::new(DynBackend::new(backend)))
78    }
79
80    /// Enumerates all **accessible** HID devices
81    ///
82    /// If this library fails to retrieve the [DeviceInfo] of a device it will be automatically excluded.
83    pub async fn enumerate(&self) -> HidResult<impl Stream<Item = Device> + Send + Unpin + '_> {
84        let steam = self.0.enumerate().await?.filter_map(|result| match result {
85            Ok(info) => Some(Device {
86                backend: self.0.clone(),
87                device_info: info
88            }),
89            Err(_) => None
90        });
91        Ok(steam)
92    }
93}
94
95/// A HID device that was detected by calling [HidBackend::enumerate]
96pub struct Device {
97    backend: Arc<DynBackend>,
98    device_info: DeviceInfo
99}
100
101impl Deref for Device {
102    type Target = DeviceInfo;
103
104    fn deref(&self) -> &Self::Target {
105        &self.device_info
106    }
107}
108
109impl Device {
110    /// Open the device in read-only mode
111    pub async fn open_readable(&self) -> HidResult<DeviceReader> {
112        let (r, _) = self.backend.open(&self.id, true, false).await?;
113        Ok(DeviceReader(r.unwrap()))
114    }
115
116    /// Open the device in write-only mode
117    /// Note: Not all backends support this mode and might upgrade the permission to read+write behind the scenes
118    pub async fn open_writeable(&self) -> HidResult<DeviceWriter> {
119        let (_, w) = self.backend.open(&self.id, false, true).await?;
120        Ok(DeviceWriter(w.unwrap()))
121    }
122
123    /// Open the device in read and write mode
124    pub async fn open(&self) -> HidResult<DeviceReaderWriter> {
125        let (r, w) = self.backend.open(&self.id, true, true).await?;
126        Ok((DeviceReader(r.unwrap()), DeviceWriter(w.unwrap())))
127    }
128}