Skip to content

Commit

Permalink
Implement hotplug events
Browse files Browse the repository at this point in the history
  • Loading branch information
Yatekii committed Dec 26, 2024
1 parent f0909ca commit 1ab30cc
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 71 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ web-sys = { version = "*", features = [
"UsbTransferStatus",
"UsbOutTransferResult",
"UsbDirection",
"UsbConnectionEvent",
] }
wasm-bindgen-futures = { version = "*" }

Expand Down
21 changes: 1 addition & 20 deletions src/enumeration.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#[cfg(target_os = "windows")]
use std::ffi::{OsStr, OsString};
use std::ops::AddAssign;

#[cfg(any(target_os = "linux", target_os = "android"))]
use crate::platform::SysfsPath;
Expand Down Expand Up @@ -102,25 +101,7 @@ impl DeviceInfo {

#[cfg(target_family = "wasm")]
{
use web_sys::{js_sys::Reflect, wasm_bindgen::JsValue};
let key = JsValue::from_str("nusbUniqueId");
static INCREMENT: std::sync::LazyLock<std::sync::Mutex<usize>> =
std::sync::LazyLock::new(|| std::sync::Mutex::new(0));
let id = if let Ok(device_id) = Reflect::get(&self.device, &key) {
device_id
.as_f64()
.expect("Expected an integer ID. This is a bug. Please report it.")
as usize
} else {
let mut lock = INCREMENT
.lock()
.expect("this should never be poisoned as we do not have multiple threads");
lock.add_assign(1);
Reflect::set(&self.device, &key, &JsValue::from_f64(*lock as f64))
.expect("Could not set ID on JS object. This is a bug. Please report it.");
*lock
};
DeviceId(crate::platform::DeviceId { id })
DeviceId(crate::platform::DeviceId::from_device(&self.device))
}
}

Expand Down
4 changes: 0 additions & 4 deletions src/platform/webusb/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,6 @@ impl WebusbDevice {
Err(Error::other("device not found"))
}

// pub(crate) fn handle_events(&self) {
// todo!()
// }

pub(crate) fn configuration_descriptors(&self) -> impl Iterator<Item = &[u8]> {
self.config_descriptors.iter().map(|d| &d[..])
}
Expand Down
89 changes: 47 additions & 42 deletions src/platform/webusb/enumeration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,48 +21,8 @@ pub async fn list_devices() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
let device: UsbDevice = JsCast::unchecked_from_js(device);
JsFuture::from(device.open()).await.unwrap();

result.push(DeviceInfo {
bus_id: "webusb".to_string(),
device_address: 0,
vendor_id: device.vendor_id(),
product_id: device.product_id(),
device_version: ((device.usb_version_major() as u16) << 8)
| device.usb_version_minor() as u16,
class: device.device_class(),
subclass: device.device_subclass(),
protocol: device.device_protocol(),
speed: None,
manufacturer_string: device.manufacturer_name(),
product_string: device.product_name(),
serial_number: device.serial_number(),
interfaces: {
let descriptors = extract_decriptors(&device).await?;
let mut interfaces = vec![];
for descriptor in descriptors.into_iter() {
let configuration = Configuration::new(&descriptor);
for interface_group in configuration.interfaces() {
let alternate = interface_group.first_alt_setting();
let interface_string = if let Some(id) = alternate.string_index() {
Some(extract_string(&device, id as u16).await)
} else {
None
};

interfaces.push(InterfaceInfo {
interface_number: interface_group.interface_number(),
class: alternate.class(),
subclass: alternate.subclass(),
protocol: alternate.protocol(),
interface_string,
});
}
}
interfaces
},
port_chain: vec![],
max_packet_size_0: 255,
device: device.clone(),
});
let device_info = device_to_info(device.clone()).await?;
result.push(device_info);
JsFuture::from(device.close()).await.unwrap();
}

Expand All @@ -75,3 +35,48 @@ pub async fn list_devices() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
pub fn list_buses() -> Result<impl Iterator<Item = BusInfo>, Error> {
Ok(vec![].into_iter())
}

pub(crate) async fn device_to_info(device: UsbDevice) -> Result<DeviceInfo, Error> {
Ok(DeviceInfo {
bus_id: "webusb".to_string(),
device_address: 0,
vendor_id: device.vendor_id(),
product_id: device.product_id(),
device_version: ((device.usb_version_major() as u16) << 8)
| device.usb_version_minor() as u16,
class: device.device_class(),
subclass: device.device_subclass(),
protocol: device.device_protocol(),
speed: None,
manufacturer_string: device.manufacturer_name(),
product_string: device.product_name(),
serial_number: device.serial_number(),
interfaces: {
let descriptors = extract_decriptors(&device).await?;
let mut interfaces = vec![];
for descriptor in descriptors.into_iter() {
let configuration = Configuration::new(&descriptor);
for interface_group in configuration.interfaces() {
let alternate = interface_group.first_alt_setting();
let interface_string = if let Some(id) = alternate.string_index() {
Some(extract_string(&device, id as u16).await)
} else {
None
};

interfaces.push(InterfaceInfo {
interface_number: interface_group.interface_number(),
class: alternate.class(),
subclass: alternate.subclass(),
protocol: alternate.protocol(),
interface_string,
});
}
}
interfaces
},
port_chain: vec![],
max_packet_size_0: 255,
device: device.clone(),
})
}
84 changes: 79 additions & 5 deletions src/platform/webusb/hotplug.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,89 @@
use std::task::Poll;
use std::{
sync::mpsc::{channel, Receiver, TryRecvError},
task::Poll,
};

use atomic_waker::AtomicWaker;
use wasm_bindgen_futures::spawn_local;
use web_sys::{
wasm_bindgen::{prelude::Closure, JsCast},
UsbConnectionEvent,
};

use crate::{hotplug::HotplugEvent, Error};

pub(crate) struct WebusbHotplugWatch {}
use super::{enumeration::device_to_info, DeviceId};

pub(crate) struct WebusbHotplugWatch {
waker: AtomicWaker,
events: Receiver<HotplugEvent>,
}

impl WebusbHotplugWatch {
pub fn new() -> Result<Self, Error> {
Ok(Self {})
let window = web_sys::window().unwrap();
let navigator = window.navigator();
let usb = navigator.usb();
let (sender, receiver) = channel();
{
let sender = sender.clone();
usb.set_onconnect(Some(
(Closure::wrap(Box::new(move |event: UsbConnectionEvent| {
let sender = sender.clone();
spawn_local(async move {
let info = device_to_info(event.device()).await;
match info {
Ok(info) => {let result = sender.clone().send(HotplugEvent::Connected(
info ,
));
if let Err(e) = result {
tracing::warn!(
"Could not send the connect event to the internal channel: {e:?}",
)
}},
Err(e) => {
tracing::warn!(
"Could not read device descriptors for internal connect event dispatch: {e:?}",
)
},
}

})
}) as Box<dyn FnMut(UsbConnectionEvent)>))
.as_ref()
.unchecked_ref(),
));
}
{
let sender = sender.clone();
usb.set_ondisconnect(Some(
(Closure::wrap(Box::new(move |event: UsbConnectionEvent| {
let sender = sender.clone();
let result = sender.send(HotplugEvent::Disconnected(
crate::DeviceId(DeviceId::from_device(&event.device()))
));
if let Err(e) = result {
tracing::warn!(
"Could not send the disconnect event to the internal channel: {e:?}",
)
}
}) as Box<dyn FnMut(UsbConnectionEvent)>))
.as_ref()
.unchecked_ref(),
));
}
Ok(Self {
waker: AtomicWaker::new(),
events: receiver,
})
}

pub(crate) fn poll_next(&mut self, _cx: &mut std::task::Context<'_>) -> Poll<HotplugEvent> {
Poll::Pending
pub(crate) fn poll_next(&mut self, cx: &mut std::task::Context<'_>) -> Poll<HotplugEvent> {
self.waker.register(cx.waker());
match self.events.try_recv() {
Ok(event) => Poll::Ready(event),
Err(TryRecvError::Empty) => Poll::Pending,
Err(TryRecvError::Disconnected) => Poll::Pending,
}
}
}
27 changes: 27 additions & 0 deletions src/platform/webusb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,31 @@ pub struct DeviceId {
pub(crate) id: usize,
}

impl DeviceId {
pub(crate) fn from_device(device: &UsbDevice) -> Self {
let key = JsValue::from_str("nusbUniqueId");
static INCREMENT: std::sync::LazyLock<std::sync::Mutex<usize>> =
std::sync::LazyLock::new(|| std::sync::Mutex::new(0));
let id = if let Ok(device_id) = Reflect::get(device, &key) {
device_id
.as_f64()
.expect("Expected an integer ID. This is a bug. Please report it.")
as usize
} else {
let mut lock = INCREMENT
.lock()
.expect("this should never be poisoned as we do not have multiple threads");
*lock += 1;
Reflect::set(device, &key, &JsValue::from_f64(*lock as f64))
.expect("Could not set ID on JS object. This is a bug. Please report it.");
*lock
};

DeviceId { id }
}
}

pub(crate) use hotplug::WebusbHotplugWatch as HotplugWatch;
use web_sys::js_sys::Reflect;
use web_sys::wasm_bindgen::JsValue;
use web_sys::UsbDevice;

0 comments on commit 1ab30cc

Please sign in to comment.