aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hid/.gitignore2
-rw-r--r--hid/Cargo.toml18
-rw-r--r--hid/examples/list.rs24
-rw-r--r--hid/src/device.rs122
-rw-r--r--hid/src/devices.rs51
-rw-r--r--hid/src/error.rs62
-rw-r--r--hid/src/handle.rs226
-rw-r--r--hid/src/lib.rs17
-rw-r--r--hid/src/manager.rs56
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)
+ }
+}