diff --git a/examples/hotplug.rs b/examples/hotplug.rs index 467e3e7..7d0f023 100644 --- a/examples/hotplug.rs +++ b/examples/hotplug.rs @@ -1,5 +1,4 @@ -use rusb::{Context, Device, UsbContext}; -use std::option::Option::Some; +use rusb::{Context, Device, HotplugBuilder, UsbContext}; struct HotPlugHandler; @@ -13,22 +12,30 @@ impl rusb::Hotplug for HotPlugHandler { } } +impl Drop for HotPlugHandler { + fn drop(&mut self) { + println!("HotPlugHandler dropped"); + } +} + fn main() -> rusb::Result<()> { if rusb::has_hotplug() { let context = Context::new()?; - let mut reg = Some(context.hotplug_register_callback( - None, - None, - None, - true, - Box::new(HotPlugHandler {}), - )?); + + let mut reg = Some( + HotplugBuilder::new() + .enumerate(true) + .register(&context, Box::new(HotPlugHandler {}))?, + ); + loop { context.handle_events(None).unwrap(); if let Some(reg) = reg.take() { - context.hotplug_unregister_callback(reg); + context.unregister_callback(reg); + break; } } + Ok(()) } else { eprint!("libusb hotplug api unsupported"); Ok(()) diff --git a/src/context.rs b/src/context.rs index c04de0d..da0ec57 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,11 +1,12 @@ -use libc::{c_int, c_void, timeval}; +use libc::{c_int, timeval}; use std::{mem, ptr, sync::Arc, sync::Once, time::Duration}; #[cfg(unix)] use std::os::unix::io::RawFd; -use crate::{device::Device, device_handle::DeviceHandle, device_list::DeviceList, error}; +use crate::hotplug::{Hotplug, HotplugBuilder, Registration}; +use crate::{device_handle::DeviceHandle, device_list::DeviceList, error}; use libusb1_sys::{constants::*, *}; #[cfg(windows)] @@ -44,36 +45,6 @@ impl Drop for ContextInner { unsafe impl Sync for Context {} unsafe impl Send for Context {} -pub trait Hotplug: Send { - fn device_arrived(&mut self, device: Device); - fn device_left(&mut self, device: Device); -} - -#[derive(Debug)] -#[must_use = "USB hotplug callbacks will be deregistered if the registration is dropped"] -pub struct Registration { - context: T, - handle: libusb_hotplug_callback_handle, -} - -impl Registration { - fn get_handle(&self) -> libusb_hotplug_callback_handle { - self.handle - } -} - -impl Drop for Registration { - fn drop(&mut self) { - let _call_back: Box>; - #[cfg(libusb_hotplug_get_user_data)] - unsafe { - let user_data = libusb_hotplug_get_user_data(self.context.as_raw(), self.get_handle()); - _call_back = Box::>::from_raw(user_data as _); - } - unsafe { libusb_hotplug_deregister_callback(self.context.as_raw(), self.get_handle()) } - } -} - pub trait UsbContext: Clone + Sized + Send + Sync { /// Get the raw libusb_context pointer, for advanced use in unsafe code. fn as_raw(&self) -> *mut libusb_context; @@ -137,72 +108,46 @@ pub trait UsbContext: Clone + Sized + Send + Sync { /// [Hotplug::device_arrived] method is called when a new device is added to /// the bus, and [Hotplug::device_left] is called when it is removed. /// - /// Devices can optionally be filtered by `vendor_id` and `device_id`. If - /// `enumerate` is `true`, then devices that are already connected will - /// cause your callback's `device_arrived()` method to be called for them. + /// Devices can optionally be filtered by vendor (`vendor_id`) and device id + /// (`product_id`). /// /// The callback will remain registered until the returned [Registration] is - /// dropped, which can be done explicitly with [Context::hotplug_unregister_callback]. + /// dropped, which can be done explicitly with [Context::unregister_callback]. /// /// When handling a [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 + /// any `rusb` function that takes a [crate::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. - fn hotplug_register_callback( + #[deprecated(since = "0.9.0", note = "Use HotplugBuilder")] + fn register_callback( &self, vendor_id: Option, product_id: Option, class: Option, - enumerate: bool, callback: Box>, ) -> crate::Result> { - let mut handle: libusb_hotplug_callback_handle = 0; - let callback = CallbackData { - context: self.clone(), - hotplug: callback, - }; - - let hotplug_flags = if enumerate { - LIBUSB_HOTPLUG_ENUMERATE - } else { - LIBUSB_HOTPLUG_NO_FLAGS - }; - - let to = Box::new(callback); + let mut builder = HotplugBuilder::new(); - let n = unsafe { - libusb_hotplug_register_callback( - self.as_raw(), - LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, - hotplug_flags, - vendor_id - .map(c_int::from) - .unwrap_or(LIBUSB_HOTPLUG_MATCH_ANY), - product_id - .map(c_int::from) - .unwrap_or(LIBUSB_HOTPLUG_MATCH_ANY), - class.map(c_int::from).unwrap_or(LIBUSB_HOTPLUG_MATCH_ANY), - hotplug_callback::, - Box::into_raw(to) as *mut c_void, - &mut handle, - ) - }; - if n < 0 { - Err(error::from_libusb(n)) - } else { - Ok(Registration { - context: self.clone(), - handle, - }) + let mut builder = &mut builder; + if let Some(vendor_id) = vendor_id { + builder = builder.vendor_id(vendor_id) + } + if let Some(product_id) = product_id { + builder = builder.product_id(product_id) + } + if let Some(class) = class { + builder = builder.class(class) } + + builder.register(self, callback) } /// Unregisters the callback corresponding to the given registration. The /// same thing can be achieved by dropping the registration. - fn hotplug_unregister_callback(&self, _reg: Registration) {} + fn unregister_callback(&self, _reg: Registration) {} fn handle_events(&self, timeout: Option) -> crate::Result<()> { let n = unsafe { @@ -277,11 +222,6 @@ impl UsbContext for GlobalContext { } } -struct CallbackData { - context: T, - hotplug: Box>, -} - impl Context { /// Opens a new `libusb` context. pub fn new() -> crate::Result { @@ -310,32 +250,6 @@ impl Context { } } -extern "system" fn hotplug_callback( - _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) }; - 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, - } -} - /// Library logging levels. #[derive(Clone, Copy)] pub enum LogLevel { diff --git a/src/hotplug.rs b/src/hotplug.rs new file mode 100644 index 0000000..e067ffe --- /dev/null +++ b/src/hotplug.rs @@ -0,0 +1,211 @@ +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: Send { + fn device_arrived(&mut self, device: Device); + fn device_left(&mut self, device: Device); +} + +#[derive(Debug)] +#[must_use = "USB hotplug callbacks will be deregistered if the registration is dropped"] +pub struct Registration { + handle: libusb_hotplug_callback_handle, + call_back: Box>, +} + +impl Registration { + fn get_handle(&self) -> libusb_hotplug_callback_handle { + self.handle + } +} + +impl Drop for Registration { + 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, + product_id: Option, + class: Option, + 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>( + self, + context: T, + callback: Box>, + ) -> crate::Result> { + 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::, + user_data, + &mut handle, + ) + }; + if n < 0 { + Err(error::from_libusb(n)) + } else { + Ok(Registration { handle, call_back }) + } + } +} + +struct CallbackData { + context: T, + hotplug: Box>, +} + +impl Debug for CallbackData +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( + _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) }; + 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, + } +} diff --git a/src/lib.rs b/src/lib.rs index fde24d0..85d83bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ pub use libusb1_sys::constants; pub use crate::{ config_descriptor::{ConfigDescriptor, Interfaces}, - context::{Context, GlobalContext, Hotplug, LogLevel, Registration, UsbContext}, + context::{Context, GlobalContext, LogLevel, UsbContext}, device::Device, device_descriptor::DeviceDescriptor, device_handle::DeviceHandle, @@ -16,6 +16,7 @@ pub use crate::{ request_type, Direction, Recipient, RequestType, Speed, SyncType, TransferType, UsageType, Version, }, + hotplug::{Hotplug, HotplugBuilder, Registration}, interface_descriptor::{ EndpointDescriptors, Interface, InterfaceDescriptor, InterfaceDescriptors, }, @@ -41,6 +42,7 @@ mod config_descriptor; mod device_descriptor; mod endpoint_descriptor; mod fields; +mod hotplug; mod interface_descriptor; mod language; mod options;