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 human-readable manufacturer
52    pub manufacturer: Option<String>,
53    /// The HID product id assigned to this device
54    pub product_id: u16,
55    /// The HID vendor id of the device's manufacturer (i.e Logitech = 0x46D)
56    pub vendor_id: u16,
57    /// The HID usage id
58    pub usage_id: u16,
59    /// The HID usage page
60    pub usage_page: u16,
61    /// 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.
62    pub serial_number: Option<String>
63}
64assert_impl_all!(DeviceInfo: Send, Sync, Unpin);
65
66impl DeviceInfo {
67    /// Convenience method for easily finding a specific device
68    pub fn matches(&self, usage_page: u16, usage_id: u16, vendor_id: u16, product_id: u16) -> bool {
69        self.usage_page == usage_page && self.usage_id == usage_id && self.vendor_id == vendor_id && self.product_id == product_id
70    }
71}
72
73#[derive(Debug, Clone, Hash, Eq, PartialEq)]
74pub enum DeviceEvent {
75    Connected(DeviceId),
76    Disconnected(DeviceId)
77}
78
79/// The main entry point of this library
80#[derive(Default, Clone)]
81pub struct HidBackend(Arc<DynBackend>);
82
83impl HidBackend {
84    /// Create a specific backend.
85    /// If you don't care and want to just use the default backend for each platform consider calling [HidBackend::default] instead
86    pub fn new(backend: BackendType) -> Self {
87        Self(Arc::new(DynBackend::new(backend)))
88    }
89
90    /// Enumerates all **accessible** HID devices
91    ///
92    /// If this library fails to retrieve the [DeviceInfo] of a device, it will be automatically excluded.
93    pub async fn enumerate(&self) -> HidResult<impl Stream<Item = Device> + Send + Unpin + use<'_>> {
94        let steam = self.0.enumerate().await?.filter_map(|result| match result {
95            Ok(info) => Some(Device {
96                backend: self.0.clone(),
97                device_info: info
98            }),
99            Err(_) => None
100        });
101        Ok(steam)
102    }
103
104    /// Retrieve all device instances connected to a given id.
105    pub async fn query_devices(&self, id: &DeviceId) -> HidResult<impl Iterator<Item = Device> + use<'_>> {
106        Ok(self.0.query_info(id).await?.into_iter().map(|info| Device {
107            backend: self.0.clone(),
108            device_info: info
109        }))
110    }
111
112    /// Listen for device connect/disconnect events
113    ///
114    /// For "connect" events the returned id can be turned into a list of new devices using [self.query_devices]
115    pub fn watch(&self) -> HidResult<impl Stream<Item = DeviceEvent> + Send + Unpin> {
116        self.0.watch()
117    }
118}
119
120/// A HID device that was detected by calling [HidBackend::enumerate]
121pub struct Device {
122    backend: Arc<DynBackend>,
123    device_info: DeviceInfo
124}
125
126impl Debug for Device {
127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128        f.debug_struct("Device")
129            .field("device_info", &self.device_info)
130            .finish_non_exhaustive()
131    }
132}
133
134impl PartialEq for Device {
135    fn eq(&self, other: &Self) -> bool {
136        Arc::ptr_eq(&self.backend, &other.backend) && DeviceInfo::eq(&self.device_info, &other.device_info)
137    }
138}
139impl Eq for Device {}
140
141impl Hash for Device {
142    fn hash<H: Hasher>(&self, state: &mut H) {
143        DeviceInfo::hash(&self.device_info, state)
144    }
145}
146
147impl Deref for Device {
148    type Target = DeviceInfo;
149
150    fn deref(&self) -> &Self::Target {
151        &self.device_info
152    }
153}
154
155impl Device {
156    pub fn to_device_info(self) -> DeviceInfo {
157        self.device_info
158    }
159
160    /// Open the device in read-only mode
161    pub async fn open_readable(&self) -> HidResult<DeviceReader> {
162        let (r, _) = self.backend.open(&self.id, true, false).await?;
163        Ok(DeviceReader(r.unwrap()))
164    }
165
166    /// Open the device in write-only mode
167    /// Note: Not all backends support this mode and might upgrade the permission to read+write behind the scenes
168    pub async fn open_writeable(&self) -> HidResult<DeviceWriter> {
169        let (_, w) = self.backend.open(&self.id, false, true).await?;
170        Ok(DeviceWriter(w.unwrap()))
171    }
172
173    /// Open the device in read and write mode
174    pub async fn open(&self) -> HidResult<DeviceReaderWriter> {
175        let (r, w) = self.backend.open(&self.id, true, true).await?;
176        Ok((DeviceReader(r.unwrap()), DeviceWriter(w.unwrap())))
177    }
178
179    /// Open the device in read and write mode
180    pub async fn open_feature_handle(&self) -> HidResult<DeviceFeatureHandle> {
181        let r = self.backend.open_feature_handle(&self.id).await?;
182        Ok(DeviceFeatureHandle(r))
183    }
184
185    /// Read a feature report from the device
186    pub async fn read_feature_report(&self, buf: &mut [u8]) -> HidResult<usize> {
187        self.backend.read_feature_report(&self.id, buf).await
188    }
189}