pub mod descriptor;
mod device_info;
mod error;
pub mod report_info;
pub use device_info::{BusType, DeviceInfo};
pub use error::{HidError, HidResult};
mod backend;
#[cfg(not(target_arch = "wasm32"))]
mod maybe_future;
#[cfg(not(target_arch = "wasm32"))]
pub use maybe_future::MaybeFuture;
#[cfg(target_arch = "wasm32")]
pub use backend::webhid::{DeviceFilter, EventListenerHandle, InputReportStream};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ApiVersion {
pub major: u16,
pub minor: u16,
pub patch: u16,
}
pub const fn version() -> ApiVersion {
const fn parse(s: &str) -> u16 {
let bytes = s.as_bytes();
let mut v = 0u16;
let mut i = 0;
while i < bytes.len() {
v = v * 10 + (bytes[i] - b'0') as u16;
i += 1;
}
v
}
ApiVersion {
major: parse(env!("CARGO_PKG_VERSION_MAJOR")),
minor: parse(env!("CARGO_PKG_VERSION_MINOR")),
patch: parse(env!("CARGO_PKG_VERSION_PATCH")),
}
}
pub const fn version_str() -> &'static str {
env!("CARGO_PKG_VERSION")
}
#[cfg(not(target_arch = "wasm32"))]
pub use native::{HidDevice, Hidra};
#[cfg(target_arch = "wasm32")]
pub use web::{HidDevice, Hidra};
#[cfg(not(target_arch = "wasm32"))]
mod native {
use core::future::Future;
use crate::backend::{HidBackend, HidDeviceBackend, PlatformApi, PlatformDevice};
use crate::{DeviceInfo, HidResult};
pub struct Hidra {
backend: PlatformApi,
device_list: Vec<DeviceInfo>,
}
impl core::fmt::Debug for Hidra {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Hidra")
.field("devices", &self.device_list.len())
.finish_non_exhaustive()
}
}
impl Hidra {
pub fn new() -> HidResult<Self> {
let mut api = Self::new_without_enumerate()?;
api.refresh_devices()?;
Ok(api)
}
pub fn new_without_enumerate() -> HidResult<Self> {
Ok(Hidra {
backend: PlatformApi::new()?,
device_list: Vec::new(),
})
}
pub fn refresh_devices(&mut self) -> HidResult<()> {
self.device_list = self.backend.enumerate(0, 0)?;
Ok(())
}
pub fn device_list(&self) -> impl Iterator<Item = &DeviceInfo> {
self.device_list.iter()
}
pub fn enumerate(&self, vendor_id: u16, product_id: u16) -> HidResult<Vec<DeviceInfo>> {
self.backend.enumerate(vendor_id, product_id)
}
pub fn open(
&self,
vendor_id: u16,
product_id: u16,
) -> impl Future<Output = HidResult<HidDevice>> + '_ {
crate::maybe_future::Blocking::new(move || {
Ok(HidDevice {
backend: self.backend.open(vendor_id, product_id, None)?,
})
})
}
pub fn open_serial<'a>(
&'a self,
vendor_id: u16,
product_id: u16,
serial_number: &'a str,
) -> impl Future<Output = HidResult<HidDevice>> + 'a {
crate::maybe_future::Blocking::new(move || {
Ok(HidDevice {
backend: self
.backend
.open(vendor_id, product_id, Some(serial_number))?,
})
})
}
pub fn open_path<'a>(
&'a self,
path: &'a str,
) -> impl Future<Output = HidResult<HidDevice>> + 'a {
crate::maybe_future::Blocking::new(move || {
Ok(HidDevice {
backend: self.backend.open_path(path)?,
})
})
}
}
#[cfg(all(target_os = "macos", not(feature = "nusb")))]
impl Hidra {
pub fn set_open_exclusive(&self, exclusive: bool) {
self.backend.set_open_exclusive(exclusive);
}
pub fn open_exclusive(&self) -> bool {
self.backend.open_exclusive()
}
}
pub struct HidDevice {
backend: PlatformDevice,
}
impl core::fmt::Debug for HidDevice {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("HidDevice").finish_non_exhaustive()
}
}
impl HidDevice {
pub fn write<'a>(&'a self, data: &'a [u8]) -> impl Future<Output = HidResult<usize>> + 'a {
crate::maybe_future::Blocking::new(move || self.backend.write(data))
}
pub fn read<'a>(
&'a self,
buf: &'a mut [u8],
) -> impl Future<Output = HidResult<usize>> + 'a {
self.backend.read_async(buf)
}
pub fn send_feature_report<'a>(
&'a self,
data: &'a [u8],
) -> impl Future<Output = HidResult<()>> + 'a {
crate::maybe_future::Blocking::new(move || self.backend.send_feature_report(data))
}
pub fn get_feature_report<'a>(
&'a self,
buf: &'a mut [u8],
) -> impl Future<Output = HidResult<usize>> + 'a {
crate::maybe_future::Blocking::new(move || self.backend.get_feature_report(buf))
}
pub fn get_input_report<'a>(
&'a self,
buf: &'a mut [u8],
) -> impl Future<Output = HidResult<usize>> + 'a {
crate::maybe_future::Blocking::new(move || self.backend.get_input_report(buf))
}
pub fn get_manufacturer_string(
&self,
) -> impl Future<Output = HidResult<Option<String>>> + '_ {
crate::maybe_future::Blocking::new(move || self.backend.get_manufacturer_string())
}
pub fn get_product_string(&self) -> impl Future<Output = HidResult<Option<String>>> + '_ {
crate::maybe_future::Blocking::new(move || self.backend.get_product_string())
}
pub fn get_serial_number_string(
&self,
) -> impl Future<Output = HidResult<Option<String>>> + '_ {
crate::maybe_future::Blocking::new(move || self.backend.get_serial_number_string())
}
pub fn get_indexed_string(
&self,
index: u32,
) -> impl Future<Output = HidResult<Option<String>>> + '_ {
crate::maybe_future::Blocking::new(move || self.backend.get_indexed_string(index))
}
pub fn get_report_descriptor<'a>(
&'a self,
buf: &'a mut [u8],
) -> impl Future<Output = HidResult<usize>> + 'a {
crate::maybe_future::Blocking::new(move || self.backend.get_report_descriptor(buf))
}
pub fn report_descriptor(&self) -> impl Future<Output = HidResult<Vec<u8>>> + '_ {
crate::maybe_future::Blocking::new(move || {
let mut buf = vec![0u8; crate::MAX_REPORT_DESCRIPTOR_SIZE];
let len = self.backend.get_report_descriptor(&mut buf)?;
buf.truncate(len);
Ok(buf)
})
}
pub async fn parsed_report_descriptor(
&self,
) -> HidResult<crate::descriptor::ReportDescriptor> {
let bytes = self.report_descriptor().await?;
crate::descriptor::ReportDescriptor::parse(&bytes)
}
pub fn get_device_info(&self) -> impl Future<Output = HidResult<DeviceInfo>> + '_ {
crate::maybe_future::Blocking::new(move || self.backend.get_device_info())
}
}
#[cfg(all(target_os = "windows", not(feature = "nusb")))]
impl HidDevice {
pub fn container_id(&self) -> impl Future<Output = HidResult<[u8; 16]>> + '_ {
crate::maybe_future::Blocking::new(move || self.backend.container_id())
}
pub fn set_write_timeout(&self, timeout_ms: u32) {
self.backend.set_write_timeout(timeout_ms)
}
}
}
pub const MAX_REPORT_DESCRIPTOR_SIZE: usize = 4096;
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
#[test]
fn public_handles_are_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<super::Hidra>();
assert_send_sync::<super::HidDevice>();
assert_send_sync::<super::HidError>();
assert_send_sync::<super::DeviceInfo>();
}
}
#[cfg(target_arch = "wasm32")]
mod web {
use core::cell::RefCell;
use core::future::Future;
use crate::backend::webhid::{
DeviceFilter, EventListenerHandle, InputReportStream, WebHidApi, WebHidDevice,
};
use crate::report_info::CollectionInfo;
use crate::{DeviceInfo, HidResult};
pub struct Hidra {
backend: WebHidApi,
}
impl core::fmt::Debug for Hidra {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Hidra").finish_non_exhaustive()
}
}
#[allow(clippy::manual_async_fn)]
impl Hidra {
pub fn new() -> HidResult<Self> {
Ok(Hidra {
backend: WebHidApi::new()?,
})
}
pub fn request_device<'a>(
&'a self,
filters: &'a [DeviceFilter],
) -> impl Future<Output = HidResult<Vec<HidDevice>>> + 'a {
async move {
let devices = self.backend.request_device(filters).await?;
Ok(devices.into_iter().map(HidDevice::new).collect())
}
}
pub fn get_devices(&self) -> impl Future<Output = HidResult<Vec<HidDevice>>> + '_ {
async move {
let devices = self.backend.get_devices().await?;
Ok(devices.into_iter().map(HidDevice::new).collect())
}
}
pub fn on_connect(&self, mut f: impl FnMut(HidDevice) + 'static) -> EventListenerHandle {
self.backend.on_connect(move |dev| f(HidDevice::new(dev)))
}
pub fn on_disconnect(&self, mut f: impl FnMut(HidDevice) + 'static) -> EventListenerHandle {
self.backend
.on_disconnect(move |dev| f(HidDevice::new(dev)))
}
pub fn raw(&self) -> &web_sys::Hid {
self.backend.raw()
}
}
pub struct HidDevice {
backend: WebHidDevice,
stream: RefCell<Option<InputReportStream>>,
}
impl core::fmt::Debug for HidDevice {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("HidDevice")
.field("opened", &self.backend.opened())
.finish_non_exhaustive()
}
}
#[allow(clippy::manual_async_fn)]
impl HidDevice {
fn new(backend: WebHidDevice) -> Self {
HidDevice {
backend,
stream: RefCell::new(None),
}
}
pub fn write<'a>(&'a self, data: &'a [u8]) -> impl Future<Output = HidResult<usize>> + 'a {
self.backend.write(data)
}
pub fn read<'a>(
&'a self,
buf: &'a mut [u8],
) -> impl Future<Output = HidResult<usize>> + 'a {
async move {
if self.stream.borrow().is_none() {
*self.stream.borrow_mut() = Some(self.backend.start_reading());
}
let read = {
let guard = self.stream.borrow();
let stream = guard.as_ref().expect("stream started above");
stream.next_report()
};
let report = read.await?;
let len = report.len().min(buf.len());
buf[..len].copy_from_slice(&report[..len]);
Ok(len)
}
}
pub fn send_feature_report<'a>(
&'a self,
data: &'a [u8],
) -> impl Future<Output = HidResult<()>> + 'a {
self.backend.send_feature_report(data)
}
pub fn get_feature_report<'a>(
&'a self,
buf: &'a mut [u8],
) -> impl Future<Output = HidResult<usize>> + 'a {
async move {
let report_id =
buf.first()
.copied()
.ok_or_else(|| crate::HidError::InvalidData {
message: "get_feature_report requires at least the report ID byte"
.into(),
})?;
let report = self.backend.get_feature_report(report_id).await?;
let len = report.len().min(buf.len());
buf[..len].copy_from_slice(&report[..len]);
Ok(len)
}
}
pub fn get_report_descriptor<'a>(
&'a self,
buf: &'a mut [u8],
) -> impl Future<Output = HidResult<usize>> + 'a {
async move {
let descriptor = self.backend.report_descriptor()?;
let len = descriptor.len().min(buf.len());
buf[..len].copy_from_slice(&descriptor[..len]);
Ok(len)
}
}
pub fn report_descriptor(&self) -> impl Future<Output = HidResult<Vec<u8>>> + '_ {
async move { self.backend.report_descriptor() }
}
pub async fn parsed_report_descriptor(
&self,
) -> HidResult<crate::descriptor::ReportDescriptor> {
self.backend.parsed_report_descriptor()
}
pub fn get_product_string(&self) -> impl Future<Output = HidResult<Option<String>>> + '_ {
async move { Ok(self.backend.product_name()) }
}
pub fn get_device_info(&self) -> impl Future<Output = HidResult<DeviceInfo>> + '_ {
async move { Ok(self.backend.device_info()) }
}
pub async fn open(&self) -> HidResult<()> {
self.backend.open().await
}
pub async fn close(&self) -> HidResult<()> {
*self.stream.borrow_mut() = None;
self.backend.close().await
}
pub fn opened(&self) -> bool {
self.backend.opened()
}
pub async fn forget(&self) -> HidResult<()> {
self.backend.forget().await
}
pub fn on_input_report(&self, f: impl FnMut(u8, Vec<u8>) + 'static) -> EventListenerHandle {
self.backend.on_input_report(f)
}
pub fn start_reading(&self) -> InputReportStream {
self.backend.start_reading()
}
pub fn collections(&self) -> Vec<CollectionInfo> {
self.backend.collections()
}
pub fn raw(&self) -> &web_sys::HidDevice {
self.backend.raw()
}
}
}