#![cfg_attr(docsrs, feature(doc_cfg))]
mod error;
mod ffi;
use cfg_if::cfg_if;
use libc::wchar_t;
use std::ffi::CStr;
use std::ffi::CString;
use std::fmt;
use std::fmt::Debug;
use std::sync::Mutex;
pub use error::HidError;
cfg_if! {
if #[cfg(all(feature = "linux-native", target_os = "linux"))] {
mod linux_native;
use linux_native::HidApiBackend;
} else if #[cfg(all(feature = "windows-native", target_os = "windows"))] {
mod windows_native;
use windows_native::HidApiBackend;
} else if #[cfg(hidapi)] {
mod hidapi;
use hidapi::HidApiBackend;
} else {
compile_error!("No backend selected");
}
}
cfg_if! {
if #[cfg(target_os = "windows")] {
#[cfg_attr(docsrs, doc(cfg(target_os = "windows")))]
mod windows;
use windows::GUID;
trait HidDeviceBackendWindows {
fn get_container_id(&self) -> HidResult<GUID>;
}
trait HidDeviceBackend: HidDeviceBackendBase + HidDeviceBackendWindows + Send {}
impl<T> HidDeviceBackend for T where T: HidDeviceBackendBase + HidDeviceBackendWindows + Send {}
} else if #[cfg(target_os = "macos")] {
#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
mod macos;
trait HidDeviceBackendMacos {
fn get_location_id(&self) -> HidResult<u32>;
fn is_open_exclusive(&self) -> HidResult<bool>;
}
trait HidDeviceBackend: HidDeviceBackendBase + HidDeviceBackendMacos + Send {}
impl<T> HidDeviceBackend for T where T: HidDeviceBackendBase + HidDeviceBackendMacos + Send {}
} else {
trait HidDeviceBackend: HidDeviceBackendBase + Send {}
impl<T> HidDeviceBackend for T where T: HidDeviceBackendBase + Send {}
}
}
pub type HidResult<T> = Result<T, HidError>;
pub const MAX_REPORT_DESCRIPTOR_SIZE: usize = 4096;
struct ContextState {
device_discovery: bool,
init_state: InitState,
}
enum InitState {
NotInit,
Init,
}
static CONTEXT_STATE: Mutex<ContextState> = Mutex::new(ContextState {
device_discovery: true,
init_state: InitState::NotInit,
});
pub struct HidApi {
device_list: Vec<DeviceInfo>,
}
impl HidApi {
pub fn new() -> HidResult<Self> {
let mut state = CONTEXT_STATE.lock().unwrap();
if let InitState::NotInit = state.init_state {
#[cfg(all(libusb, not(target_os = "freebsd")))]
if !state.device_discovery {
unsafe { ffi::libusb_set_option(std::ptr::null_mut(), 2) }
}
#[cfg(hidapi)]
if unsafe { ffi::hid_init() } == -1 {
return Err(HidError::InitializationError);
}
#[cfg(all(target_os = "macos", feature = "macos-shared-device"))]
unsafe {
ffi::macos::hid_darwin_set_open_exclusive(0)
}
state.init_state = InitState::Init;
}
let mut api = HidApi {
device_list: Vec::with_capacity(8),
};
api.add_devices(0, 0)?;
Ok(api)
}
pub fn disable_device_discovery() {
let mut state = CONTEXT_STATE.lock().unwrap();
if let InitState::NotInit = state.init_state {
state.device_discovery = false; } else if state.device_discovery {
core::mem::drop(state); panic!("Cannot disable device discovery after HidApi has been initialized");
}
}
#[deprecated(
note = "Please use only `HidApi::new()` in library code. Application code should disable device discovery explicitly."
)]
pub fn new_without_enumerate() -> HidResult<Self> {
Self::disable_device_discovery();
Self::new()
}
pub fn refresh_devices(&mut self) -> HidResult<()> {
self.reset_devices()?;
self.add_devices(0, 0)?;
Ok(())
}
pub fn reset_devices(&mut self) -> HidResult<()> {
self.device_list.clear();
Ok(())
}
pub fn add_devices(&mut self, vid: u16, pid: u16) -> HidResult<()> {
self.device_list
.append(&mut HidApiBackend::get_hid_device_info_vector(vid, pid)?);
Ok(())
}
pub fn device_list(&self) -> impl Iterator<Item = &DeviceInfo> {
self.device_list.iter()
}
pub fn open(&self, vid: u16, pid: u16) -> HidResult<HidDevice> {
let dev = HidApiBackend::open(vid, pid)?;
Ok(HidDevice::from_backend(Box::new(dev)))
}
pub fn open_serial(&self, vid: u16, pid: u16, sn: &str) -> HidResult<HidDevice> {
let dev = HidApiBackend::open_serial(vid, pid, sn)?;
Ok(HidDevice::from_backend(Box::new(dev)))
}
pub fn open_path(&self, device_path: &CStr) -> HidResult<HidDevice> {
let dev = HidApiBackend::open_path(device_path)?;
Ok(HidDevice::from_backend(Box::new(dev)))
}
#[cfg(libusb)]
pub fn wrap_sys_device(&self, sys_dev: isize, interface_num: i32) -> HidResult<HidDevice> {
let device = unsafe { ffi::hid_libusb_wrap_sys_device(sys_dev, interface_num) };
if device.is_null() {
match HidApiBackend::check_error() {
Ok(err) => Err(err),
Err(e) => Err(e),
}
} else {
let dev = hidapi::HidDevice::from_raw(device);
Ok(HidDevice::from_backend(Box::new(dev)))
}
}
#[cfg(hidapi)]
#[deprecated(since = "2.2.3", note = "use the return values from the other methods")]
pub fn check_error(&self) -> HidResult<HidError> {
HidApiBackend::check_error()
}
}
#[allow(dead_code)]
#[derive(Clone, PartialEq)]
enum WcharString {
String(String),
#[cfg_attr(all(feature = "linux-native", target_os = "linux"), allow(dead_code))]
Raw(Vec<wchar_t>),
None,
}
impl From<WcharString> for Option<String> {
fn from(val: WcharString) -> Self {
match val {
WcharString::String(s) => Some(s),
_ => None,
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub enum BusType {
Unknown = 0x00,
Usb = 0x01,
Bluetooth = 0x02,
I2c = 0x03,
Spi = 0x04,
}
#[derive(Clone)]
pub struct DeviceInfo {
path: CString,
vendor_id: u16,
product_id: u16,
serial_number: WcharString,
release_number: u16,
manufacturer_string: WcharString,
product_string: WcharString,
#[allow(dead_code)]
usage_page: u16,
#[allow(dead_code)]
usage: u16,
interface_number: i32,
bus_type: BusType,
}
impl DeviceInfo {
pub fn path(&self) -> &CStr {
&self.path
}
pub fn vendor_id(&self) -> u16 {
self.vendor_id
}
pub fn product_id(&self) -> u16 {
self.product_id
}
pub fn serial_number(&self) -> Option<&str> {
match self.serial_number {
WcharString::String(ref s) => Some(s),
_ => None,
}
}
pub fn serial_number_raw(&self) -> Option<&[wchar_t]> {
match self.serial_number {
WcharString::Raw(ref s) => Some(s),
_ => None,
}
}
pub fn release_number(&self) -> u16 {
self.release_number
}
pub fn manufacturer_string(&self) -> Option<&str> {
match self.manufacturer_string {
WcharString::String(ref s) => Some(s),
_ => None,
}
}
pub fn manufacturer_string_raw(&self) -> Option<&[wchar_t]> {
match self.manufacturer_string {
WcharString::Raw(ref s) => Some(s),
_ => None,
}
}
pub fn product_string(&self) -> Option<&str> {
match self.product_string {
WcharString::String(ref s) => Some(s),
_ => None,
}
}
pub fn product_string_raw(&self) -> Option<&[wchar_t]> {
match self.product_string {
WcharString::Raw(ref s) => Some(s),
_ => None,
}
}
#[cfg(not(all(libusb, target_os = "linux")))]
pub fn usage_page(&self) -> u16 {
self.usage_page
}
#[cfg(not(all(libusb, target_os = "linux")))]
pub fn usage(&self) -> u16 {
self.usage
}
pub fn interface_number(&self) -> i32 {
self.interface_number
}
pub fn bus_type(&self) -> BusType {
self.bus_type
}
pub fn open_device(&self, hidapi: &HidApi) -> HidResult<HidDevice> {
if !self.path.as_bytes().is_empty() {
hidapi.open_path(self.path.as_c_str())
} else if let Some(sn) = self.serial_number() {
hidapi.open_serial(self.vendor_id, self.product_id, sn)
} else {
Err(HidError::OpenHidDeviceWithDeviceInfoError {
device_info: Box::new(self.clone()),
})
}
}
}
impl fmt::Debug for DeviceInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HidDeviceInfo")
.field("vendor_id", &self.vendor_id)
.field("product_id", &self.product_id)
.finish()
}
}
trait HidDeviceBackendBase {
#[cfg(hidapi)]
fn check_error(&self) -> HidResult<HidError>;
fn write(&self, data: &[u8]) -> HidResult<usize>;
fn read(&self, buf: &mut [u8]) -> HidResult<usize>;
fn read_timeout(&self, buf: &mut [u8], timeout: i32) -> HidResult<usize>;
fn send_feature_report(&self, data: &[u8]) -> HidResult<()>;
fn get_feature_report(&self, buf: &mut [u8]) -> HidResult<usize>;
fn send_output_report(&self, data: &[u8]) -> HidResult<()>;
#[cfg(any(hidapi, target_os = "linux"))]
fn get_input_report(&self, data: &mut [u8]) -> HidResult<usize>;
fn set_blocking_mode(&self, blocking: bool) -> HidResult<()>;
fn get_device_info(&self) -> HidResult<DeviceInfo>;
fn get_manufacturer_string(&self) -> HidResult<Option<String>>;
fn get_product_string(&self) -> HidResult<Option<String>>;
fn get_serial_number_string(&self) -> HidResult<Option<String>>;
fn get_report_descriptor(&self, buf: &mut [u8]) -> HidResult<usize>;
fn get_indexed_string(&self, _index: i32) -> HidResult<Option<String>> {
Err(HidError::HidApiError {
message: "get_indexed_string: not supported".to_string(),
})
}
}
pub struct HidDevice {
inner: Box<dyn HidDeviceBackend>,
}
impl Debug for HidDevice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HidDevice").finish_non_exhaustive()
}
}
impl HidDevice {
fn from_backend(inner: Box<dyn HidDeviceBackend>) -> Self {
Self { inner }
}
}
impl HidDevice {
#[cfg(hidapi)]
#[deprecated(since = "2.2.3", note = "use the return values from the other methods")]
pub fn check_error(&self) -> HidResult<HidError> {
self.inner.check_error()
}
pub fn write(&self, data: &[u8]) -> HidResult<usize> {
self.inner.write(data)
}
pub fn read(&self, buf: &mut [u8]) -> HidResult<usize> {
self.inner.read(buf)
}
pub fn read_timeout(&self, buf: &mut [u8], timeout: i32) -> HidResult<usize> {
self.inner.read_timeout(buf, timeout)
}
pub fn send_feature_report(&self, data: &[u8]) -> HidResult<()> {
self.inner.send_feature_report(data)
}
pub fn get_feature_report(&self, buf: &mut [u8]) -> HidResult<usize> {
self.inner.get_feature_report(buf)
}
pub fn send_output_report(&self, data: &[u8]) -> HidResult<()> {
self.inner.send_output_report(data)
}
#[cfg(any(hidapi, target_os = "linux"))]
pub fn get_input_report(&self, data: &mut [u8]) -> HidResult<usize> {
self.inner.get_input_report(data)
}
pub fn set_blocking_mode(&self, blocking: bool) -> HidResult<()> {
self.inner.set_blocking_mode(blocking)
}
pub fn get_manufacturer_string(&self) -> HidResult<Option<String>> {
self.inner.get_manufacturer_string()
}
pub fn get_product_string(&self) -> HidResult<Option<String>> {
self.inner.get_product_string()
}
pub fn get_serial_number_string(&self) -> HidResult<Option<String>> {
self.inner.get_serial_number_string()
}
pub fn get_indexed_string(&self, index: i32) -> HidResult<Option<String>> {
self.inner.get_indexed_string(index)
}
pub fn get_report_descriptor(&self, buf: &mut [u8]) -> HidResult<usize> {
self.inner.get_report_descriptor(buf)
}
pub fn get_device_info(&self) -> HidResult<DeviceInfo> {
self.inner.get_device_info()
}
}