mirror of
https://github.com/G2-Games/cross-usb.git
synced 2025-04-19 13:22:53 -05:00
First code commit
This commit is contained in:
commit
f3f58f9ab5
9 changed files with 543 additions and 0 deletions
11
.appveyor.yml
Normal file
11
.appveyor.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
install:
|
||||
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly
|
||||
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||
- rustc -V
|
||||
- cargo -V
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- cargo test --locked
|
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[target.'cfg(target_family = "wasm")']
|
||||
rustflags = ["--cfg=web_sys_unstable_apis"]
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
bin/
|
||||
pkg/
|
||||
wasm-pack.log
|
||||
|
||||
www
|
69
.travis.yml
Normal file
69
.travis.yml
Normal file
|
@ -0,0 +1,69 @@
|
|||
language: rust
|
||||
sudo: false
|
||||
|
||||
cache: cargo
|
||||
|
||||
matrix:
|
||||
include:
|
||||
|
||||
# Builds with wasm-pack.
|
||||
- rust: beta
|
||||
env: RUST_BACKTRACE=1
|
||||
addons:
|
||||
firefox: latest
|
||||
chrome: stable
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
|
||||
- cargo install-update -a
|
||||
- curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
|
||||
script:
|
||||
- cargo generate --git . --name testing
|
||||
# Having a broken Cargo.toml (in that it has curlies in fields) anywhere
|
||||
# in any of our parent dirs is problematic.
|
||||
- mv Cargo.toml Cargo.toml.tmpl
|
||||
- cd testing
|
||||
- wasm-pack build
|
||||
- wasm-pack test --chrome --firefox --headless
|
||||
|
||||
# Builds on nightly.
|
||||
- rust: nightly
|
||||
env: RUST_BACKTRACE=1
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
|
||||
- cargo install-update -a
|
||||
- rustup target add wasm32-unknown-unknown
|
||||
script:
|
||||
- cargo generate --git . --name testing
|
||||
- mv Cargo.toml Cargo.toml.tmpl
|
||||
- cd testing
|
||||
- cargo check
|
||||
- cargo check --target wasm32-unknown-unknown
|
||||
- cargo check --no-default-features
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features
|
||||
- cargo check --no-default-features --features console_error_panic_hook
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
|
||||
- cargo check --no-default-features --features "console_error_panic_hook wee_alloc"
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc"
|
||||
|
||||
# Builds on beta.
|
||||
- rust: beta
|
||||
env: RUST_BACKTRACE=1
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
|
||||
- cargo install-update -a
|
||||
- rustup target add wasm32-unknown-unknown
|
||||
script:
|
||||
- cargo generate --git . --name testing
|
||||
- mv Cargo.toml Cargo.toml.tmpl
|
||||
- cd testing
|
||||
- cargo check
|
||||
- cargo check --target wasm32-unknown-unknown
|
||||
- cargo check --no-default-features
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features
|
||||
- cargo check --no-default-features --features console_error_panic_hook
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
|
||||
# Note: no enabling the `wee_alloc` feature here because it requires
|
||||
# nightly for now.
|
38
Cargo.toml
Normal file
38
Cargo.toml
Normal file
|
@ -0,0 +1,38 @@
|
|||
[package]
|
||||
name = "cross-usb"
|
||||
version = "0.1.0"
|
||||
authors = ["G2-Games <ke0bhogsg@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
wasm-bindgen = "0.2.84"
|
||||
wasm-bindgen-futures = "0.4.39"
|
||||
js-sys = "0.3.67"
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies.web-sys]
|
||||
version = "0.3"
|
||||
features = [
|
||||
"Window",
|
||||
"Navigator",
|
||||
"console",
|
||||
"Usb",
|
||||
"UsbDevice",
|
||||
"UsbInterface",
|
||||
"UsbRecipient",
|
||||
"UsbRequestType",
|
||||
"UsbControlTransferParameters",
|
||||
"UsbDeviceRequestOptions",
|
||||
"Storage"
|
||||
]
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
nusb = "0.1.2"
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s"
|
||||
|
||||
[package.metadata.wasm-pack.profile.dev.wasm-bindgen]
|
||||
dwarf-debug-info = true
|
130
src/backend/native.rs
Normal file
130
src/backend/native.rs
Normal file
|
@ -0,0 +1,130 @@
|
|||
use std::error::Error;
|
||||
use nusb;
|
||||
|
||||
use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient};
|
||||
|
||||
pub struct UsbDevice {
|
||||
device: nusb::Device,
|
||||
}
|
||||
|
||||
pub struct UsbInterface {
|
||||
interface: nusb::Interface,
|
||||
}
|
||||
|
||||
/// Gets a single device from the VendorID and ProductID
|
||||
pub async fn get_device(vendor_id: u16, product_id: u16) -> Result<UsbDevice, Box<dyn Error>> {
|
||||
let devices = nusb::list_devices().unwrap();
|
||||
|
||||
let mut device_info = None;
|
||||
for device in devices {
|
||||
if device.vendor_id() == vendor_id && device.product_id() == product_id {
|
||||
device_info = Some(device);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let device_info = match device_info {
|
||||
Some(dev) => dev,
|
||||
None => return Err("Device not found".into()),
|
||||
};
|
||||
|
||||
let device = device_info.open()?;
|
||||
|
||||
Ok(UsbDevice { device })
|
||||
}
|
||||
|
||||
impl Device for UsbDevice {
|
||||
type UsbDevice = UsbDevice;
|
||||
type UsbInterface = UsbInterface;
|
||||
|
||||
async fn open_interface(&self, number: u8) -> Result<UsbInterface, Box<dyn Error>> {
|
||||
let interface = match self.device.claim_interface(number) {
|
||||
Ok(inter) => inter,
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
Ok(UsbInterface { interface })
|
||||
}
|
||||
|
||||
async fn reset(&self) -> Result<(), Box<dyn Error>> {
|
||||
match self.device.reset() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Interface<'a> for UsbInterface {
|
||||
async fn control_in(&self, data: ControlIn) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
Ok(self.interface.control_in(data.into()).await.into_result()?)
|
||||
}
|
||||
|
||||
async fn control_out(&self, data: ControlOut<'a>) -> Result<(), Box<dyn Error>> {
|
||||
match self.interface.control_out(data.into()).await.into_result() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(e.into())
|
||||
}
|
||||
}
|
||||
|
||||
async fn bulk_in(&self, endpoint: u8, buf: Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let buf_len = buf.len();
|
||||
let request_buffer = nusb::transfer::RequestBuffer::reuse(buf, buf_len);
|
||||
|
||||
Ok(self.interface.bulk_in(endpoint, request_buffer).await.into_result()?)
|
||||
}
|
||||
|
||||
async fn bulk_out(&self, endpoint: u8, buf: Vec<u8>) -> Result<(), Box<dyn Error>> {
|
||||
match self.interface.bulk_out(endpoint, buf).await.into_result() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ControlIn> for nusb::transfer::ControlIn {
|
||||
fn from(val: ControlIn) -> Self {
|
||||
nusb::transfer::ControlIn {
|
||||
control_type: val.control_type.into(),
|
||||
recipient: val.recipient.into(),
|
||||
request: val.request,
|
||||
value: val.value,
|
||||
index: val.index,
|
||||
length: val.length,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<ControlOut<'a>> for nusb::transfer::ControlOut<'a> {
|
||||
fn from(val: ControlOut<'a>) -> Self {
|
||||
nusb::transfer::ControlOut {
|
||||
control_type: val.control_type.into(),
|
||||
recipient: val.recipient.into(),
|
||||
request: val.request,
|
||||
value: val.value,
|
||||
index: val.index,
|
||||
data: val.data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ControlType> for nusb::transfer::ControlType {
|
||||
fn from(val: ControlType) -> Self {
|
||||
match val {
|
||||
ControlType::Standard => nusb::transfer::ControlType::Standard,
|
||||
ControlType::Class => nusb::transfer::ControlType::Class,
|
||||
ControlType::Vendor => nusb::transfer::ControlType::Vendor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Recipient> for nusb::transfer::Recipient {
|
||||
fn from(val: Recipient) -> Self {
|
||||
match val {
|
||||
|
||||
Recipient::Device => nusb::transfer::Recipient::Device,
|
||||
Recipient::Interface => nusb::transfer::Recipient::Interface,
|
||||
Recipient::Endpoint => nusb::transfer::Recipient::Endpoint,
|
||||
Recipient::Other => nusb::transfer::Recipient::Other,
|
||||
}
|
||||
}
|
||||
}
|
196
src/backend/wasm.rs
Normal file
196
src/backend/wasm.rs
Normal file
|
@ -0,0 +1,196 @@
|
|||
#![cfg_attr(debug_assertions, allow(dead_code, unused_imports))]
|
||||
use std::error::Error;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use js_sys::JSON;
|
||||
|
||||
use web_sys::{
|
||||
console,
|
||||
Usb,
|
||||
UsbDevice as WasmUsbDevice,
|
||||
UsbInterface as WasmUsbInterface,
|
||||
UsbControlTransferParameters,
|
||||
UsbRecipient,
|
||||
UsbRequestType,
|
||||
UsbDeviceRequestOptions,
|
||||
};
|
||||
use js_sys::{Array, Uint8Array, Promise};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
|
||||
// Crate stuff
|
||||
use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient};
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct UsbDevice {
|
||||
device: WasmUsbDevice,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct UsbInterface {
|
||||
device: WasmUsbDevice,
|
||||
}
|
||||
|
||||
/// Gets a single device from the VendorID and ProductID
|
||||
#[wasm_bindgen]
|
||||
pub async fn get_device(vendor_id: u16, product_id: u16) -> Result<UsbDevice, js_sys::Error> {
|
||||
let window = web_sys::window().unwrap();
|
||||
|
||||
let navigator = window.navigator();
|
||||
let usb = navigator.usb();
|
||||
|
||||
let arr = Array::new();
|
||||
let filter1 = js_sys::Object::new();
|
||||
js_sys::Reflect::set(
|
||||
&filter1,
|
||||
&JsValue::from_str("vendorId"),
|
||||
&JsValue::from(vendor_id),
|
||||
)
|
||||
.unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&filter1,
|
||||
&JsValue::from_str("productId"),
|
||||
&JsValue::from(product_id),
|
||||
)
|
||||
.unwrap();
|
||||
arr.push(&filter1);
|
||||
let filters = JsValue::from(&arr);
|
||||
|
||||
let filters2 = UsbDeviceRequestOptions::new(&filters);
|
||||
|
||||
let device_promise = JsFuture::from(Promise::resolve(&usb.request_device(&filters2))).await;
|
||||
|
||||
let device: WasmUsbDevice = match device_promise {
|
||||
Ok(res) => res.into(),
|
||||
Err(err) => {
|
||||
console::log_1(&err.clone().into());
|
||||
return Err(err.into())
|
||||
},
|
||||
};
|
||||
|
||||
let _open_promise = JsFuture::from(Promise::resolve(&device.open()));
|
||||
|
||||
console::log_1(&"got device".into());
|
||||
|
||||
Ok(UsbDevice {
|
||||
device
|
||||
})
|
||||
}
|
||||
|
||||
impl Device for UsbDevice {
|
||||
type UsbDevice = UsbDevice;
|
||||
type UsbInterface = UsbInterface;
|
||||
|
||||
async fn open_interface(&self, number: u8) -> Result<UsbInterface, Box<dyn Error>> {
|
||||
let dev_promise = JsFuture::from(Promise::resolve(&self.device.claim_interface(number)));
|
||||
|
||||
// Wait for the interface to be claimed
|
||||
let result = dev_promise;
|
||||
|
||||
Ok(UsbInterface {
|
||||
device: self.device.clone()
|
||||
})
|
||||
}
|
||||
|
||||
async fn reset(&self) -> Result<(), Box<dyn Error>> {
|
||||
let promise = Promise::resolve(&self.device.reset());
|
||||
|
||||
let result = JsFuture::from(promise).await;
|
||||
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => {
|
||||
console::log_1(&"Cancelled".into());
|
||||
return Err("cancelled".into())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Interface<'a> for UsbInterface {
|
||||
async fn control_in(&self, data: crate::usb::ControlIn) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let length = data.length;
|
||||
let params = data.into();
|
||||
let promise = Promise::resolve(&self.device.control_transfer_in(¶ms, length));
|
||||
|
||||
let mut result = JsFuture::from(promise).await;
|
||||
|
||||
let data = match result {
|
||||
Ok(res) => res.into(),
|
||||
Err(_) => {
|
||||
console::log_1(&"Cancelled".into());
|
||||
return Err("cancelled".into())
|
||||
},
|
||||
};
|
||||
|
||||
let unitarray = Uint8Array::new(&data);
|
||||
|
||||
Ok(unitarray.to_vec())
|
||||
}
|
||||
|
||||
async fn control_out(&self, data: crate::usb::ControlOut<'a>) -> Result<(), Box<dyn Error>> {
|
||||
let params = data.into();
|
||||
let promise = Promise::resolve(&self.device.control_transfer_out(¶ms));
|
||||
|
||||
let mut result = JsFuture::from(promise).await;
|
||||
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
console::log_1(&"Cancelled".into());
|
||||
Err(format!("{:?}", err).into())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn bulk_in(&self, _endpoint: u8, _buf: Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn bulk_out(&self, _endpoint: u8, _buf: Vec<u8>) -> Result<(), Box<dyn Error>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ControlIn> for UsbControlTransferParameters {
|
||||
fn from(value: ControlIn) -> Self {
|
||||
UsbControlTransferParameters::new(
|
||||
value.index,
|
||||
value.recipient.into(),
|
||||
value.request.into(),
|
||||
value.control_type.into(),
|
||||
value.value
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ControlOut<'_>> for UsbControlTransferParameters {
|
||||
fn from(value: ControlOut) -> Self {
|
||||
UsbControlTransferParameters::new(
|
||||
value.index,
|
||||
value.recipient.into(),
|
||||
value.request.into(),
|
||||
value.control_type.into(),
|
||||
value.value
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Recipient> for UsbRecipient {
|
||||
fn from(value: Recipient) -> Self {
|
||||
match value {
|
||||
Recipient::Device => UsbRecipient::Device,
|
||||
Recipient::Interface => UsbRecipient::Interface,
|
||||
Recipient::Endpoint => UsbRecipient::Endpoint,
|
||||
Recipient::Other => UsbRecipient::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ControlType> for UsbRequestType {
|
||||
fn from(value: ControlType) -> Self {
|
||||
match value {
|
||||
ControlType::Standard => UsbRequestType::Standard,
|
||||
ControlType::Class => UsbRequestType::Class,
|
||||
ControlType::Vendor => UsbRequestType::Vendor,
|
||||
}
|
||||
}
|
||||
}
|
9
src/lib.rs
Normal file
9
src/lib.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
pub mod usb;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[path = "./backend/native.rs"]
|
||||
pub mod context;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
#[path = "./backend/wasm.rs"]
|
||||
pub mod context;
|
80
src/usb.rs
Normal file
80
src/usb.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use std::error::Error;
|
||||
|
||||
use crate::context::UsbInterface;
|
||||
|
||||
/// A unique USB device
|
||||
pub trait Device {
|
||||
type UsbDevice;
|
||||
type UsbInterface;
|
||||
|
||||
async fn open_interface(&self, number: u8) -> Result<UsbInterface, Box<dyn Error>>;
|
||||
async fn reset(&self) -> Result<(), Box<dyn Error>>;
|
||||
|
||||
//TODO: Implement these placeholders
|
||||
async fn product_id(&self) -> u16 {
|
||||
0x00
|
||||
}
|
||||
async fn vendor_id(&self) -> u16 {
|
||||
0x00
|
||||
}
|
||||
async fn class(&self) -> u16 {
|
||||
0x00
|
||||
}
|
||||
async fn subclass(&self) -> u16 {
|
||||
0x00
|
||||
}
|
||||
async fn manufacturer_string(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
async fn product_string(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A specific interface of a USB device
|
||||
pub trait Interface<'a> {
|
||||
async fn control_in(&self, data: ControlIn) -> Result<Vec<u8>, Box<dyn Error>>;
|
||||
async fn control_out(&self, data: ControlOut<'a>) -> Result<(), Box<dyn Error>>;
|
||||
|
||||
async fn bulk_in(&self, endpoint: u8, buf: Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>>;
|
||||
async fn bulk_out(&self, endpoint: u8, buf: Vec<u8>) -> Result<(), Box<dyn Error>>;
|
||||
|
||||
async fn interrupt_in(&self, _endpoint: u8, _buf: Vec<u8>) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn interrupt_out(&self, _endpoint: u8, _buf: Vec<u8>) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ControlType {
|
||||
Standard = 0,
|
||||
Class = 1,
|
||||
Vendor = 2,
|
||||
}
|
||||
|
||||
pub enum Recipient {
|
||||
Device = 0,
|
||||
Interface = 1,
|
||||
Endpoint = 2,
|
||||
Other = 3,
|
||||
}
|
||||
|
||||
pub struct ControlIn {
|
||||
pub control_type: ControlType,
|
||||
pub recipient: Recipient,
|
||||
pub request: u8,
|
||||
pub value: u16,
|
||||
pub index: u16,
|
||||
pub length: u16,
|
||||
}
|
||||
|
||||
pub struct ControlOut<'a> {
|
||||
pub control_type: ControlType,
|
||||
pub recipient: Recipient,
|
||||
pub request: u8,
|
||||
pub value: u16,
|
||||
pub index: u16,
|
||||
pub data: &'a[u8],
|
||||
}
|
Loading…
Reference in a new issue