Skip to content

Commit

Permalink
Implement the asynchronous hotplug API
Browse files Browse the repository at this point in the history
  • Loading branch information
StephanvanSchaik committed Nov 8, 2022
1 parent 2fdb5c7 commit b40623b
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 0 deletions.
1 change: 1 addition & 0 deletions rusb-async/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ vendored = [ "rusb/vendored" ]

[dependencies]
async-trait = "0.1"
futures = "0.3"
rusb = { path = "..", version = "0.9.1" }
libc = "0.2"

Expand Down
29 changes: 29 additions & 0 deletions rusb-async/examples/async_hotplug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use rusb::Error;
use rusb_async::{Context, HotplugBuilder, HotplugEvent, Registration};

#[tokio::main]
async fn main() -> Result<(), Error> {
if !rusb::has_hotplug() {
eprint!("libusb hotplug api unsupported");
return Ok(());
}

let mut context = Context::new()?;

let mut registration: Registration<Context> = HotplugBuilder::new()
.enumerate(true)
.register(&mut context)?;

while let Some(event) = registration.next_event().await {
match event {
HotplugEvent::Arrived(device) => {
println!("device arrived {:?}", device);
}
HotplugEvent::Left(device) => {
println!("device left {:?}", device);
}
}
}

Ok(())
}
111 changes: 111 additions & 0 deletions rusb-async/src/hotplug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use rusb::{Device, Error, UsbContext};

use futures::{
channel::mpsc::{channel, Receiver, Sender},
SinkExt, StreamExt,
};
use std::borrow::Borrow;

/// Events retrieved by polling the [`Registration`] whenever new USB devices arrive or existing
/// USB devices leave.
#[derive(Debug)]
pub enum HotplugEvent<T: UsbContext> {
/// A new device arrived.
Arrived(Device<T>),
/// The specified device left.
Left(Device<T>),
}

/// Builds hotplug [`Registration`] with custom configuration values.
pub struct HotplugBuilder {
inner: rusb::HotplugBuilder,
}

impl HotplugBuilder {
/// Returns a new builder with no filter. Devices can optionally be filtered by
/// [`HotplugBuilder::vendor_id`], [`HotplugBuilder::product_id`] and
/// [`HotplugBuilder::class`].
///
/// Registration is done by calling [`HotplugBuilder::register`].
pub fn new() -> Self {
Self {
inner: rusb::HotplugBuilder::new(),
}
}

/// Devices can optionally be filtered by their vendor ID.
pub fn vendor_id(&mut self, vendor_id: u16) -> &mut Self {
self.inner.vendor_id(vendor_id);
self
}

/// Devices can optionally be filtered by their product ID.
pub fn product_id(&mut self, product_id: u16) -> &mut Self {
self.inner.product_id(product_id);
self
}

/// Devices can optionally be filtered by their class.
pub fn class(&mut self, class: u8) -> &mut Self {
self.inner.class(class);
self
}

/// If `enumerate` is `true`, then devices that are already connected will cause the
/// [`Registration`] to return [`HotplugEvent::Arrived`] events for them.
pub fn enumerate(&mut self, enumerate: bool) -> &mut Self {
self.inner.enumerate(enumerate);
self
}

/// Registers the hotplug configuration and returns a [`Registration`] object that can be
/// polled for [`HotplugEvents`](HotplugEvent).
pub fn register<U: rusb::UsbContext + 'static, T: Borrow<U>>(
&mut self,
context: T,
) -> Result<Registration<U>, Error> {
let (tx, rx): (Sender<HotplugEvent<U>>, Receiver<HotplugEvent<U>>) = channel(1);

let hotplug = Box::new(Hotplug {
tx,
});

let inner = self.inner.register(context, hotplug)?;

Ok(Registration {
_inner: inner,
rx,
})
}
}

struct Hotplug<T: UsbContext> {
tx: Sender<HotplugEvent<T>>,
}

impl<T: UsbContext> rusb::Hotplug<T> for Hotplug<T> {
fn device_arrived(&mut self, device: Device<T>) {
futures::executor::block_on(async {
self.tx.send(HotplugEvent::Arrived(device)).await.unwrap();
})
}

fn device_left(&mut self, device: Device<T>) {
futures::executor::block_on(async {
self.tx.send(HotplugEvent::Left(device)).await.unwrap();
});
}
}

/// The hotplug registration which can be polled for [`HotplugEvents`](HotplugEvent).
pub struct Registration<T: UsbContext> {
_inner: rusb::Registration<T>,
rx: Receiver<HotplugEvent<T>>,
}

impl<T: UsbContext> Registration<T> {
/// Creates a future to await the next [`HotplugEvent`].
pub async fn next_event(&mut self) -> Option<HotplugEvent<T>> {
self.rx.next().await
}
}
2 changes: 2 additions & 0 deletions rusb-async/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
pub mod context;
pub mod hotplug;
pub mod transfer;

pub use crate::context::Context;
pub use crate::hotplug::{HotplugBuilder, HotplugEvent, Registration};
pub use crate::transfer::{CancellationToken, DeviceHandleExt, Transfer};

0 comments on commit b40623b

Please sign in to comment.