| use libc::{c_char, c_int, c_void, timeval}; |
| |
| use std::{ |
| cmp::Ordering, ffi::CStr, mem, ptr, sync::Arc, sync::Mutex, sync::Once, sync::OnceLock, |
| time::Duration, |
| }; |
| |
| #[cfg(unix)] |
| use std::os::unix::io::RawFd; |
| |
| use crate::hotplug::{Hotplug, HotplugBuilder, Registration}; |
| use crate::{device_handle::DeviceHandle, device_list::DeviceList, error}; |
| use libusb1_sys::{constants::*, *}; |
| |
| #[cfg(windows)] |
| type Seconds = ::libc::c_long; |
| #[cfg(windows)] |
| type MicroSeconds = ::libc::c_long; |
| |
| #[cfg(not(windows))] |
| type Seconds = ::libc::time_t; |
| #[cfg(not(windows))] |
| type MicroSeconds = ::libc::suseconds_t; |
| |
| #[derive(Copy, Clone, Eq, PartialEq, Default)] |
| pub struct GlobalContext {} |
| |
| /// A `libusb` context. |
| #[derive(Clone, Debug, Eq, PartialEq)] |
| pub struct Context { |
| context: Arc<ContextInner>, |
| } |
| |
| #[derive(Debug, Eq, PartialEq)] |
| struct ContextInner { |
| inner: ptr::NonNull<libusb_context>, |
| } |
| |
| impl Drop for ContextInner { |
| /// Closes the `libusb` context. |
| fn drop(&mut self) { |
| unsafe { |
| libusb_exit(self.inner.as_ptr()); |
| } |
| } |
| } |
| |
| unsafe impl Sync for Context {} |
| unsafe impl Send for Context {} |
| |
| type LogCallback = Box<dyn Fn(LogLevel, String)>; |
| |
| struct LogCallbackMap { |
| map: std::collections::HashMap<*mut libusb_context, LogCallback>, |
| } |
| |
| unsafe impl Sync for LogCallbackMap {} |
| unsafe impl Send for LogCallbackMap {} |
| |
| impl LogCallbackMap { |
| pub fn new() -> Self { |
| Self { |
| map: std::collections::HashMap::new(), |
| } |
| } |
| } |
| |
| static LOG_CALLBACK_MAP: OnceLock<Mutex<LogCallbackMap>> = OnceLock::new(); |
| |
| extern "system" fn static_log_callback( |
| context: *mut libusb_context, |
| level: c_int, |
| text: *mut c_void, |
| ) { |
| if let Some(log_callback_map) = LOG_CALLBACK_MAP.get() { |
| if let Ok(locked_table) = log_callback_map.lock() { |
| if let Some(logger) = locked_table.map.get(&context) { |
| let c_str: &CStr = unsafe { CStr::from_ptr(text as *const c_char) }; |
| let str_slice: &str = c_str.to_str().unwrap_or(""); |
| let log_message = str_slice.to_owned(); |
| |
| logger(LogLevel::from_c_int(level), log_message); |
| } |
| } |
| } |
| } |
| |
| 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; |
| |
| /// Returns a list of the current USB devices. |
| fn devices(&self) -> crate::Result<DeviceList<Self>> { |
| DeviceList::new_with_context(self.clone()) |
| } |
| |
| /// 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 |
| /// iterate a [`DeviceList`](struct.DeviceList.html). It is not meant for production |
| /// applications. |
| /// |
| /// Returns a device handle for the first device found matching `vendor_id` and `product_id`. |
| /// On error, or if the device could not be found, it returns `None`. |
| fn open_device_with_vid_pid( |
| &self, |
| vendor_id: u16, |
| product_id: u16, |
| ) -> Option<DeviceHandle<Self>> { |
| let handle = |
| unsafe { libusb_open_device_with_vid_pid(self.as_raw(), vendor_id, product_id) }; |
| let ptr = std::ptr::NonNull::new(handle)?; |
| Some(unsafe { DeviceHandle::from_libusb(self.clone(), ptr) }) |
| } |
| |
| /// Opens the device with a pre-opened file descriptor. |
| /// |
| /// This is UNIX-only and platform-specific. It is currently working with |
| /// Linux/Android, but might work with other systems in the future. |
| /// |
| /// Note: This function does not take ownership of the specified file |
| /// descriptor. The caller has the responsibility of keeping it opened for |
| /// as long as the device handle. |
| #[cfg(unix)] |
| #[doc(alias = "libusb_wrap_sys_device")] |
| unsafe fn open_device_with_fd(&self, fd: RawFd) -> crate::Result<DeviceHandle<Self>> { |
| let mut handle = mem::MaybeUninit::<*mut libusb_device_handle>::uninit(); |
| |
| match libusb_wrap_sys_device(self.as_raw(), fd as _, handle.as_mut_ptr()) { |
| 0 => { |
| let ptr = |
| std::ptr::NonNull::new(handle.assume_init()).ok_or(crate::Error::NoDevice)?; |
| |
| Ok(DeviceHandle::from_libusb(self.clone(), ptr)) |
| } |
| err => Err(error::from_libusb(err)), |
| } |
| } |
| |
| /// Sets the log level of a `libusb` for context. |
| fn set_log_level(&mut self, level: LogLevel) { |
| unsafe { |
| libusb_set_debug(self.as_raw(), level.as_c_int()); |
| } |
| } |
| |
| fn set_log_callback(&mut self, log_callback: LogCallback, mode: LogCallbackMode) { |
| let log_callback_map = LOG_CALLBACK_MAP.get_or_init(|| Mutex::new(LogCallbackMap::new())); |
| if let Ok(mut locked_table) = log_callback_map.lock() { |
| locked_table.map.insert(self.as_raw(), log_callback); |
| } |
| |
| unsafe { |
| libusb_set_log_cb(self.as_raw(), Some(static_log_callback), mode.as_c_int()); |
| } |
| } |
| |
| /// Register a callback to be called on hotplug events. The callback's |
| /// [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 (`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::unregister_callback]. |
| /// |
| /// When handling a [Hotplug::device_arrived] event it is considered safe to call |
| /// 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. |
| #[deprecated(since = "0.9.0", note = "Use HotplugBuilder")] |
| fn register_callback( |
| &self, |
| vendor_id: Option<u16>, |
| product_id: Option<u16>, |
| class: Option<u8>, |
| callback: Box<dyn Hotplug<Self>>, |
| ) -> crate::Result<Registration<Self>> { |
| let mut builder = HotplugBuilder::new(); |
| |
| 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 unregister_callback(&self, _reg: Registration<Self>) {} |
| |
| /// Handle any pending events. |
| /// If timeout less then 1 microseconds then this function will handle any already-pending |
| /// events and then immediately return in non-blocking style. |
| /// If timeout is [None] then function will handle any pending events in blocking mode. |
| fn handle_events(&self, timeout: Option<Duration>) -> crate::Result<()> { |
| let n = unsafe { |
| match timeout { |
| Some(t) => { |
| let tv = timeval { |
| tv_sec: t.as_secs() as Seconds, |
| tv_usec: t.subsec_nanos() as MicroSeconds / 1000, |
| }; |
| libusb_handle_events_timeout_completed(self.as_raw(), &tv, ptr::null_mut()) |
| } |
| None => libusb_handle_events_completed(self.as_raw(), ptr::null_mut()), |
| } |
| }; |
| if n < 0 { |
| Err(error::from_libusb(n as c_int)) |
| } else { |
| Ok(()) |
| } |
| } |
| |
| /// Interrupt any active thread that is handling events (for example with |
| /// [handle_events][`Self::handle_events()`]). |
| #[doc(alias = "libusb_interrupt_event_handler")] |
| fn interrupt_handle_events(&self) { |
| unsafe { libusb_interrupt_event_handler(self.as_raw()) } |
| } |
| |
| fn next_timeout(&self) -> crate::Result<Option<Duration>> { |
| let mut tv = timeval { |
| tv_sec: 0, |
| tv_usec: 0, |
| }; |
| let n = unsafe { libusb_get_next_timeout(self.as_raw(), &mut tv) }; |
| |
| match n.cmp(&0) { |
| Ordering::Less => Err(error::from_libusb(n as c_int)), |
| Ordering::Equal => Ok(None), |
| Ordering::Greater => { |
| let duration = Duration::new(tv.tv_sec as _, (tv.tv_usec * 1000) as _); |
| Ok(Some(duration)) |
| } |
| } |
| } |
| } |
| |
| impl UsbContext for Context { |
| fn as_raw(&self) -> *mut libusb_context { |
| self.context.inner.as_ptr() |
| } |
| } |
| |
| impl UsbContext for GlobalContext { |
| fn as_raw(&self) -> *mut libusb_context { |
| static mut USB_CONTEXT: *mut libusb_context = ptr::null_mut(); |
| static ONCE: Once = Once::new(); |
| |
| ONCE.call_once(|| { |
| let mut context = mem::MaybeUninit::<*mut libusb_context>::uninit(); |
| unsafe { |
| USB_CONTEXT = match libusb_init(context.as_mut_ptr()) { |
| 0 => context.assume_init(), |
| err => panic!( |
| "Can't init Global usb context, error {:?}", |
| error::from_libusb(err) |
| ), |
| } |
| }; |
| }); |
| // Clone data that is safe to use concurrently. |
| unsafe { USB_CONTEXT } |
| } |
| } |
| |
| impl Context { |
| /// Opens a new `libusb` context. |
| pub fn new() -> crate::Result<Self> { |
| let mut context = mem::MaybeUninit::<*mut libusb_context>::uninit(); |
| |
| try_unsafe!(libusb_init(context.as_mut_ptr())); |
| |
| Ok(unsafe { Self::from_raw(context.assume_init()) }) |
| } |
| |
| /// Creates a new `libusb` context and sets runtime options. |
| pub fn with_options(opts: &[crate::UsbOption]) -> crate::Result<Self> { |
| let mut this = Self::new()?; |
| |
| for opt in opts { |
| opt.apply(&mut this)?; |
| } |
| |
| Ok(this) |
| } |
| |
| /// Creates rusb Context from existing libusb context. |
| /// Note: This transfers ownership of the context to Rust. |
| /// # Safety |
| /// This is unsafe because it does not check if the context is valid, |
| /// so the caller must guarantee that libusb_context is created properly. |
| pub unsafe fn from_raw(raw: *mut libusb_context) -> Self { |
| Context { |
| context: Arc::new(ContextInner { |
| inner: ptr::NonNull::new_unchecked(raw), |
| }), |
| } |
| } |
| } |
| |
| /// Library logging levels. |
| #[derive(Clone, Copy)] |
| pub enum LogLevel { |
| /// No messages are printed by `libusb` (default). |
| None, |
| |
| /// Error messages printed to `stderr`. |
| Error, |
| |
| /// Warning and error messages are printed to `stderr`. |
| Warning, |
| |
| /// Informational messages are printed to `stdout`. Warnings and error messages are printed to |
| /// `stderr`. |
| Info, |
| |
| /// Debug and informational messages are printed to `stdout`. Warnings and error messages are |
| /// printed to `stderr`. |
| Debug, |
| } |
| |
| impl LogLevel { |
| pub(crate) fn as_c_int(self) -> c_int { |
| match self { |
| LogLevel::None => LIBUSB_LOG_LEVEL_NONE, |
| LogLevel::Error => LIBUSB_LOG_LEVEL_ERROR, |
| LogLevel::Warning => LIBUSB_LOG_LEVEL_WARNING, |
| LogLevel::Info => LIBUSB_LOG_LEVEL_INFO, |
| LogLevel::Debug => LIBUSB_LOG_LEVEL_DEBUG, |
| } |
| } |
| |
| fn from_c_int(value: c_int) -> LogLevel { |
| match value { |
| LIBUSB_LOG_LEVEL_ERROR => LogLevel::Error, |
| LIBUSB_LOG_LEVEL_WARNING => LogLevel::Warning, |
| LIBUSB_LOG_LEVEL_INFO => LogLevel::Info, |
| LIBUSB_LOG_LEVEL_DEBUG => LogLevel::Debug, |
| _ => LogLevel::None, |
| } |
| } |
| } |
| |
| pub enum LogCallbackMode { |
| /// Callback function handling all log messages. |
| Global, |
| |
| /// Callback function handling context related log messages. |
| Context, |
| } |
| |
| impl LogCallbackMode { |
| fn as_c_int(&self) -> c_int { |
| match *self { |
| LogCallbackMode::Global => LIBUSB_LOG_CB_GLOBAL, |
| LogCallbackMode::Context => LIBUSB_LOG_CB_CONTEXT, |
| } |
| } |
| } |