diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..45f4bdb --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.wasm32-unknown-unknown] +rustflags = "--cfg=web_sys_unstable_apis" diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..842fb10 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.check.targets": ["wasm32-unknown-unknown"], + "rust-analyzer.cargo.target": "wasm32-unknown-unknown", + "rust-analyzer.showUnlinkedFileNotification": false +} diff --git a/Cargo.toml b/Cargo.toml index f6c43be..1bbf562 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ authors = [ "Artyom Pavlov ", "mberndt123", "niklasad1", - "Stefan Kerkmann" + "Stefan Kerkmann", ] repository = "https://github.com/ruabmbua/hidapi-rs" description = "Rust-y wrapper around hidapi" @@ -60,20 +60,39 @@ windows-native = [ "windows-sys/Win32_Storage_FileSystem", "windows-sys/Win32_System_IO", "windows-sys/Win32_System_Threading", - "windows-sys/Win32_UI_Shell_PropertiesSystem" + "windows-sys/Win32_UI_Shell_PropertiesSystem", ] [dependencies] -libc = "0.2" cfg-if = "1" +tracing = "0.1" [target.'cfg(target_os = "linux")'.dependencies] +libc = "0.2" udev = { version = "0.8", optional = true } nix = { version = "0.27", optional = true, features = ["fs", "ioctl", "poll"] } +[target.'cfg(target_os = "macos")'.dependencies] +libc = "0.2" + + [target.'cfg(windows)'.dependencies] +libc = "0.2" windows-sys = { version = "0.48", features = ["Win32_Foundation"] } +[target.'cfg(target_family="wasm")'.dependencies] +web-sys = { version = "*", features = [ + "Document", + "Window", + "Navigator", + "Usb", + "UsbDevice", + "UsbDeviceRequestOptions", + "Hid", + "HidDevice", +] } +wasm-bindgen-futures = "*" + [build-dependencies] cc = "1.0" pkg-config = "0.3" diff --git a/build.rs b/build.rs index 56f7d51..b8c20e1 100644 --- a/build.rs +++ b/build.rs @@ -23,6 +23,9 @@ extern crate pkg_config; use std::env; fn main() { + println!("cargo::rustc-check-cfg=cfg(hidapi)"); + println!("cargo::rustc-check-cfg=cfg(libusb)"); + let target = env::var("TARGET").unwrap(); if target.contains("linux") { @@ -37,6 +40,8 @@ fn main() { compile_openbsd(); } else if target.contains("illumos") { compile_illumos(); + } else if target.contains("wasm") { + compile_wasm(); } else { panic!("Unsupported target os for hidapi-rs"); } @@ -215,3 +220,7 @@ fn compile_macos() { println!("cargo:rustc-link-lib=framework=CoreFoundation"); println!("cargo:rustc-link-lib=framework=AppKit") } + +fn compile_wasm() { + // println!("cargo:rustc-cfg=webhid"); +} diff --git a/src/error.rs b/src/error.rs index 5b7108d..c205d33 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,6 +4,7 @@ // This file is part of hidapi-rs, based on hidapi-rs by Osspial // ************************************************************************** +#[cfg(not(target_family = "wasm"))] use libc::wchar_t; use std::error::Error; use std::fmt::{Display, Formatter, Result}; @@ -16,6 +17,7 @@ pub enum HidError { message: String, }, HidApiErrorEmpty, + #[cfg(not(target_family = "wasm"))] FromWideCharError { wide_char: wchar_t, }, @@ -42,6 +44,7 @@ impl Display for HidError { match self { HidError::HidApiError { message } => write!(f, "hidapi error: {}", message), HidError::HidApiErrorEmpty => write!(f, "hidapi error: (could not get error message)"), + #[cfg(not(target_family = "wasm"))] HidError::FromWideCharError { wide_char } => { write!(f, "failed converting {:#X} to rust char", wide_char) } diff --git a/src/hidapi.rs b/src/hidapi.rs index 701574e..60b0583 100644 --- a/src/hidapi.rs +++ b/src/hidapi.rs @@ -5,9 +5,12 @@ use std::{ fmt::{self, Debug}, }; +#[cfg(not(target_family = "wasm"))] use libc::{c_int, size_t, wchar_t}; -use crate::{ffi, DeviceInfo, HidDeviceBackendBase, HidError, HidResult, WcharString}; +#[cfg(not(target_family = "wasm"))] +use crate::ffi; +use crate::{DeviceInfo, HidDeviceBackendBase, HidError, HidResult, WcharString}; #[cfg(target_os = "macos")] mod macos; @@ -19,7 +22,7 @@ const STRING_BUF_LEN: usize = 128; pub struct HidApiBackend; impl HidApiBackend { - pub fn get_hid_device_info_vector(vid: u16, pid: u16) -> HidResult> { + pub async fn get_hid_device_info_vector(vid: u16, pid: u16) -> HidResult> { let mut device_vector = Vec::with_capacity(8); let enumeration = unsafe { ffi::hid_enumerate(vid, pid) }; diff --git a/src/lib.rs b/src/lib.rs index d81686a..329febb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,9 +61,11 @@ //! an opt-in that can be enabled with the `macos-shared-device` feature flag. mod error; +#[cfg(not(target_family = "wasm"))] mod ffi; use cfg_if::cfg_if; +#[cfg(not(target_family = "wasm"))] use libc::wchar_t; use std::ffi::CStr; use std::ffi::CString; @@ -85,6 +87,9 @@ cfg_if! { } else if #[cfg(hidapi)] { mod hidapi; use hidapi::HidApiBackend; + } else if #[cfg(target_family="wasm")] { + mod webusb; + use webusb::HidApiBackend; } else { compile_error!("No backend selected"); } @@ -191,13 +196,13 @@ impl HidApi { /// /// Panics if hidapi is already initialized in "without enumerate" mode /// (i.e. if `new_without_enumerate()` has been called before). - pub fn new() -> HidResult { + pub async fn new() -> HidResult { lazy_init(true)?; let mut api = HidApi { device_list: Vec::with_capacity(8), }; - api.add_devices(0, 0)?; + api.add_devices(0, 0).await?; Ok(api) } @@ -220,9 +225,9 @@ impl HidApi { /// Refresh devices list and information about them (to access them use /// `device_list()` method) /// Identical to `reset_devices()` followed by `add_devices(0, 0)`. - pub fn refresh_devices(&mut self) -> HidResult<()> { + pub async fn refresh_devices(&mut self) -> HidResult<()> { self.reset_devices()?; - self.add_devices(0, 0)?; + self.add_devices(0, 0).await?; Ok(()) } @@ -234,9 +239,9 @@ impl HidApi { /// Indexes devices that match the given VID and PID filters. /// 0 indicates no filter. - pub fn add_devices(&mut self, vid: u16, pid: u16) -> HidResult<()> { + pub async fn add_devices(&mut self, vid: u16, pid: u16) -> HidResult<()> { self.device_list - .append(&mut HidApiBackend::get_hid_device_info_vector(vid, pid)?); + .append(&mut HidApiBackend::get_hid_device_info_vector(vid, pid).await?); Ok(()) } @@ -307,6 +312,7 @@ impl HidApi { enum WcharString { String(String), #[cfg_attr(all(feature = "linux-native", target_os = "linux"), allow(dead_code))] + #[cfg(not(target_family = "wasm"))] Raw(Vec), None, } @@ -374,6 +380,7 @@ impl DeviceInfo { } } + #[cfg(not(target_family = "wasm"))] pub fn serial_number_raw(&self) -> Option<&[wchar_t]> { match self.serial_number { WcharString::Raw(ref s) => Some(s), @@ -393,6 +400,7 @@ impl DeviceInfo { } } + #[cfg(not(target_family = "wasm"))] pub fn manufacturer_string_raw(&self) -> Option<&[wchar_t]> { match self.manufacturer_string { WcharString::Raw(ref s) => Some(s), @@ -408,6 +416,7 @@ impl DeviceInfo { } } + #[cfg(not(target_family = "wasm"))] pub fn product_string_raw(&self) -> Option<&[wchar_t]> { match self.product_string { WcharString::Raw(ref s) => Some(s), diff --git a/src/webusb.rs b/src/webusb.rs new file mode 100644 index 0000000..dbeaff1 --- /dev/null +++ b/src/webusb.rs @@ -0,0 +1,123 @@ +use core::cell::Ref; +use core::ffi::CStr; +use std::ffi::CString; + +use wasm_bindgen_futures::{js_sys::Array, wasm_bindgen::JsCast, JsFuture}; + +use crate::{DeviceInfo, HidDeviceBackendBase, HidResult}; + +pub struct HidApiBackend; + +impl HidApiBackend { + pub async fn get_hid_device_info_vector(vid: u16, pid: u16) -> HidResult> { + let window = web_sys::window().unwrap(); + let navigator = window.navigator(); + let hid = navigator.hid(); + let devices = JsFuture::from(hid.get_devices()).await.unwrap(); + + let devices: Array = JsCast::unchecked_from_js(devices); + + let mut result = vec![]; + for device in devices { + let device: web_sys::HidDevice = JsCast::unchecked_from_js(device); + + // vid = 0 and pid = 0 means no filter + if (device.vendor_id() != vid && vid != 0) || (device.product_id() != pid && pid != 0) { + continue; + } + + result.push(DeviceInfo { + path: CString::new("").unwrap(), + vendor_id: device.vendor_id(), + product_id: device.product_id(), + serial_number: crate::WcharString::None, + release_number: 0, + manufacturer_string: crate::WcharString::None, + product_string: crate::WcharString::String(device.product_name()), + usage_page: 0, + usage: 0, + interface_number: 0, + bus_type: crate::BusType::Usb, + }); + } + + Ok(result) + } + + pub fn open(vid: u16, pid: u16) -> HidResult { + HidDevice::open(vid, pid, None) + } + + pub fn open_serial(vid: u16, pid: u16, sn: &str) -> HidResult { + HidDevice::open(vid, pid, Some(sn)) + } + + pub fn open_path(device_path: &CStr) -> HidResult { + HidDevice::open_path(device_path) + } +} + +pub struct HidDevice {} + +unsafe impl Send for HidDevice {} + +// API for the library to call us, or for internal uses +impl HidDevice { + pub(crate) fn open(_vid: u16, _pid: u16, _sn: Option<&str>) -> HidResult { + todo!() + } + + pub(crate) fn open_path(_device_path: &CStr) -> HidResult { + todo!() + } + + fn _info(&self) -> HidResult> { + todo!() + } +} + +impl HidDeviceBackendBase for HidDevice { + fn write(&self, _data: &[u8]) -> HidResult { + todo!() + } + + fn read(&self, _buf: &mut [u8]) -> HidResult { + todo!() + } + + fn read_timeout(&self, _buf: &mut [u8], _timeout: i32) -> HidResult { + todo!() + } + + fn send_feature_report(&self, _data: &[u8]) -> HidResult<()> { + todo!() + } + + fn get_feature_report(&self, _buf: &mut [u8]) -> HidResult { + todo!() + } + + fn set_blocking_mode(&self, _blocking: bool) -> HidResult<()> { + todo!() + } + + fn get_manufacturer_string(&self) -> HidResult> { + todo!() + } + + fn get_product_string(&self) -> HidResult> { + todo!() + } + + fn get_serial_number_string(&self) -> HidResult> { + todo!() + } + + fn get_device_info(&self) -> HidResult { + todo!() + } + + fn get_report_descriptor(&self, _buf: &mut [u8]) -> HidResult { + todo!() + } +}