nusb 0.2.7

Cross-platform low-level access to USB devices in pure Rust
Documentation
use std::{
    collections::VecDeque,
    sync::{Arc, Mutex},
    task::{Poll, Waker},
};

use web_sys::{
    wasm_bindgen::{prelude::Closure, JsCast},
    UsbConnectionEvent,
};

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

use super::{enumeration::device_to_info, DeviceId};

struct Inner {
    waker: Option<Waker>,
    events: VecDeque<HotplugEvent>,
}

pub(crate) struct WebusbHotplugWatch {
    inner: Arc<Mutex<Inner>>,
    onconnect: Closure<dyn FnMut(UsbConnectionEvent)>,
    ondisconnect: Closure<dyn FnMut(UsbConnectionEvent)>,
}

fn push(inner: &Mutex<Inner>, event: HotplugEvent) {
    let waker = {
        let mut guard = inner.lock().unwrap();
        guard.events.push_back(event);
        guard.waker.take()
    };
    if let Some(w) = waker {
        w.wake();
    }
}

impl WebusbHotplugWatch {
    pub fn new() -> Result<Self, Error> {
        let usb = super::usb()?;
        let inner = Arc::new(Mutex::new(Inner {
            waker: None,
            events: VecDeque::new(),
        }));

        let onconnect = {
            let inner = inner.clone();
            Closure::wrap(Box::new(move |event: UsbConnectionEvent| {
                push(
                    &inner,
                    HotplugEvent::Connected(device_to_info(event.device())),
                )
            }) as Box<dyn FnMut(UsbConnectionEvent)>)
        };
        usb.add_event_listener_with_callback("connect", onconnect.as_ref().unchecked_ref())
            .map_err(super::js_value_to_error)?;

        let ondisconnect = {
            let inner = inner.clone();
            Closure::wrap(Box::new(move |event: UsbConnectionEvent| {
                let id = crate::DeviceId(DeviceId::from_device(&event.device()));
                push(&inner, HotplugEvent::Disconnected(id));
            }) as Box<dyn FnMut(UsbConnectionEvent)>)
        };
        usb.add_event_listener_with_callback("disconnect", ondisconnect.as_ref().unchecked_ref())
            .map_err(super::js_value_to_error)?;

        Ok(Self {
            inner,
            onconnect,
            ondisconnect,
        })
    }

    pub(crate) fn poll_next(&mut self, cx: &mut std::task::Context<'_>) -> Poll<HotplugEvent> {
        let mut guard = self.inner.lock().unwrap();
        if let Some(event) = guard.events.pop_front() {
            Poll::Ready(event)
        } else {
            guard.waker = Some(cx.waker().clone());
            Poll::Pending
        }
    }
}

impl Drop for WebusbHotplugWatch {
    fn drop(&mut self) {
        if let Ok(usb) = super::usb() {
            let _ = usb.remove_event_listener_with_callback(
                "connect",
                self.onconnect.as_ref().unchecked_ref(),
            );
            let _ = usb.remove_event_listener_with_callback(
                "disconnect",
                self.ondisconnect.as_ref().unchecked_ref(),
            );
        }
    }
}