extern crate libc;
#[cfg(all(feature = "linux-native", target_os = "linux"))]
extern crate nix;
#[cfg(target_os = "windows")]
extern crate winapi;
#[cfg(target_os = "windows")]
use winapi::shared::guiddef::GUID;
mod error;
mod ffi;
#[cfg(hidapi)]
mod hidapi;
#[cfg(all(feature = "linux-native", target_os = "linux"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "linux-native", target_os = "linux"))))]
mod linux_native;
#[cfg(target_os = "macos")]
#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
mod macos;
#[cfg(target_os = "windows")]
#[cfg_attr(docsrs, doc(cfg(target_os = "windows")))]
mod windows;
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(hidapi)]
use crate::hidapi::HidApiBackend;
#[cfg(all(feature = "linux-native", target_os = "linux"))]
use linux_native::HidApiBackend;
pub type HidResult<T> = Result<T, HidError>;
pub const MAX_REPORT_DESCRIPTOR_SIZE: usize = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum InitState {
NotInit,
Init { enumerate: bool },
}
static INIT_STATE: Mutex<InitState> = Mutex::new(InitState::NotInit);
fn lazy_init(do_enumerate: bool) -> HidResult<()> {
let mut init_state = INIT_STATE.lock().unwrap();
match *init_state {
InitState::NotInit => {
#[cfg(all(libusb, not(target_os = "freebsd")))]
if !do_enumerate {
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)
}
*init_state = InitState::Init {
enumerate: do_enumerate,
}
}
InitState::Init { enumerate } => {
if enumerate != do_enumerate {
panic!("Trying to initialize hidapi with enumeration={}, but it is already initialized with enumeration={}.", do_enumerate, enumerate)
}
}
}
Ok(())
}
pub struct HidApi {
device_list: Vec<DeviceInfo>,
}
impl HidApi {
pub fn new() -> HidResult<Self> {
lazy_init(true)?;
let device_list = HidApiBackend::get_hid_device_info_vector()?;
Ok(HidApi { device_list })
}
pub fn new_without_enumerate() -> HidResult<Self> {
lazy_init(false)?;
Ok(HidApi {
device_list: Vec::new(),
})
}
pub fn refresh_devices(&mut self) -> HidResult<()> {
let device_list = HidApiBackend::get_hid_device_info_vector()?;
self.device_list = device_list;
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 self.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()
}
}
#[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 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(),
})
}
}
#[cfg(target_os = "macos")]
trait HidDeviceBackendMacos {
fn get_location_id(&self) -> HidResult<u32>;
fn is_open_exclusive(&self) -> HidResult<bool>;
}
#[cfg(target_os = "windows")]
trait HidDeviceBackendWindows {
fn get_container_id(&self) -> HidResult<GUID>;
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
trait HidDeviceBackend: HidDeviceBackendBase + Send {}
#[cfg(target_os = "macos")]
trait HidDeviceBackend: HidDeviceBackendBase + HidDeviceBackendMacos + Send {}
#[cfg(target_os = "windows")]
trait HidDeviceBackend: HidDeviceBackendBase + HidDeviceBackendWindows + Send {}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
impl<T> HidDeviceBackend for T where T: HidDeviceBackendBase + Send {}
#[cfg(target_os = "macos")]
impl<T> HidDeviceBackend for T where T: HidDeviceBackendBase + HidDeviceBackendMacos + Send {}
#[cfg(target_os = "windows")]
impl<T> HidDeviceBackend for T where T: HidDeviceBackendBase + HidDeviceBackendWindows + Send {}
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 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()
}
}