diff --git a/src/context.rs b/src/context.rs index 07efe87..2add6b3 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,6 +1,9 @@ -use libc::{c_int, timeval}; +use libc::{c_char, c_int, c_void, timeval}; -use std::{cmp::Ordering, mem, ptr, sync::Arc, sync::Once, time::Duration}; +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; @@ -45,6 +48,43 @@ impl Drop for ContextInner { unsafe impl Sync for Context {} unsafe impl Send for Context {} +type LogCallback = Box; + +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> = 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; @@ -104,6 +144,17 @@ pub trait UsbContext: Clone + Sized + Send + Sync { } } + 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. @@ -292,4 +343,31 @@ impl LogLevel { 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, + } + } } diff --git a/src/lib.rs b/src/lib.rs index 10165d2..4c16277 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ pub use libusb1_sys::constants; pub use crate::options::disable_device_discovery; pub use crate::{ config_descriptor::{ConfigDescriptor, Interfaces}, - context::{Context, GlobalContext, LogLevel, UsbContext}, + context::{Context, GlobalContext, LogCallbackMode, LogLevel, UsbContext}, device::Device, device_descriptor::DeviceDescriptor, device_handle::DeviceHandle,