diff --git a/Cargo.toml b/Cargo.toml index 577deec..ea5a909 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["usb", "libusb", "hardware", "bindings"] [dependencies] bit-set = "0.2.0" -libusb-sys = "0.2.3" +libusb-sys = { path = "../libusb-sys" } libc = "0.2" [dev-dependencies] diff --git a/examples/hotplug_events.rs b/examples/hotplug_events.rs new file mode 100644 index 0000000..133a2a1 --- /dev/null +++ b/examples/hotplug_events.rs @@ -0,0 +1,25 @@ +extern crate libusb; + +use std::error::Error; + +#[derive(Debug)] +struct Endpoint { + config: u8, + iface: u8, + setting: u8, + address: u8 +} + +fn main() -> Result<(), Box> { + let mut ctx = libusb::Context::new()?; + let filter = libusb::HotplugFilter::new() + .enumerate(); + ctx.register_callback(filter, |device, event| { + eprintln!("invoked"); + println!("{:?} - {:?}", device.device_descriptor(), event); + })?; + loop { + ctx.handle_events(); + } +} + diff --git a/src/config_descriptor.rs b/src/config_descriptor.rs index c982f2e..bdfd626 100644 --- a/src/config_descriptor.rs +++ b/src/config_descriptor.rs @@ -13,6 +13,7 @@ pub struct ConfigDescriptor { impl Drop for ConfigDescriptor { fn drop(&mut self) { + eprintln!("Dropping a config descriptor"); unsafe { libusb_free_config_descriptor(self.descriptor); } diff --git a/src/context.rs b/src/context.rs index 4033cb4..26e156a 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,22 +1,31 @@ use std::marker::PhantomData; -use std::mem; +use std::mem::{self, ManuallyDrop}; use libc::c_int; use libusb::*; +use device::{self, Device}; use device_list::{self, DeviceList}; use device_handle::{self, DeviceHandle}; use error; +use event::HotPlugEvent; +use hotplug::{CallbackWrapper, HotplugFilter}; /// A `libusb` context. pub struct Context { context: *mut libusb_context, + cbs: Vec>, } impl Drop for Context { /// Closes the `libusb` context. fn drop(&mut self) { + eprintln!("Dropping a ctx"); + unsafe { + for ref cb in &self.cbs { + // TODO(richo) Deregister the callback + } libusb_exit(self.context); } } @@ -32,7 +41,10 @@ impl Context { try_unsafe!(libusb_init(&mut context)); - Ok(Context { context: context }) + Ok(Context { + context: context, + cbs: vec![], + }) } /// Sets the log level of a `libusb` context. @@ -83,6 +95,38 @@ impl Context { } } + + /// Register a callback to fire when a device attached or removed. + pub fn register_callback(&mut self, filter: HotplugFilter, closure: F) -> ::Result<()> + where F: Fn(&Device, HotPlugEvent) + 'static { + let mut wrapper = Box::new(CallbackWrapper { + closure: Box::new(closure), + handle: 0, + }); + let mut handle = 0; + let res = unsafe { libusb_hotplug_register_callback( + self.context, + filter.get_events(), + filter.get_flags(), + filter.get_vendor(), + filter.get_product(), + filter.get_class(), + invoke_callback, + &mut *wrapper as *mut _ as *mut ::std::ffi::c_void, + &mut handle) + }; + if res != LIBUSB_SUCCESS { + panic!("Couldn't setup callback"); + } + wrapper.handle = handle; + self.cbs.push(wrapper); + Ok(()) + } + + pub fn handle_events(&self) { + unsafe { libusb_handle_events(self.context) }; + } + /// Convenience function to open a device by its vendor ID and product ID. /// /// This function is provided as a convenience for building prototypes without having to @@ -103,6 +147,25 @@ impl Context { } } +extern "C" fn invoke_callback(_ctx: *mut libusb_context, device: *const libusb_device, event: i32, data: *mut std::ffi::c_void) -> i32 { + match HotPlugEvent::from_i32(event) { + Some(event) => { + let device = ManuallyDrop::new(unsafe { device::from_libusb(PhantomData, device as *mut libusb_device) }); + + let wrapper = data as *mut CallbackWrapper; + + unsafe { ((*wrapper).closure)(&device, event) }; + + 0 + }, + None => { + // With no meaningful way to signal this error condition we simply don't dispatch the + // call and return. + return 0; + } + } +} + /// Library logging levels. pub enum LogLevel { diff --git a/src/device.rs b/src/device.rs index cbfda2b..fed2fcc 100644 --- a/src/device.rs +++ b/src/device.rs @@ -11,6 +11,7 @@ use fields::{self, Speed}; /// A reference to a USB device. +#[derive(Debug)] pub struct Device<'a> { context: PhantomData<&'a Context>, device: *mut libusb_device, @@ -19,6 +20,7 @@ pub struct Device<'a> { impl<'a> Drop for Device<'a> { /// Releases the device reference. fn drop(&mut self) { + eprintln!("Dropping a device"); unsafe { libusb_unref_device(self.device); } diff --git a/src/device_handle.rs b/src/device_handle.rs index eb9c360..f0d8e70 100644 --- a/src/device_handle.rs +++ b/src/device_handle.rs @@ -25,6 +25,7 @@ pub struct DeviceHandle<'a> { impl<'a> Drop for DeviceHandle<'a> { /// Closes the device. fn drop(&mut self) { + eprintln!("Dropping a device handle"); unsafe { for iface in self.interfaces.iter() { libusb_release_interface(self.handle, iface as c_int); diff --git a/src/device_list.rs b/src/device_list.rs index 8a22e54..da25a91 100644 --- a/src/device_list.rs +++ b/src/device_list.rs @@ -16,6 +16,7 @@ pub struct DeviceList<'a> { impl<'a> Drop for DeviceList<'a> { /// Frees the device list. fn drop(&mut self) { + eprintln!("Dropping a device lisst"); unsafe { libusb_free_device_list(self.list, 1); } diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 0000000..14de886 --- /dev/null +++ b/src/event.rs @@ -0,0 +1,17 @@ +use libusb::*; + +#[derive(Debug, Clone, Copy)] +pub enum HotPlugEvent { + Arrived, + Left, +} + +impl HotPlugEvent { + pub fn from_i32(value: i32) -> Option { + match value { + e if e == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED => Some(HotPlugEvent::Arrived), + e if e == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT => Some(HotPlugEvent::Left), + _ => None, + } + } +} diff --git a/src/hotplug.rs b/src/hotplug.rs new file mode 100644 index 0000000..e3b820b --- /dev/null +++ b/src/hotplug.rs @@ -0,0 +1,86 @@ +use device::Device; +use event::HotPlugEvent; + +use libusb::*; + +pub struct CallbackWrapper { + pub closure: Box, + pub handle: i32, +} + +#[derive(Default)] +pub struct HotplugFilter { + vendor: Option, + product: Option, + class: Option, + events: Option, + enumerate: bool, +} + +impl HotplugFilter { + pub fn new() -> Self { + Self { + vendor: None, + product: None, + class: None, + events: None, + enumerate: false, + } + } + + pub(crate) fn get_vendor(&self) -> i32 { + self.vendor.unwrap_or(LIBUSB_HOTPLUG_MATCH_ANY) + } + + pub(crate) fn get_product(&self) -> i32 { + self.product.unwrap_or(LIBUSB_HOTPLUG_MATCH_ANY) + } + + pub(crate) fn get_class(&self) -> i32 { + self.class.unwrap_or(LIBUSB_HOTPLUG_MATCH_ANY) + } + + pub(crate) fn get_events(&self) -> i32 { + self.events.unwrap_or(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) + } + + pub(crate) fn get_flags(&self) -> i32 { + if self.enumerate { + LIBUSB_HOTPLUG_ENUMERATE + } else { + 0 + } + } + + pub fn vendor(mut self, vendor: i32) -> Self { + self.vendor = Some(vendor); + self + } + + pub fn product(mut self, product: i32) -> Self { + self.product = Some(product); + self + } + + pub fn class(mut self, class: i32) -> Self { + self.class = Some(class); + self + } + + pub fn arrived_only(mut self) -> Self { + self.events = Some(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED); + self + } + + pub fn left_only(mut self) -> Self { + self.events = Some(LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT); + self + } + + pub fn enumerate(mut self) -> Self { + self.enumerate = true; + self + } +} + + diff --git a/src/lib.rs b/src/lib.rs index 55cb3b4..22ed3bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ pub use context::{Context, LogLevel}; pub use device_list::{DeviceList, Devices}; pub use device::Device; pub use device_handle::DeviceHandle; +pub use hotplug::HotplugFilter; pub use fields::{Speed, TransferType, SyncType, UsageType, Direction, RequestType, Recipient, Version, request_type}; pub use device_descriptor::DeviceDescriptor; @@ -32,6 +33,7 @@ mod context; mod device_list; mod device; mod device_handle; +mod event; mod fields; mod device_descriptor; @@ -39,3 +41,4 @@ mod config_descriptor; mod interface_descriptor; mod endpoint_descriptor; mod language; +mod hotplug;