From 202d2d1fbd1d96be12e158d6f0d04de7b607915d Mon Sep 17 00:00:00 2001 From: G2-Games Date: Sat, 23 Mar 2024 20:14:48 -0500 Subject: [PATCH] Added `UsbDescriptor`, added `get_device_list()` on native, updated deps --- src/backend/native.rs | 198 ++++++++++++++++++++++++++++---------- src/backend/wasm.rs | 216 +++++++++++++++++++++++++++++++++++------- src/lib.rs | 37 ++++++-- src/usb.rs | 46 +++++++-- 4 files changed, 393 insertions(+), 104 deletions(-) diff --git a/src/backend/native.rs b/src/backend/native.rs index af47659..0d7db55 100644 --- a/src/backend/native.rs +++ b/src/backend/native.rs @@ -1,7 +1,13 @@ -use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient, UsbError}; +use crate::usb::{ + ControlIn, ControlOut, ControlType, Descriptor, Device, Interface, Recipient, UsbError, +}; + +pub struct UsbDescriptor { + device_info: nusb::DeviceInfo, +} pub struct UsbDevice { - device_info: nusb::DeviceInfo, + device_info: UsbDescriptor, device: nusb::Device, } @@ -36,42 +42,39 @@ impl DeviceFilter { } } -pub async fn get_device(device_filter: Vec) -> Result { +pub async fn get_device(device_filters: Vec) -> Result { let devices = nusb::list_devices().unwrap(); let mut device_info = None; for prelim_dev_inf in devices { // See if the device exists in the list - if device_filter - .iter() - .any(|info| { - let mut result = false; + if device_filters.iter().any(|info| { + let mut result = false; - if info.vendor_id.is_some() { - result = info.vendor_id.unwrap() == prelim_dev_inf.vendor_id(); - } + if info.vendor_id.is_some() { + result = info.vendor_id.unwrap() == prelim_dev_inf.vendor_id(); + } - if info.product_id.is_some() { - result = info.product_id.unwrap() == prelim_dev_inf.product_id(); - } + if info.product_id.is_some() { + result = info.product_id.unwrap() == prelim_dev_inf.product_id(); + } - if info.class.is_some() { - result = info.class.unwrap() == prelim_dev_inf.class(); - } + if info.class.is_some() { + result = info.class.unwrap() == prelim_dev_inf.class(); + } - if info.subclass.is_some() { - result = info.subclass.unwrap() == prelim_dev_inf.subclass(); - } + if info.subclass.is_some() { + result = info.subclass.unwrap() == prelim_dev_inf.subclass(); + } - if info.protocol.is_some() { - result = info.protocol.unwrap() == prelim_dev_inf.protocol(); - } + if info.protocol.is_some() { + result = info.protocol.unwrap() == prelim_dev_inf.protocol(); + } - result - }) - { + result + }) { device_info = Some(prelim_dev_inf); - break + break; } } @@ -80,39 +83,69 @@ pub async fn get_device(device_filter: Vec) -> Result return Err(UsbError::DeviceNotFound), }; - let device = match device_info.open() { - Ok(dev) => dev, - Err(_) => return Err(UsbError::CommunicationError), - }; - - Ok(UsbDevice { - device_info, - device, - }) + Ok(UsbDescriptor { device_info }) } -impl Device for UsbDevice { - type UsbDevice = UsbDevice; - type UsbInterface = UsbInterface; +pub async fn get_device_list( + device_filters: Vec, +) -> Result, UsbError> { + let devices_info = nusb::list_devices().unwrap(); - async fn open_interface(&self, number: u8) -> Result { - let interface = match self.device.claim_interface(number) { - Ok(inter) => inter, - Err(_) => return Err(UsbError::CommunicationError), - }; + let mut devices = Vec::new(); + for prelim_dev_inf in devices_info { + // See if the device exists in the list + if device_filters.iter().any(|info| { + let mut result = false; - Ok(UsbInterface { interface }) - } + if info.vendor_id.is_some() { + result = info.vendor_id.unwrap() == prelim_dev_inf.vendor_id(); + } - async fn reset(&self) -> Result<(), UsbError> { - match self.device.reset() { - Ok(_) => Ok(()), - Err(_) => Err(UsbError::CommunicationError), + if info.product_id.is_some() { + result = info.product_id.unwrap() == prelim_dev_inf.product_id(); + } + + if info.class.is_some() { + result = info.class.unwrap() == prelim_dev_inf.class(); + } + + if info.subclass.is_some() { + result = info.subclass.unwrap() == prelim_dev_inf.subclass(); + } + + if info.protocol.is_some() { + result = info.protocol.unwrap() == prelim_dev_inf.protocol(); + } + + result + }) { + devices.push(prelim_dev_inf); } } - async fn forget(&self) -> Result<(), UsbError> { - self.reset().await + if devices.is_empty() { + return Err(UsbError::DeviceNotFound); + } + + let devices_opened: Vec = devices + .into_iter() + .map(|d| UsbDescriptor { device_info: d }) + .collect(); + + Ok(devices_opened) +} + +impl Descriptor for UsbDescriptor { + type Device = UsbDevice; + + async fn open(self) -> Result { + match self.device_info.open() { + Ok(dev) => Ok(Self::Device { + device_info: self, + device: dev, + }), + Err(err) => Err(UsbError::CommunicationError(err.to_string())), + } } async fn vendor_id(&self) -> u16 { @@ -140,6 +173,69 @@ impl Device for UsbDevice { } } +impl Device for UsbDevice { + type Interface = UsbInterface; + + async fn open_interface(&self, number: u8) -> Result { + let interface = match self.device.claim_interface(number) { + Ok(inter) => inter, + Err(err) => return Err(UsbError::CommunicationError(err.to_string())), + }; + + Ok(UsbInterface { interface }) + } + + async fn detach_and_open_interface(&self, number: u8) -> Result { + let interface = match self.device.detach_and_claim_interface(number) { + Ok(inter) => inter, + Err(err) => return Err(UsbError::CommunicationError(err.to_string())), + }; + + Ok(UsbInterface { interface }) + } + + async fn reset(&self) -> Result<(), UsbError> { + match self.device.reset() { + Ok(_) => Ok(()), + Err(err) => Err(UsbError::CommunicationError(err.to_string())), + } + } + + async fn forget(&self) -> Result<(), UsbError> { + self.reset().await + } + + async fn product_id(&self) -> u16 { + self.device_info.product_id().await + } + + async fn vendor_id(&self) -> u16 { + self.device_info.vendor_id().await + } + + async fn class(&self) -> u8 { + self.device_info.class().await + } + + async fn subclass(&self) -> u8 { + self.device_info.subclass().await + } + + async fn manufacturer_string(&self) -> Option { + self.device_info.manufacturer_string().await + } + + async fn product_string(&self) -> Option { + self.device_info.product_string().await + } +} + +impl Drop for UsbDevice { + fn drop(&mut self) { + let _ = self.device.reset(); + } +} + impl<'a> Interface<'a> for UsbInterface { async fn control_in(&self, data: ControlIn) -> Result, UsbError> { let result = match self.interface.control_in(data.into()).await.into_result() { diff --git a/src/backend/wasm.rs b/src/backend/wasm.rs index 818a276..d1f78b4 100644 --- a/src/backend/wasm.rs +++ b/src/backend/wasm.rs @@ -1,4 +1,4 @@ -#![cfg_attr(debug_assertions, allow(dead_code, unused_imports))] +//#![cfg_attr(debug_assertions, allow(dead_code, unused_imports))] use wasm_bindgen::prelude::*; use js_sys::{Array, Object, Promise, Uint8Array}; @@ -9,7 +9,12 @@ use web_sys::{ }; // Crate stuff -use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient, UsbError}; +use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient, UsbError, Descriptor}; + +#[wasm_bindgen] +pub struct UsbDescriptor { + device: WasmUsbDevice, +} #[wasm_bindgen] pub struct UsbDevice { @@ -51,9 +56,7 @@ impl DeviceFilter { } #[wasm_bindgen] -pub async fn get_device( - device_filter: Vec, -) -> Result { +pub async fn get_device(device_filter: Vec) -> Result { let window = web_sys::window().unwrap(); let navigator = window.navigator(); @@ -68,36 +71,33 @@ pub async fn get_device( for js_device in device_list { let device: WasmUsbDevice = js_device.into(); - if device_filter - .iter() - .any(|info| { - let mut result = false; + if device_filter.iter().any(|info| { + let mut result = false; - if info.vendor_id.is_some() { - result = info.vendor_id.unwrap() == device.vendor_id(); - } + if info.vendor_id.is_some() { + result = info.vendor_id.unwrap() == device.vendor_id(); + } - if info.product_id.is_some() { - result = info.product_id.unwrap() == device.product_id(); - } + if info.product_id.is_some() { + result = info.product_id.unwrap() == device.product_id(); + } - if info.class.is_some() { - result = info.class.unwrap() == device.device_class(); - } + if info.class.is_some() { + result = info.class.unwrap() == device.device_class(); + } - if info.subclass.is_some() { - result = info.subclass.unwrap() == device.device_subclass(); - } + if info.subclass.is_some() { + result = info.subclass.unwrap() == device.device_subclass(); + } - if info.protocol.is_some() { - result = info.protocol.unwrap() == device.device_protocol(); - } + if info.protocol.is_some() { + result = info.protocol.unwrap() == device.device_protocol(); + } - result - }) - { + result + }) { let _open_promise = JsFuture::from(Promise::resolve(&device.open())).await?; - return Ok(UsbDevice { device }); + return Ok(UsbDescriptor { device }); } } @@ -156,12 +156,154 @@ pub async fn get_device( let _open_promise = JsFuture::from(Promise::resolve(&device.open())).await?; - Ok(UsbDevice { device }) + Ok(UsbDescriptor { device }) +} + +/* +#[wasm_bindgen] +pub async fn get_device_list(device_filter: Vec) -> Result, js_sys::Error> { + let window = web_sys::window().unwrap(); + + let navigator = window.navigator(); + let usb = navigator.usb(); + + let device_list: Array = match JsFuture::from(Promise::resolve(&usb.get_devices())).await { + Ok(list) => list.into(), + Err(_) => Array::new(), + }; + + let mut devices = Vec::new(); + // Check if the device is already paired, if so, we don't need to request it again + for js_device in device_list { + let device: WasmUsbDevice = js_device.into(); + + if device_filter.iter().any(|info| { + let mut result = false; + + if info.vendor_id.is_some() { + result = info.vendor_id.unwrap() == device.vendor_id(); + } + + if info.product_id.is_some() { + result = info.product_id.unwrap() == device.product_id(); + } + + if info.class.is_some() { + result = info.class.unwrap() == device.device_class(); + } + + if info.subclass.is_some() { + result = info.subclass.unwrap() == device.device_subclass(); + } + + if info.protocol.is_some() { + result = info.protocol.unwrap() == device.device_protocol(); + } + + result + }) { + let _open_promise = JsFuture::from(Promise::resolve(&device.open())).await?; + devices.push(UsbDescriptor { device }); + } + } + + let arr = Array::new(); + for filter in device_filter { + let js_filter = js_sys::Object::new(); + if let Some(vid) = filter.vendor_id { + js_sys::Reflect::set( + &js_filter, + &JsValue::from_str("vendorId"), + &JsValue::from(vid), + ) + .unwrap(); + } + if let Some(pid) = filter.product_id { + js_sys::Reflect::set( + &js_filter, + &JsValue::from_str("productId"), + &JsValue::from(pid), + ) + .unwrap(); + } + if let Some(class) = filter.class { + js_sys::Reflect::set( + &js_filter, + &JsValue::from_str("classCode"), + &JsValue::from(class), + ) + .unwrap(); + } + if let Some(subclass) = filter.subclass { + js_sys::Reflect::set( + &js_filter, + &JsValue::from_str("subclassCode"), + &JsValue::from(subclass), + ) + .unwrap(); + } + if let Some(pro) = filter.protocol { + js_sys::Reflect::set( + &js_filter, + &JsValue::from_str("protocolCode"), + &JsValue::from(pro), + ) + .unwrap(); + } + arr.push(&js_filter); + } + + let filters = JsValue::from(&arr); + let filters2 = UsbDeviceRequestOptions::new(&filters); + + let device: WasmUsbDevice = JsFuture::from(Promise::resolve(&usb.request_device(&filters2))) + .await? + .into(); + + let _open_promise = JsFuture::from(Promise::resolve(&device.open())).await?; + + devices.push(UsbDescriptor { device }); + + return Ok(devices); +} +*/ + +impl Descriptor for UsbDescriptor { + type Device = UsbDevice; + + async fn open(self) -> Result { + Ok(Self::Device { + device: self.device + }) + } + + async fn product_id(&self) -> u16 { + self.device.vendor_id() + } + + async fn vendor_id(&self) -> u16 { + self.device.product_id() + } + + async fn class(&self) -> u8 { + self.device.device_class() + } + + async fn subclass(&self) -> u8 { + self.device.device_subclass() + } + + async fn manufacturer_string(&self) -> Option { + self.device.manufacturer_name() + } + + async fn product_string(&self) -> Option { + self.device.product_name() + } } impl Device for UsbDevice { - type UsbDevice = UsbDevice; - type UsbInterface = UsbInterface; + type Interface = UsbInterface; async fn open_interface(&self, number: u8) -> Result { let dev_promise = @@ -170,8 +312,8 @@ impl Device for UsbDevice { // Wait for the interface to be claimed let _device: WasmUsbDevice = match dev_promise { Ok(dev) => dev.into(), - Err(_) => { - return Err(UsbError::CommunicationError); + Err(err) => { + return Err(UsbError::CommunicationError(err.as_string().unwrap_or_default())); } }; @@ -181,12 +323,16 @@ impl Device for UsbDevice { }) } + async fn detach_and_open_interface(&self, number: u8) -> Result { + self.open_interface(number).await + } + async fn reset(&self) -> Result<(), UsbError> { let result = JsFuture::from(Promise::resolve(&self.device.reset())).await; match result { Ok(_) => Ok(()), - Err(_) => Err(UsbError::CommunicationError), + Err(err) => Err(UsbError::CommunicationError(err.as_string().unwrap_or_default())), } } @@ -195,7 +341,7 @@ impl Device for UsbDevice { match result { Ok(_) => Ok(()), - Err(_) => Err(UsbError::CommunicationError), + Err(err) => Err(UsbError::CommunicationError(err.as_string().unwrap_or_default())), } } diff --git a/src/lib.rs b/src/lib.rs index d8a4681..8eaca3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,24 +9,34 @@ //! //! When a [UsbInterface] is dropped, it is automatically released. //! +//! ### CURRENT LIMITATIONS: +//! * Hotplug support is not implemented. Waiting on [hotplug support in nusb](https://github.com/kevinmehall/nusb/pull/20). +//! +//! * Until [this pull request](https://github.com/rustwasm/wasm-bindgen/issues/3155) +//! is merged into wasm bindgen, getting a list of USB devices is not possible on WASM +//! targets. However, this isn't a huge deal as the user gets a list to select from anyway. +//! //! ## Example: //! ```no_run //! # tokio_test::block_on(async { -//! use cross_usb::usb::{Device, Interface, Recipient, ControlType, ControlIn}; +//! use cross_usb::usb::{Descriptor, Device, Interface, Recipient, ControlType, ControlIn}; //! use cross_usb::device_filter; //! -//! // Obtain a device using its VendorID and ProductID -//! let filter = vec![ +//! // Obtain a device descriptor (UsbDescriptor) using a DeviceFilter, +//! // in this case with its VendorID and ProductID +//! let filters = vec![ //! device_filter!{vendor_id: 0x054c, product_id: 0x00c9} //! ]; +//! let dev_descriptor = cross_usb::get_device(filters).await.expect("Failed to find device"); //! -//! let device = cross_usb::get_device(filter).await.expect("Failed to get device"); +//! // Open the device that the descriptor is describing +//! let dev = dev_descriptor.open().await.expect("Failed to open device"); //! //! // Obtain an interface of the device -//! let interface = device.open_interface(0).await.expect("Failed to open interface"); +//! let interface = dev.open_interface(0).await.expect("Failed to open interface"); //! //! // Send a Control transfer to the device, obtaining -//! // the result and storing it in `result`, and you're done! +//! // the result and storing it in `result` //! let result = interface.control_in(ControlIn { //! control_type: ControlType::Vendor, //! recipient: Recipient::Interface, @@ -41,16 +51,19 @@ //! ``` pub mod usb; +/// The context contains the platform specific implementation of the USB transfers #[cfg(not(target_family = "wasm"))] #[path = "./backend/native.rs"] -/// The context contains the platform specific implementation of the USB transfers mod context; #[cfg(target_family = "wasm")] #[path = "./backend/wasm.rs"] -/// The context contains the platform specific implementation of the USB transfers mod context; +#[doc(inline)] +/// An implementation of a USB device descriptor +pub use crate::context::UsbDescriptor; + #[doc(inline)] /// An implementation of a USB device pub use crate::context::UsbDevice; @@ -64,14 +77,13 @@ pub use crate::context::UsbInterface; #[doc(inline)] pub use crate::context::DeviceFilter; -/// Gets a single device from a list of VendorID and ProductIDs +/// Gets a single device descriptor ([UsbDescriptor]) from a list of VendorID and ProductIDs /// /// ## Example /// ```no_run /// # tokio_test::block_on(async { /// use cross_usb::{get_device, DeviceFilter, device_filter}; /// -/// /// let filter = vec![ /// device_filter!{vendor_id: 0x054c, product_id: 0x00c9}, /// device_filter!{vendor_id: 0x054c}, @@ -83,6 +95,11 @@ pub use crate::context::DeviceFilter; #[doc(inline)] pub use crate::context::get_device; +/// Gets a list of devices from a list of VendorID and ProductIDs +#[cfg(not(target_family = "wasm"))] +#[doc(inline)] +pub use crate::context::get_device_list; + /// Macro to create a device filter more easily. /// /// The only valid keys are fields of the [DeviceFilter] struct. diff --git a/src/usb.rs b/src/usb.rs index 885520d..0def554 100644 --- a/src/usb.rs +++ b/src/usb.rs @@ -2,19 +2,49 @@ //! This module contains the traits and associated functions and //! structs which allow for USB communication. -use crate::context::UsbInterface; use thiserror::Error; +pub trait Descriptor { + /// A unique USB Device + type Device; + + /// Opens the USB connection, returning a [Self::Device] + async fn open(self) -> Result; + + /// 16 bit device Product ID + async fn product_id(&self) -> u16; + + /// 16 bit device Vendor ID + async fn vendor_id(&self) -> u16; + + /// Device standard class + async fn class(&self) -> u8; + + /// Device standard subclass + async fn subclass(&self) -> u8; + + /// Get the manufacturer string string of the device, if available without device IO + /// + /// Not available on Windows + async fn manufacturer_string(&self) -> Option; + + /// Get the product string of the device, if available without device IO + async fn product_string(&self) -> Option; +} + /// A unique USB device pub trait Device { - /// A unique USB Device - type UsbDevice; - /// A unique Interface on a USB Device - type UsbInterface; + type Interface; /// Open a specific interface of the device - async fn open_interface(&self, number: u8) -> Result; + async fn open_interface(&self, number: u8) -> Result; + + /// Open a specific interface of the device, detaching any + /// kernel drivers and claiming it. + /// + /// **Note:** This only has an effect on Native, and only on Linux. + async fn detach_and_open_interface(&self, number: u8) -> Result; /// Reset the device, which causes it to no longer be usable. You must /// request a new device with [crate::get_device] @@ -22,7 +52,7 @@ pub trait Device { /// Remove the device from the paired devices list, causing it to no longer be usable. You must request to reconnect using [crate::get_device] /// - /// **Note: on Native with `nusb` this simply resets the device** + /// **Note:** On Native this simply resets the device. async fn forget(&self) -> Result<(), UsbError>; /// 16 bit device Product ID @@ -87,7 +117,7 @@ pub enum UsbError { TransferError, #[error("device communication failed")] - CommunicationError, + CommunicationError(String), #[error("device disconnected")] Disconnected,