First code commit

This commit is contained in:
G2-Games 2024-01-30 11:21:31 -06:00
commit f3f58f9ab5
9 changed files with 543 additions and 0 deletions

11
.appveyor.yml Normal file
View 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
View file

@ -0,0 +1,2 @@
[target.'cfg(target_family = "wasm")']
rustflags = ["--cfg=web_sys_unstable_apis"]

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log
www

69
.travis.yml Normal file
View 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
View 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
View 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
View 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(&params, 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(&params));
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
View 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
View 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],
}