Skip to main content

async_hid/
device_info.rs

1use std::fmt::Debug;
2use std::hash::{Hash, Hasher};
3use std::ops::Deref;
4use std::sync::Arc;
5
6use futures_lite::{Stream, StreamExt};
7use static_assertions::assert_impl_all;
8
9use crate::backend::{Backend, BackendType, DynBackend};
10use crate::device::DeviceFeatureHandle;
11use crate::{DeviceReader, DeviceReaderWriter, DeviceWriter, HidResult};
12
13/// A platform-specific identifier for a device.
14///
15/// Can be used as opaque type for equality checks or inspected with platform specific code:
16/// ```no_run
17/// # use async_hid::DeviceId;
18/// let id: DeviceId = /* ... */
19/// # panic!();
20/// match(id) {
21///    #[cfg(target_os = "windows")]
22///     DeviceId::UncPath(path) => { /* .. */ },
23///     #[cfg(target_os = "linux")]
24///     DeviceId::DevPath(path) => { /* .. */ },
25///     #[cfg(target_os = "macos")]
26///     DeviceId::RegistryEntryId(id) => { /* .. */ }
27///     _ => {}
28/// }
29/// ```
30#[non_exhaustive]
31#[derive(Debug, PartialEq, Eq, Clone, Hash)]
32pub enum DeviceId {
33    #[cfg(target_os = "windows")]
34    UncPath(windows::core::HSTRING),
35    #[cfg(target_os = "linux")]
36    DevPath(std::path::PathBuf),
37    #[cfg(target_os = "macos")]
38    RegistryEntryId(u64)
39}
40assert_impl_all!(DeviceId: Send, Sync, Unpin);
41
42/// A struct containing basic information about a device
43///
44/// This struct is part of [Device].
45#[derive(Debug, Clone, Hash, Eq, PartialEq)]
46pub struct DeviceInfo {
47    /// OS specific identifier
48    pub id: DeviceId,
49    /// The human-readable name
50    pub name: String,
51    /// The HID product id assigned to this device
52    pub product_id: u16,
53    /// The HID vendor id of the device's manufacturer (i.e Logitech = 0x46D)
54    pub vendor_id: u16,
55    /// The HID usage id
56    pub usage_id: u16,
57    /// The HID usage page
58    pub usage_page: u16,
59    /// 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.
60    pub serial_number: Option<String>
61}
62assert_impl_all!(DeviceInfo: Send, Sync, Unpin);
63
64impl DeviceInfo {
65    /// Convenience method for easily finding a specific device
66    pub fn matches(&self, usage_page: u16, usage_id: u16, vendor_id: u16, product_id: u16) -> bool {
67        self.usage_page == usage_page && self.usage_id == usage_id && self.vendor_id == vendor_id && self.product_id == product_id
68    }
69}
70
71#[derive(Debug, Clone, Hash, Eq, PartialEq)]
72pub enum DeviceEvent {
73    Connected(DeviceId),
74    Disconnected(DeviceId)
75}
76
77/// The main entry point of this library
78#[derive(Default, Clone)]
79pub struct HidBackend(Arc<DynBackend>);
80
81impl HidBackend {
82    /// Create a specific backend.
83    /// If you don't care and want to just use the default backend for each platform consider calling [HidBackend::default] instead
84    pub fn new(backend: BackendType) -> Self {
85        Self(Arc::new(DynBackend::new(backend)))
86    }
87
88    /// Enumerates all **accessible** HID devices
89    ///
90    /// If this library fails to retrieve the [DeviceInfo] of a device, it will be automatically excluded.
91    pub async fn enumerate(&self) -> HidResult<impl Stream<Item = Device> + Send + Unpin + use<'_>> {
92        let steam = self.0.enumerate().await?.filter_map(|result| match result {
93            Ok(info) => Some(Device {
94                backend: self.0.clone(),
95                device_info: info
96            }),
97            Err(_) => None
98        });
99        Ok(steam)
100    }
101
102    /// Retrieve all device instances connected to a given id.
103    pub async fn query_devices(&self, id: &DeviceId) -> HidResult<impl Iterator<Item = Device> + use<'_>> {
104        Ok(self.0.query_info(id).await?.into_iter().map(|info| Device {
105            backend: self.0.clone(),
106            device_info: info
107        }))
108    }
109
110    /// Listen for device connect/disconnect events
111    ///
112    /// For "connect" events the returned id can be turned into a list of new devices using [self.query_devices]
113    pub fn watch(&self) -> HidResult<impl Stream<Item = DeviceEvent> + Send + Unpin> {
114        self.0.watch()
115    }
116}
117
118/// A HID device that was detected by calling [HidBackend::enumerate]
119pub struct Device {
120    backend: Arc<DynBackend>,
121    device_info: DeviceInfo
122}
123
124impl Debug for Device {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        f.debug_struct("Device")
127            .field("device_info", &self.device_info)
128            .finish_non_exhaustive()
129    }
130}
131
132impl PartialEq for Device {
133    fn eq(&self, other: &Self) -> bool {
134        Arc::ptr_eq(&self.backend, &other.backend) && DeviceInfo::eq(&self.device_info, &other.device_info)
135    }
136}
137impl Eq for Device {}
138
139impl Hash for Device {
140    fn hash<H: Hasher>(&self, state: &mut H) {
141        DeviceInfo::hash(&self.device_info, state)
142    }
143}
144
145impl Deref for Device {
146    type Target = DeviceInfo;
147
148    fn deref(&self) -> &Self::Target {
149        &self.device_info
150    }
151}
152
153impl Device {
154    pub fn to_device_info(self) -> DeviceInfo {
155        self.device_info
156    }
157
158    /// Open the device in read-only mode
159    pub async fn open_readable(&self) -> HidResult<DeviceReader> {
160        let (r, _) = self.backend.open(&self.id, true, false).await?;
161        Ok(DeviceReader(r.unwrap()))
162    }
163
164    /// Open the device in write-only mode
165    /// Note: Not all backends support this mode and might upgrade the permission to read+write behind the scenes
166    pub async fn open_writeable(&self) -> HidResult<DeviceWriter> {
167        let (_, w) = self.backend.open(&self.id, false, true).await?;
168        Ok(DeviceWriter(w.unwrap()))
169    }
170
171    /// Open the device in read and write mode
172    pub async fn open(&self) -> HidResult<DeviceReaderWriter> {
173        let (r, w) = self.backend.open(&self.id, true, true).await?;
174        Ok((DeviceReader(r.unwrap()), DeviceWriter(w.unwrap())))
175    }
176
177    /// Open the device in read and write mode
178    pub async fn open_feature_handle(&self) -> HidResult<DeviceFeatureHandle> {
179        let r = self.backend.open_feature_handle(&self.id).await?;
180        Ok(DeviceFeatureHandle(r))
181    }
182
183    /// Read a feature report from the device
184    pub async fn read_feature_report(&self, buf: &mut [u8]) -> HidResult<usize> {
185        self.backend.read_feature_report(&self.id, buf).await
186    }
187}