Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(Breaking) API changes | Part 2 #15

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 135 additions & 101 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,156 +3,190 @@
mod backend;
mod error;

use std::fmt::{Debug, Formatter};
use std::fmt::Debug;
use std::future::Future;
use std::hash::{Hash, Hasher};

use futures_core::Stream;
use static_assertions::assert_impl_all;
use crate::backend::{BackendDevice, BackendDeviceId, BackendPrivateData};

pub use crate::backend::BackendError;
pub use crate::error::{ErrorSource, HidError, HidResult};

/// A struct containing basic information about a device
/// A struct containing basic information about a device.
///
/// This struct can be obtained by calling [DeviceInfo::enumerate] or [DeviceInfo::enumerate_with_criteria].
///
/// This struct can be obtained by calling [DeviceInfo::enumerate] and upgraded into a usable [Device] by calling [DeviceInfo::open].
#[derive(Debug, Clone)]
/// A usable [DeviceReader] can be obtained by calling [DeviceInfo::open_readonly] or by calling [DeviceInfo::open] to obtain it in combination with a [DeviceWriter].
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DeviceInfo {
/// OS specific identifier
pub id: DeviceId,
/// The human readable name
pub name: String,
/// The HID product id assigned to this device
pub product_id: u16,
/// The HID vendor id of the device's manufacturer (i.e Logitech = 0x46D)
pub vendor_id: u16,
/// The HID usage id
pub usage_id: u16,
/// The HID usage page
pub usage_page: u16,

pub(crate) private_data: BackendPrivateData,
pub(crate) name: String,
pub(crate) product_id: u16,
pub(crate) vendor_id: u16,
pub(crate) usage_id: u16,
pub(crate) usage_page: u16,

#[cfg(any(all(target_os = "windows", feature = "win32"), target_os = "macos", target_os = "linux"))]
pub(crate) serial_number: Option<String>,

#[cfg(target_os = "windows")]
pub(crate) handle: windows::core::HSTRING,

#[cfg(target_os = "macos")]
pub(crate) registry_entry_id: u64,

#[cfg(target_os = "linux")]
pub(crate) path: std::path::PathBuf,
}

impl DeviceInfo {
/// Enumerates all **accessible** HID devices
/// Enumerates all **accessible** HID devices.
///
/// If this library fails to retrieve the [DeviceInfo] of a device it will be automatically excluded.
/// Register a `log` compatible logger at `trace` level for more information about the discarded devices.
pub fn enumerate() -> impl Future<Output = HidResult<impl Stream<Item = DeviceInfo> + Unpin + Send>> {
backend::enumerate()
}

/// Opens the associated device in the requested [AccessMode]
pub async fn open(&self, mode: AccessMode) -> HidResult<Device> {
let dev = backend::open(&self.id.0, mode).await?;
Ok(Device {
inner: dev,
info: self.clone(),
mode
})
/// Opens the associated device in readonly mode.
pub async fn open_readonly(&self) -> HidResult<DeviceReader> {
backend::open_readonly(self).await
}

/// Convenience method for easily finding a specific device
pub fn matches(&self, usage_page: u16, usage_id: u16, vendor_id: u16, product_id: u16) -> bool {
self.usage_page == usage_page && self.usage_id == usage_id && self.vendor_id == vendor_id && self.product_id == product_id
/// Opens the associated device in read/write mode.
pub async fn open(&self) -> HidResult<(DeviceReader, DeviceWriter)> {
backend::open(self).await
}
}

impl Hash for DeviceInfo {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
self.name.hash(state);
self.product_id.hash(state);
self.vendor_id.hash(state);
self.usage_id.hash(state);
self.usage_page.hash(state);
/// The human-readable name.
pub fn name(&self) -> &str {
self.name.as_str()
}
}

impl PartialEq for DeviceInfo {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
&& self.name == other.name
&& self.product_id == other.product_id
&& self.vendor_id == other.vendor_id
&& self.usage_id == other.usage_id
&& self.usage_page == other.usage_page
/// The HID vendor id of the device's manufacturer (i.e Logitech = 0x46D).
pub fn vendor_id(&self) -> u16 {
self.vendor_id
}

/// The HID product id assigned to this device.
pub fn product_id(&self) -> u16 {
self.product_id
}

/// The HID usage page.
pub fn usage_page(&self) -> u16 {
self.usage_page
}
}

impl Eq for DeviceInfo {}
/// The HID usage id.
pub fn usage_id(&self) -> u16 {
self.usage_id
}

#[cfg(any(all(target_os = "windows", feature = "win32"), target_os = "macos", target_os = "linux"))]
/// *(Windows Win32, macOS & Linux only)* The HID serial number.
///
/// Only available on some USB devices.
pub fn serial_number(&self) -> Option<&str> {
self.serial_number.map(|x| x.as_ref())
}

#[cfg(target_os = "windows")]
/// *(Windows only)* Handle identifier for device.
pub fn handle(&self) -> &windows::core::HSTRING {
&self.handle
}

pub trait SerialNumberExt {
fn serial_number(&self) -> Option<&str>;
#[cfg(target_os = "macos")]
/// *(macOS only)* Registry entry identifier for device.
pub fn registry_entry_id(&self) -> u64 {
self.registry_entry_id
}

#[cfg(target_os = "linux")]
/// *(Linux only)* File path to device.
pub fn path(&self) -> &std::path::Path {
self.path.as_path()
}

/// Convenience method for easily finding a specific device
pub fn matches(&self, usage_page: u16, usage_id: u16, vendor_id: u16, product_id: u16) -> bool {
self.usage_page == usage_page && self.usage_id == usage_id && self.vendor_id == vendor_id && self.product_id == product_id
}
}

/// A struct representing an opened device
/// A struct representing an opened device reader.
///
/// Dropping this struct will close the associated device
pub struct Device {
inner: BackendDevice,
info: DeviceInfo,
mode: AccessMode
/// Dropping this struct and optional associated writer will close the HID.
#[derive(Debug)]
pub struct DeviceReader {
pub(crate) inner: backend::BackendDeviceReader,
pub(crate) device_info: DeviceInfo,
}

impl Device {
/// Read a input report from this device
pub fn read_input_report<'a>(&'a self, buf: &'a mut [u8]) -> impl Future<Output = HidResult<usize>> + Send + 'a {
debug_assert!(self.mode.readable());
self.inner.read_input_report(buf)
impl DeviceReader {
/// Read an input report from this device.
pub fn read_input_report<'a>(&'a mut self, buffer: &'a mut [u8]) -> impl Future<Output = HidResult<usize>> + Send + 'a {
self.inner.read_input_report(buffer)
}

/// Write an output report to this device
pub fn write_output_report<'a>(&'a self, buf: &'a [u8]) -> impl Future<Output = HidResult<()>> + Send + 'a {
debug_assert!(self.mode.writeable());
self.inner.write_output_report(buf)
/// Retrieves the [DeviceInfo] associated with this device.
pub fn device_info(&self) -> &DeviceInfo {
&self.device_info
}
}

/// Retrieves the [DeviceInfo] associated with this device
pub fn info(&self) -> &DeviceInfo {
&self.info
impl PartialEq for DeviceReader {
fn eq(&self, other: &Self) -> bool {
self.device_info.eq(&other.device_info)
}
}

/// An opaque struct that wraps the OS specific identifier of a device
#[derive(Hash, Clone, Eq, PartialEq)]
#[repr(transparent)]
pub struct DeviceId(BackendDeviceId);
impl Eq for DeviceReader {}

impl From<BackendDeviceId> for DeviceId {
fn from(value: BackendDeviceId) -> Self {
Self(value)
impl Hash for DeviceReader {
fn hash<H: Hasher>(&self, state: &mut H) {
"BackendDeviceReader".hash(state);
self.device_info.hash(state);
}
}

impl Debug for DeviceId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.0)
}
/// A struct representing an opened device writer.
///
/// Dropping this struct and associated reader will close the HID.
#[derive(Debug)]
pub struct DeviceWriter {
pub(crate) inner: backend::BackendDeviceWriter,
pub(crate) device_info: DeviceInfo,
}

/// An enum that controls how a device will be opened
///
/// This mainly influences the flags passed to the underlying OS api,
/// but is also used to avoid initializing read specific data structures for write-only devices.
///
/// In general `Read` means shared access and `Write` or `ReadWrite` means exclusive access
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub enum AccessMode {
Read,
Write,
#[default]
ReadWrite
impl DeviceWriter {
/// Write an output report to this device.
pub fn write_output_report<'a>(&'a mut self, buffer: &'a [u8]) -> impl Future<Output = HidResult<()>> + Send + 'a {
self.inner.write_output_report(buffer)
}

/// Retrieves the [DeviceInfo] associated with this device.
pub fn device_info(&self) -> &DeviceInfo {
&self.device_info
}
}

impl AccessMode {
pub fn readable(self) -> bool {
matches!(self, Self::Read | Self::ReadWrite)
impl PartialEq for DeviceWriter {
fn eq(&self, other: &Self) -> bool {
self.device_info.eq(&other.device_info)
}
pub fn writeable(self) -> bool {
matches!(self, Self::Write | Self::ReadWrite)
}

impl Eq for DeviceWriter {}

impl Hash for DeviceWriter {
fn hash<H: Hasher>(&self, state: &mut H) {
"BackendDeviceWriter".hash(state);
self.device_info.hash(state);
}
}

assert_impl_all!(Device: Send, Sync);
assert_impl_all!(DeviceInfo: Send, Sync);
assert_impl_all!(DeviceReader: Send, Sync);
assert_impl_all!(DeviceWriter: Send, Sync);
assert_impl_all!(DeviceInfo: Send, Sync);