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