blob: e067ffe100abab7db646e1663e565f2337d1d99b [file] [log] [blame] [edit]
use crate::constants::{
LIBUSB_HOTPLUG_ENUMERATE, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED,
LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_NO_FLAGS,
};
use crate::ffi::{
libusb_context, libusb_device, libusb_hotplug_callback_handle,
libusb_hotplug_deregister_callback, libusb_hotplug_event, libusb_hotplug_register_callback,
};
use crate::{error, Device, UsbContext};
use std::{
borrow::Borrow,
ffi::c_void,
fmt::{self, Debug},
os::raw::c_int,
};
/// When handling a [method@Hotplug::device_arrived] event it is considered safe to call
/// any `rusb` function that takes a [`Device`]. It also safe to open a device and
/// submit **asynchronous** transfers.
/// However, most other functions that take a [`DeviceHandle`] are **not safe** to call.
/// Examples of such functions are any of the synchronous API functions or
/// the blocking functions that retrieve various USB descriptors.
/// These functions must be used outside of the context of the [Hotplug] functions.
///
/// [`Device`]: crate::Device
/// [`DeviceHandle`]: crate::DeviceHandle
/// [`Context::unregister_callback`]: method@crate::Context::unregister_callback
pub trait Hotplug<T: UsbContext>: Send {
fn device_arrived(&mut self, device: Device<T>);
fn device_left(&mut self, device: Device<T>);
}
#[derive(Debug)]
#[must_use = "USB hotplug callbacks will be deregistered if the registration is dropped"]
pub struct Registration<T: UsbContext> {
handle: libusb_hotplug_callback_handle,
call_back: Box<CallbackData<T>>,
}
impl<T: UsbContext> Registration<T> {
fn get_handle(&self) -> libusb_hotplug_callback_handle {
self.handle
}
}
impl<T: UsbContext> Drop for Registration<T> {
fn drop(&mut self) {
unsafe {
libusb_hotplug_deregister_callback(self.call_back.context.as_raw(), self.get_handle())
}
}
}
#[derive(Copy, Clone, Debug, Default)]
#[doc(alias = "libusb_hotplug_register_callback")]
/// Builds hotplug [Registration] with custom configuration values.
pub struct HotplugBuilder {
vendor_id: Option<u16>,
product_id: Option<u16>,
class: Option<u8>,
enumerate: bool,
}
impl HotplugBuilder {
/// Returns a new builder with the no filter
/// Devices can optionally be filtered by [HotplugBuilder::vendor_id]
/// and [HotplugBuilder::product_id]
///
/// Registration is done by by calling [`register`].
///
/// [`register`]: method@Self::register
pub fn new() -> Self {
HotplugBuilder {
vendor_id: None,
product_id: None,
class: None,
enumerate: false,
}
}
/// Devices can optionally be filtered by vendor
pub fn vendor_id(&mut self, vendor_id: u16) -> &mut Self {
self.vendor_id = Some(vendor_id);
self
}
/// Devices can optionally be filtered by product id
pub fn product_id(&mut self, product_id: u16) -> &mut Self {
self.product_id = Some(product_id);
self
}
/// Devices can optionally be filtered by class
pub fn class(&mut self, class: u8) -> &mut Self {
self.class = Some(class);
self
}
/// If `enumerate` is `true`, then devices that are already
/// connected will cause your callback's [Hotplug::device_arrived] method to be
/// called for them.
pub fn enumerate(&mut self, enumerate: bool) -> &mut Self {
self.enumerate = enumerate;
self
}
/// Register a `callback` to be called on hotplug events. The callback's
/// [method@Hotplug::device_arrived] method is called when a new device is added to
/// the bus, and [method@Hotplug::device_left] is called when it is removed.
///
/// The callback will remain registered until the returned [Registration] is
/// dropped, which can be done explicitly with [`Context::unregister_callback`].
///
/// When handling a [method@Hotplug::device_arrived] event it is considered safe to call
/// any `rusb` function that takes a [`Device`]. It also safe to open a device and
/// submit **asynchronous** transfers.
/// However, most other functions that take a [`DeviceHandle`] are **not safe** to call.
/// Examples of such functions are any of the synchronous API functions or
/// the blocking functions that retrieve various USB descriptors.
/// These functions must be used outside of the context of the [Hotplug] functions.
///
/// [`Device`]: crate::Device
/// [`DeviceHandle`]: crate::DeviceHandle
/// [`Context::unregister_callback`]: method@crate::Context::unregister_callback
pub fn register<U: UsbContext, T: Borrow<U>>(
self,
context: T,
callback: Box<dyn Hotplug<U>>,
) -> crate::Result<Registration<U>> {
let mut handle: libusb_hotplug_callback_handle = 0;
let mut call_back = Box::new(CallbackData {
context: context.borrow().clone(),
hotplug: callback,
});
let hotplug_flags = if self.enumerate {
LIBUSB_HOTPLUG_ENUMERATE
} else {
LIBUSB_HOTPLUG_NO_FLAGS
};
let user_data = &mut *call_back as *mut _ as *mut _;
let n = unsafe {
libusb_hotplug_register_callback(
context.borrow().as_raw(),
LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
hotplug_flags,
self.vendor_id
.map(c_int::from)
.unwrap_or(LIBUSB_HOTPLUG_MATCH_ANY),
self.product_id
.map(c_int::from)
.unwrap_or(LIBUSB_HOTPLUG_MATCH_ANY),
self.class
.map(c_int::from)
.unwrap_or(LIBUSB_HOTPLUG_MATCH_ANY),
hotplug_callback::<U>,
user_data,
&mut handle,
)
};
if n < 0 {
Err(error::from_libusb(n))
} else {
Ok(Registration { handle, call_back })
}
}
}
struct CallbackData<T: UsbContext> {
context: T,
hotplug: Box<dyn Hotplug<T>>,
}
impl<T> Debug for CallbackData<T>
where
T: UsbContext + Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("CallbackData")
.field("context", &self.context)
.finish()
}
}
pub extern "system" fn hotplug_callback<T: UsbContext>(
_ctx: *mut libusb_context,
device: *mut libusb_device,
event: libusb_hotplug_event,
user_data: *mut c_void,
) -> c_int {
let ret = std::panic::catch_unwind(|| {
let reg = unsafe { &mut *(user_data as *mut CallbackData<T>) };
let device = unsafe {
Device::from_libusb(
reg.context.clone(),
std::ptr::NonNull::new_unchecked(device),
)
};
match event {
LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED => reg.hotplug.device_arrived(device),
LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT => reg.hotplug.device_left(device),
_ => (),
};
});
match ret {
Ok(_) => 0,
Err(_) => 1,
}
}