use libc::timeval;
use once_cell::sync::Lazy;
use std::{mem, os::raw::c_int, ptr, sync::Arc, time::Duration};
#[cfg(windows)]
use std::os::raw::c_long;
#[cfg(unix)]
use std::os::unix::io::RawFd;
use crate::{
device_handle::DeviceHandle,
device_list::DeviceList,
hotplug::{Hotplug, HotplugBuilder, Registration},
Error, Result,
};
use libusb1_sys::{self as ffi, constants::*};
#[cfg(windows)]
type Seconds = c_long;
#[cfg(windows)]
type MicroSeconds = c_long;
#[cfg(not(windows))]
type Seconds = libc::time_t;
#[cfg(not(windows))]
type MicroSeconds = libc::suseconds_t;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Context {
inner: Arc<ContextInner>,
}
#[derive(Debug, Eq, PartialEq)]
struct ContextInner(*mut ffi::libusb_context);
unsafe impl Sync for ContextInner {}
unsafe impl Send for ContextInner {}
impl Drop for ContextInner {
fn drop(&mut self) {
unsafe {
ffi::libusb_exit(self.0);
}
}
}
pub static GLOBAL_CONTEXT: Lazy<Context> = Lazy::new(|| {
let _ = unsafe { ffi::libusb_init(ptr::null_mut()) };
Context {
inner: Arc::new(ContextInner(ptr::null_mut())),
}
});
impl Context {
pub fn new() -> Result<Self> {
let mut context = mem::MaybeUninit::<*mut ffi::libusb_context>::uninit();
try_unsafe!(ffi::libusb_init(context.as_mut_ptr()));
Ok(unsafe { Self::from_raw(context.assume_init()) })
}
pub fn with_options(opts: &[crate::UsbOption]) -> Result<Self> {
let mut this = Self::new()?;
for opt in opts {
opt.apply(&mut this)?;
}
Ok(this)
}
pub fn global() -> Self {
Self::default()
}
pub fn is_global(&self) -> bool {
self.inner.0.is_null()
}
pub unsafe fn from_raw(raw: *mut ffi::libusb_context) -> Self {
Context {
inner: Arc::new(ContextInner(raw)),
}
}
pub fn as_raw(&self) -> *mut ffi::libusb_context {
self.inner.0
}
pub fn devices(&self) -> Result<DeviceList> {
DeviceList::new_with_context(self.clone())
}
pub fn open_device_with_vid_pid(
&self,
vendor_id: u16,
product_id: u16,
) -> Option<DeviceHandle> {
let handle =
unsafe { ffi::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) })
}
#[cfg(unix)]
#[doc(alias = "libusb_wrap_sys_device")]
pub unsafe fn open_device_with_fd(&self, fd: RawFd) -> Result<DeviceHandle> {
let mut handle = mem::MaybeUninit::<*mut ffi::libusb_device_handle>::uninit();
match ffi::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(Error::NoDevice)?;
Ok(DeviceHandle::from_libusb(self.clone(), ptr))
}
err => Err(Error::from(err)),
}
}
pub fn set_log_level(&mut self, level: LogLevel) {
unsafe {
ffi::libusb_set_debug(self.as_raw(), level.as_c_int());
}
}
#[deprecated(since = "0.9.0", note = "Use HotplugBuilder")]
pub fn register_callback(
&self,
vendor_id: Option<u16>,
product_id: Option<u16>,
class: Option<u8>,
callback: Box<dyn Hotplug>,
) -> Result<Registration> {
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.clone(), callback)
}
pub fn unregister_callback(&self, _reg: Registration) {}
pub fn handle_events(&self, timeout: Option<Duration>) -> 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,
};
ffi::libusb_handle_events_timeout_completed(self.as_raw(), &tv, ptr::null_mut())
}
None => ffi::libusb_handle_events_completed(self.as_raw(), ptr::null_mut()),
}
};
if n < 0 {
Err(Error::from(n as c_int))
} else {
Ok(())
}
}
#[doc(alias = "libusb_interrupt_event_handler")]
pub fn interrupt_handle_events(&self) {
unsafe { ffi::libusb_interrupt_event_handler(self.as_raw()) }
}
pub fn next_timeout(&self) -> Result<Option<Duration>> {
let mut tv = timeval {
tv_sec: 0,
tv_usec: 0,
};
let n = unsafe { ffi::libusb_get_next_timeout(self.as_raw(), &mut tv) };
match n {
0 => Ok(None),
n if n < 0 => Err(Error::from(n as c_int)),
_ => {
let duration = Duration::new(tv.tv_sec as _, (tv.tv_usec * 1000) as _);
Ok(Some(duration))
}
}
}
}
impl Default for Context {
fn default() -> Self {
GLOBAL_CONTEXT.clone()
}
}
#[derive(Clone, Copy)]
pub enum LogLevel {
None,
Error,
Warning,
Info,
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,
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn is_global_context() {
let ctx = Context::global();
assert!(ctx.is_global());
let ctx = Context::new().unwrap();
assert!(!ctx.is_global());
}
}