diff options
Diffstat (limited to 'hid/src')
| -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 | 
6 files changed, 534 insertions, 0 deletions
| 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) +	} +} | 
