Added UsbDescriptor, added get_device_list() on native, updated deps

This commit is contained in:
G2-Games 2024-03-23 20:14:48 -05:00
parent 54154d54c3
commit 202d2d1fbd
4 changed files with 393 additions and 104 deletions

View file

@ -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,15 +42,13 @@ impl DeviceFilter {
}
}
pub async fn get_device(device_filter: Vec<DeviceFilter>) -> Result<UsbDevice, UsbError> {
pub async fn get_device(device_filters: Vec<DeviceFilter>) -> Result<UsbDescriptor, UsbError> {
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| {
if device_filters.iter().any(|info| {
let mut result = false;
if info.vendor_id.is_some() {
@ -68,10 +72,9 @@ pub async fn get_device(device_filter: Vec<DeviceFilter>) -> Result<UsbDevice, U
}
result
})
{
}) {
device_info = Some(prelim_dev_inf);
break
break;
}
}
@ -80,39 +83,69 @@ pub async fn get_device(device_filter: Vec<DeviceFilter>) -> Result<UsbDevice, U
None => 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<DeviceFilter>,
) -> Result<Vec<UsbDescriptor>, UsbError> {
let devices_info = nusb::list_devices().unwrap();
async fn open_interface(&self, number: u8) -> Result<UsbInterface, UsbError> {
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<UsbDescriptor> = 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<Self::Device, UsbError> {
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<Self::Interface, UsbError> {
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<Self::Interface, UsbError> {
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<String> {
self.device_info.manufacturer_string().await
}
async fn product_string(&self) -> Option<String> {
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<Vec<u8>, UsbError> {
let result = match self.interface.control_in(data.into()).await.into_result() {

View file

@ -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<DeviceFilter>,
) -> Result<UsbDevice, js_sys::Error> {
pub async fn get_device(device_filter: Vec<DeviceFilter>) -> Result<UsbDescriptor, js_sys::Error> {
let window = web_sys::window().unwrap();
let navigator = window.navigator();
@ -68,9 +71,7 @@ pub async fn get_device(
for js_device in device_list {
let device: WasmUsbDevice = js_device.into();
if device_filter
.iter()
.any(|info| {
if device_filter.iter().any(|info| {
let mut result = false;
if info.vendor_id.is_some() {
@ -94,10 +95,9 @@ pub async fn get_device(
}
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<DeviceFilter>) -> Result<Vec<UsbDescriptor>, 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<Self::Device, UsbError> {
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<String> {
self.device.manufacturer_name()
}
async fn product_string(&self) -> Option<String> {
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<UsbInterface, UsbError> {
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::Interface, UsbError> {
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())),
}
}

View file

@ -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.

View file

@ -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<Self::Device, UsbError>;
/// 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<String>;
/// Get the product string of the device, if available without device IO
async fn product_string(&self) -> Option<String>;
}
/// 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<UsbInterface, UsbError>;
async fn open_interface(&self, number: u8) -> Result<Self::Interface, UsbError>;
/// 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<Self::Interface, UsbError>;
/// 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,