diff options
-rw-r--r-- | hid/.gitignore | 2 | ||||
-rw-r--r-- | hid/Cargo.toml | 18 | ||||
-rw-r--r-- | hid/examples/list.rs | 24 | ||||
-rw-r--r-- | hid/src/device.rs | 122 | ||||
-rw-r--r-- | hid/src/devices.rs | 51 | ||||
-rw-r--r-- | hid/src/error.rs | 62 | ||||
-rw-r--r-- | hid/src/handle.rs | 226 | ||||
-rw-r--r-- | hid/src/lib.rs | 17 | ||||
-rw-r--r-- | hid/src/manager.rs | 56 |
9 files changed, 578 insertions, 0 deletions
diff --git a/hid/.gitignore b/hid/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/hid/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/hid/Cargo.toml b/hid/Cargo.toml new file mode 100644 index 0000000..13a7d49 --- /dev/null +++ b/hid/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "hid" +version = "0.3.0" + +authors = ["meh. <meh@schizofreni.co>"] +license = "WTFPL" + +description = "Safe hidapi wrapper" +repository = "https://github.com/meh/rust-hid" +keywords = ["hid"] + +[features] +static = ["hidapi-sys/static"] +build = ["static", "hidapi-sys/build"] + +[dependencies] +libc = "0.2.21" +hidapi-sys = "0.1.2" diff --git a/hid/examples/list.rs b/hid/examples/list.rs new file mode 100644 index 0000000..da24bcb --- /dev/null +++ b/hid/examples/list.rs @@ -0,0 +1,24 @@ +extern crate hid; + +fn main() { + let hid = hid::init().unwrap(); + + for device in hid.devices() { + print!("{} ", device.path().to_str().unwrap()); + print!("ID {:x}:{:x} ", device.vendor_id(), device.product_id()); + + if let Some(name) = device.manufacturer_string() { + print!("{} ", name); + } + + if let Some(name) = device.product_string() { + print!("{} ", name); + } + + if let Some(name) = device.serial_number() { + print!("{} ", name); + } + + println!(); + } +} diff --git a/hid/src/device.rs b/hid/src/device.rs new file mode 100644 index 0000000..dc718c7 --- /dev/null +++ b/hid/src/device.rs @@ -0,0 +1,122 @@ +use std::marker::PhantomData; +use std::path::Path; +use std::ffi::CStr; + +use sys::*; +use libc::{size_t, wchar_t, wcstombs}; +use handle::Handle; +use error::{self, Error}; + +/// The HID device. +pub struct Device<'a> { + ptr: *const hid_device_info, + + _marker: PhantomData<&'a ()>, +} + +impl<'a> Device<'a> { + #[doc(hidden)] + pub unsafe fn new<'b>(ptr: *const hid_device_info) -> Device<'b> { + Device { + ptr: ptr, + + _marker: PhantomData, + } + } + + /// The path representation. + pub fn path(&self) -> &Path { + unsafe { + Path::new(CStr::from_ptr((*self.ptr).path).to_str().unwrap()) + } + } + + /// The vendor ID. + pub fn vendor_id(&self) -> u16 { + unsafe { + (*self.ptr).vendor_id + } + } + + /// The product ID. + pub fn product_id(&self) -> u16 { + unsafe { + (*self.ptr).product_id + } + } + + /// The serial number. + pub fn serial_number(&self) -> Option<String> { + unsafe { + (*self.ptr).serial_number.as_ref().and_then(|p| to_string(p)) + } + } + + /// The manufacturer string. + pub fn manufacturer_string(&self) -> Option<String> { + unsafe { + (*self.ptr).manufacturer_string.as_ref().and_then(|p| to_string(p)) + } + } + + /// The product string. + pub fn product_string(&self) -> Option<String> { + unsafe { + (*self.ptr).product_string.as_ref().and_then(|p| to_string(p)) + } + } + + /// The release number. + pub fn release_number(&self) -> u16 { + unsafe { + (*self.ptr).release_number + } + } + + /// The usage page. + pub fn usage_page(&self) -> u16 { + unsafe { + (*self.ptr).usage_page + } + } + + /// The usage number. + pub fn usage(&self) -> u16 { + unsafe { + (*self.ptr).usage + } + } + + /// The interface number. + pub fn interface_number(&self) -> isize { + unsafe { + (*self.ptr).interface_number as isize + } + } + + /// Opens the device to use it. + pub fn open(&self) -> error::Result<Handle> { + unsafe { + let handle = hid_open((*self.ptr).vendor_id, (*self.ptr).product_id, (*self.ptr).serial_number); + + if handle.is_null() { + return Err(Error::NotFound); + } + + Ok(Handle::new(handle)) + } + } +} + +#[inline] +unsafe fn to_string(value: *const wchar_t) -> Option<String> { + // USB descriptors are limited to 255 bytes. + let mut buffer = [0u8; 256]; + let length = wcstombs(buffer.as_mut_ptr() as *mut _, value, buffer.len()); + + if length == size_t::max_value() { + return None; + } + + Some(String::from_utf8_lossy(&buffer[0.. length as usize]).into_owned()) +} diff --git a/hid/src/devices.rs b/hid/src/devices.rs new file mode 100644 index 0000000..002ffa9 --- /dev/null +++ b/hid/src/devices.rs @@ -0,0 +1,51 @@ +use std::marker::PhantomData; + +use sys::*; +use Device; + +/// An iterator over the available devices. +pub struct Devices<'a> { + ptr: *mut hid_device_info, + cur: *mut hid_device_info, + + _marker: PhantomData<&'a ()>, +} + +impl<'a> Devices<'a> { + #[doc(hidden)] + pub unsafe fn new(vendor: Option<u16>, product: Option<u16>) -> Self { + let list = hid_enumerate(vendor.unwrap_or(0), product.unwrap_or(0)); + + Devices { + ptr: list, + cur: list, + + _marker: PhantomData, + } + } +} + +impl<'a> Iterator for Devices<'a> { + type Item = Device<'a>; + + fn next(&mut self) -> Option<Self::Item> { + if self.cur.is_null() { + return None; + } + + unsafe { + let info = Device::new(self.cur); + self.cur = (*self.cur).next; + + Some(info) + } + } +} + +impl<'a> Drop for Devices<'a> { + fn drop(&mut self) { + unsafe { + hid_free_enumeration(self.ptr); + } + } +} diff --git a/hid/src/error.rs b/hid/src/error.rs new file mode 100644 index 0000000..367f47f --- /dev/null +++ b/hid/src/error.rs @@ -0,0 +1,62 @@ +use std::ffi::CStr; +use libc::c_int; +use sys::*; +use std::{error, fmt}; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum Error { + Initialized, + NotFound, + General, + Write, + Read, + String(String), +} + +pub type Result<T> = ::std::result::Result<T, Error>; + +impl From<c_int> for Error { + fn from(value: c_int) -> Error { + match value { + _ => Error::General + } + } +} + +impl From<*mut hid_device> for Error { + fn from(value: *mut hid_device) -> Error { + unsafe { + Error::String(CStr::from_ptr(hid_error(value) as *const _).to_str().unwrap().to_owned()) + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(error::Error::description(self)) + } +} + +impl error::Error for Error { + fn description(&self) -> &str { + match *self { + Error::Initialized => + "Already initialized.", + + Error::NotFound => + "Device not found.", + + Error::General => + "General error.", + + Error::Write => + "Write error.", + + Error::Read => + "Read error.", + + Error::String(ref err) => + err, + } + } +} diff --git a/hid/src/handle.rs b/hid/src/handle.rs new file mode 100644 index 0000000..280bdcf --- /dev/null +++ b/hid/src/handle.rs @@ -0,0 +1,226 @@ +use std::time::Duration; + +use libc::c_int; +use sys::*; +use error::{self, Error}; + +pub struct Handle { + ptr: *mut hid_device, +} + +impl Handle { + #[doc(hidden)] + pub unsafe fn new(ptr: *mut hid_device) -> Self { + Handle { + ptr: ptr, + } + } + + #[doc(hidden)] + pub unsafe fn as_ptr(&self) -> *const hid_device { + self.ptr as *const _ + } + + #[doc(hidden)] + pub unsafe fn as_mut_ptr(&mut self) -> *mut hid_device { + self.ptr + } +} + +impl Handle { + /// Set the handle in blocking or non-blocking mode. + pub fn blocking(&mut self, value: bool) -> error::Result<()> { + unsafe { + match hid_set_nonblocking(self.as_mut_ptr(), if value { 1 } else { 0 }) { + 0 => + Ok(()), + + _ => + Err(Error::General) + } + } + } + + /// The data accessor. + pub fn data(&mut self) -> Data { + unsafe { + Data::new(self) + } + } + + /// The feature accessor. + pub fn feature(&mut self) -> Feature { + unsafe { + Feature::new(self) + } + } +} + +/// The data accessor. +pub struct Data<'a> { + handle: &'a mut Handle, +} + +impl<'a> Data<'a> { + #[doc(hidden)] + pub unsafe fn new(handle: &mut Handle) -> Data { + Data { handle: handle } + } + + /// Write data to the device. + /// + /// The first byte must be the report ID. + pub fn write<T: AsRef<[u8]>>(&mut self, data: T) -> error::Result<usize> { + let data = data.as_ref(); + + unsafe { + match hid_write(self.handle.as_mut_ptr(), data.as_ptr(), data.len()) { + -1 => + Err(Error::Write), + + length => + Ok(length as usize) + } + } + } + + /// Write data to the device with the given report ID. + pub fn write_to<T: AsRef<[u8]>>(&mut self, id: u8, data: T) -> error::Result<usize> { + let data = data.as_ref(); + let mut buffer = Vec::with_capacity(data.len() + 1); + + buffer.push(id); + buffer.extend(data); + + self.write(&buffer) + } + + /// Read data from the device. + /// + /// If the device supports reports the first byte will contain the report ID. + /// + /// Returns the amount of read bytes or `None` if there was a timeout. + pub fn read<T: AsMut<[u8]>>(&mut self, mut data: T, timeout: Duration) -> error::Result<Option<usize>> { + let data = data.as_mut(); + let result = if timeout.as_secs() == 0 && timeout.subsec_nanos() == 0 { + unsafe { + hid_read(self.handle.as_mut_ptr(), data.as_mut_ptr(), data.len()) + } + } + else { + unsafe { + // Timeout is in milliseconds. + hid_read_timeout(self.handle.as_mut_ptr(), data.as_mut_ptr(), data.len(), + timeout.as_secs() as c_int * 1_000 + timeout.subsec_nanos() as c_int / 1_000_000) + } + }; + + match result { + -1 => + Err(Error::Read), + + 0 => + Ok(None), + + v => + Ok(Some(v as usize)) + } + } + + /// Read data from the device. + /// + /// Returns the report ID and the amount of read bytes or `None` if there was a timeout. + pub fn read_from<T: AsMut<[u8]>>(&mut self, mut data: T, timeout: Duration) -> error::Result<Option<(u8, usize)>> { + let mut data = data.as_mut(); + let mut buffer = Vec::with_capacity(data.len() + 1); + + match try!(self.read(&mut buffer, timeout)) { + None => { + Ok(None) + } + + Some(length) => { + data.clone_from_slice(&buffer[1..length]); + + Ok(Some((data[0], length - 1))) + } + } + } +} + +/// The feature accessor. +pub struct Feature<'a> { + handle: &'a mut Handle, +} + +impl<'a> Feature<'a> { + #[doc(hidden)] + pub unsafe fn new(handle: &mut Handle) -> Feature { + Feature { handle: handle } + } + + /// Send a feature request. + /// + /// The first byte must be the report ID. + pub fn send<T: AsRef<[u8]>>(&mut self, data: T) -> error::Result<usize> { + let data = data.as_ref(); + + unsafe { + match hid_send_feature_report(self.handle.as_mut_ptr(), data.as_ptr(), data.len()) { + -1 => + Err(Error::Write), + + length => + Ok(length as usize) + } + } + } + + /// Send a feature request to the given report ID. + pub fn send_to<T: AsRef<[u8]>>(&mut self, id: u8, data: T) -> error::Result<usize> { + let data = data.as_ref(); + let mut buffer = Vec::with_capacity(data.len() + 1); + + buffer.push(id); + buffer.extend(data); + + self.send(&buffer).map(|v| v - 1) + } + + /// Get a feature request. + /// + /// The first byte must be the report ID. + pub fn get<T: AsMut<[u8]>>(&mut self, mut data: T) -> error::Result<Option<usize>> { + let data = data.as_mut(); + + unsafe { + match hid_get_feature_report(self.handle.as_mut_ptr(), data.as_mut_ptr(), data.len()) { + -1 => + Err(Error::Read), + + 0 => + Ok(None), + + v => + Ok(Some(v as usize)) + } + } + } + + /// Get a feature request from the given report ID. + pub fn get_from<T: AsMut<[u8]>>(&mut self, id: u8, mut data: T) -> error::Result<Option<usize>> { + let data = data.as_mut(); + let mut buffer = vec![0u8; data.len() + 1]; + + buffer[0] = id; + self.get(&mut buffer) + } +} + +impl Drop for Handle { + fn drop(&mut self) { + unsafe { + hid_close(self.as_mut_ptr()); + } + } +} diff --git a/hid/src/lib.rs b/hid/src/lib.rs new file mode 100644 index 0000000..d71b5a5 --- /dev/null +++ b/hid/src/lib.rs @@ -0,0 +1,17 @@ +extern crate hidapi_sys as sys; +extern crate libc; + +mod error; +pub use error::{Error, Result}; + +mod manager; +pub use manager::{Manager, init}; + +mod devices; +pub use devices::Devices; + +mod device; +pub use device::Device; + +mod handle; +pub use handle::Handle; diff --git a/hid/src/manager.rs b/hid/src/manager.rs new file mode 100644 index 0000000..4c2eb23 --- /dev/null +++ b/hid/src/manager.rs @@ -0,0 +1,56 @@ +use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; + +use sys::*; +use error::{self, Error}; +use devices::Devices; + +static INITIALIZED: AtomicBool = ATOMIC_BOOL_INIT; + +/// The device manager. +pub struct Manager; + +unsafe impl Send for Manager { } + +/// Create the manager. +pub fn init() -> error::Result<Manager> { + if INITIALIZED.load(Ordering::Relaxed) { + return Err(Error::Initialized); + } + + let status = unsafe { hid_init() }; + + if status != 0 { + return Err(Error::from(status)); + } + + INITIALIZED.store(true, Ordering::Relaxed); + + Ok(Manager) +} + +impl Drop for Manager { + fn drop(&mut self) { + let status = unsafe { hid_exit() }; + + if status != 0 { + panic!("hid_exit() failed"); + } + + INITIALIZED.store(false, Ordering::Relaxed); + } +} + +impl Manager { + /// Find the wanted device, `vendor` or `product` are given it will + /// returns only the matches devices. + pub fn find(&self, vendor: Option<u16>, product: Option<u16>) -> Devices { + unsafe { + Devices::new(vendor, product) + } + } + + /// Return all devices. + pub fn devices(&self) -> Devices { + self.find(None, None) + } +} |