hidapi_rusb/
lib.rs

1// **************************************************************************
2// Copyright (c) 2015 Osspial All Rights Reserved.
3//
4// This file is part of hidapi-rs, based on hidapi_rust by Roland Ruckerbauer.
5// *************************************************************************
6
7//! This crate provides a rust abstraction over the features of the C library
8//! hidapi by [signal11](https://github.com/libusb/hidapi).
9//!
10//! # Usage
11//!
12//! This crate is [on crates.io](https://crates.io/crates/hidapi) and can be
13//! used by adding `hidapi` to the dependencies in your project's `Cargo.toml`.
14//!
15//! # Example
16//!
17//! ```rust,no_run
18//! extern crate hidapi_rusb;
19//!
20//! use hidapi_rusb::HidApi;
21//!
22//! fn main() {
23//!     println!("Printing all available hid devices:");
24//!
25//!     match HidApi::new() {
26//!         Ok(api) => {
27//!             for device in api.device_list() {
28//!                 println!("{:04x}:{:04x}", device.vendor_id(), device.product_id());
29//!             }
30//!         },
31//!         Err(e) => {
32//!             eprintln!("Error: {}", e);
33//!         },
34//!     }
35//! }
36//! ```
37
38// Allow use of deprecated items, we defined ourselfes...
39#![allow(deprecated)]
40
41#[cfg(all(feature = "linux-static-rusb", not(target_os = "macos")))]
42extern crate rusb;
43
44extern crate libc;
45
46mod error;
47mod ffi;
48
49use libc::{c_int, size_t, wchar_t};
50use std::ffi::CStr;
51use std::ffi::CString;
52use std::fmt;
53use std::mem::ManuallyDrop;
54use std::sync::atomic::{AtomicBool, Ordering};
55use std::sync::Arc;
56
57pub use error::HidError;
58
59pub type HidResult<T> = Result<T, HidError>;
60
61const STRING_BUF_LEN: usize = 128;
62
63/// Hidapi context and device member, which ensures deinitialization
64/// of the C library happens, when, and only when all devices and the api instance is dropped.
65struct HidApiLock;
66
67impl HidApiLock {
68    fn acquire() -> HidResult<HidApiLock> {
69        const EXPECTED_CURRENT: bool = false;
70
71        if EXPECTED_CURRENT
72            == HID_API_LOCK.compare_and_swap(EXPECTED_CURRENT, true, Ordering::SeqCst)
73        {
74            // Initialize the HID and prevent other HIDs from being created
75            unsafe {
76                // This option must be set for Android Termux
77                #[cfg(target_os = "android")]
78                rusb::ffi::libusb_set_option(
79                    std::ptr::null_mut(),
80                    rusb::ffi::constants::LIBUSB_OPTION_WEAK_AUTHORITY,
81                );
82
83                if ffi::hid_init() == -1 {
84                    HID_API_LOCK.store(false, Ordering::SeqCst);
85                    return Err(HidError::InitializationError);
86                }
87                Ok(HidApiLock)
88            }
89        } else {
90            Err(HidError::InitializationError)
91        }
92    }
93}
94
95impl Drop for HidApiLock {
96    fn drop(&mut self) {
97        unsafe {
98            ffi::hid_exit();
99        }
100        HID_API_LOCK.store(false, Ordering::SeqCst);
101    }
102}
103
104/// Object for handling hidapi context and implementing RAII for it.
105/// Only one instance can exist at a time.
106pub struct HidApi {
107    devices: Vec<HidDeviceInfo>, /* Deprecated */
108    device_list: Vec<DeviceInfo>,
109    _lock: Arc<HidApiLock>,
110}
111
112static HID_API_LOCK: AtomicBool = AtomicBool::new(false);
113
114impl HidApi {
115    /// Initializes the hidapi.
116    ///
117    /// Will also initialize the currently available device list.
118    pub fn new() -> HidResult<Self> {
119        let lock = HidApiLock::acquire()?;
120
121        let device_list = unsafe { HidApi::get_hid_device_info_vector()? };
122
123        Ok(HidApi {
124            device_list: device_list.clone(),
125            devices: device_list.into_iter().map(|d| d.into()).collect(),
126            _lock: Arc::new(lock),
127        })
128    }
129
130    /// Refresh devices list and information about them (to access them use
131    /// `device_list()` method)
132    pub fn refresh_devices(&mut self) -> HidResult<()> {
133        let device_list = unsafe { HidApi::get_hid_device_info_vector()? };
134        self.device_list = device_list.clone();
135        self.devices = device_list.into_iter().map(|d| d.into()).collect();
136        Ok(())
137    }
138
139    unsafe fn get_hid_device_info_vector() -> HidResult<Vec<DeviceInfo>> {
140        let mut device_vector = Vec::with_capacity(8);
141
142        let enumeration = ffi::hid_enumerate(0, 0);
143        {
144            let mut current_device = enumeration;
145
146            while !current_device.is_null() {
147                device_vector.push(conv_hid_device_info(current_device)?);
148                current_device = (*current_device).next;
149            }
150        }
151
152        if !enumeration.is_null() {
153            ffi::hid_free_enumeration(enumeration);
154        }
155
156        Ok(device_vector)
157    }
158
159    /// Returns vec of objects containing information about connected devices
160    ///
161    /// Deprecated. Use `HidApi::device_list()` instead.
162    #[deprecated]
163    pub fn devices(&self) -> &Vec<HidDeviceInfo> {
164        &self.devices
165    }
166
167    /// Returns iterator containing information about attached HID devices.
168    pub fn device_list(&self) -> impl Iterator<Item = &DeviceInfo> {
169        self.device_list.iter()
170    }
171
172    /// Open a HID device using a Vendor ID (VID) and Product ID (PID).
173    ///
174    /// When multiple devices with the same vid and pid are available, then the
175    /// first one found in the internal device list will be used. There are however
176    /// no guarantees, which device this will be.
177    pub fn open(&self, vid: u16, pid: u16) -> HidResult<HidDevice> {
178        let device = unsafe { ffi::hid_open(vid, pid, std::ptr::null()) };
179
180        if device.is_null() {
181            match self.check_error() {
182                Ok(err) => Err(err),
183                Err(e) => Err(e),
184            }
185        } else {
186            Ok(HidDevice {
187                _hid_device: device,
188                _lock: ManuallyDrop::new(self._lock.clone()),
189            })
190        }
191    }
192
193    /// Open a HID device using a Vendor ID (VID), Product ID (PID) and
194    /// a serial number.
195    pub fn open_serial(&self, vid: u16, pid: u16, sn: &str) -> HidResult<HidDevice> {
196        let mut chars = sn.chars().map(|c| c as wchar_t).collect::<Vec<_>>();
197        chars.push(0 as wchar_t);
198        let device = unsafe { ffi::hid_open(vid, pid, chars.as_ptr()) };
199        if device.is_null() {
200            match self.check_error() {
201                Ok(err) => Err(err),
202                Err(e) => Err(e),
203            }
204        } else {
205            Ok(HidDevice {
206                _hid_device: device,
207                _lock: ManuallyDrop::new(self._lock.clone()),
208            })
209        }
210    }
211
212    /// The path name be determined by inspecting the device list available with [HidApi::devices()](struct.HidApi.html#method.devices)
213    ///
214    /// Alternatively a platform-specific path name can be used (eg: /dev/hidraw0 on Linux).
215    pub fn open_path(&self, device_path: &CStr) -> HidResult<HidDevice> {
216        let device = unsafe { ffi::hid_open_path(device_path.as_ptr()) };
217
218        if device.is_null() {
219            match self.check_error() {
220                Ok(err) => Err(err),
221                Err(e) => Err(e),
222            }
223        } else {
224            Ok(HidDevice {
225                _hid_device: device,
226                _lock: ManuallyDrop::new(self._lock.clone()),
227            })
228        }
229    }
230
231    /// Open a HID device using `libusb_wrap_sys_device`. Useful for Android.
232    ///
233    /// ### Arguments
234    ///
235    /// * `sys_dev`: Platform-specific file descriptor that can be recognised by libusb.
236    /// * `interface_num`: USB interface number of the device to be used as HID interface. Pass -1
237    /// to select first HID interface of the device.
238    #[cfg(all(unix, not(target_os = "macos")))]
239    pub fn wrap_sys_device(&self, sys_dev: i32, interface_num: i32) -> HidResult<HidDevice> {
240        let device = unsafe { ffi::hid_libusb_wrap_sys_device(sys_dev as _, interface_num) };
241
242        if device.is_null() {
243            match self.check_error() {
244                Ok(err) => Err(err),
245                Err(e) => Err(e),
246            }
247        } else {
248            Ok(HidDevice {
249                _hid_device: device,
250                _lock: ManuallyDrop::new(self._lock.clone()),
251            })
252        }
253    }
254
255    /// Get the last non-device specific error, which happened in the underlying hidapi C library.
256    /// To get the last device specific error, use [`HidDevice::check_error`].
257    ///
258    /// The `Ok()` variant of the result will contain a [HidError::HidApiError](enum.HidError.html).
259    ///
260    /// When `Err()` is returned, then acquiring the error string from the hidapi C
261    /// library failed. The contained [HidError](enum.HidError.html) is the cause, why no error could
262    /// be fetched.
263    pub fn check_error(&self) -> HidResult<HidError> {
264        Ok(HidError::HidApiError {
265            message: unsafe {
266                match wchar_to_string(ffi::hid_error(std::ptr::null_mut())) {
267                    WcharString::String(s) => s,
268                    _ => return Err(HidError::HidApiErrorEmpty),
269                }
270            },
271        })
272    }
273}
274
275/// Converts a pointer to a `*const wchar_t` to a WcharString.
276unsafe fn wchar_to_string(wstr: *const wchar_t) -> WcharString {
277    if wstr.is_null() {
278        return WcharString::None;
279    }
280
281    let mut char_vector: Vec<char> = Vec::with_capacity(8);
282    let mut raw_vector: Vec<wchar_t> = Vec::with_capacity(8);
283    let mut index: isize = 0;
284    let mut invalid_char = false;
285
286    let o = |i| *wstr.offset(i);
287
288    while o(index) != 0 {
289        use std::char;
290
291        raw_vector.push(*wstr.offset(index));
292
293        if !invalid_char {
294            if let Some(c) = char::from_u32(o(index) as u32) {
295                char_vector.push(c);
296            } else {
297                invalid_char = true;
298            }
299        }
300
301        index += 1;
302    }
303
304    if !invalid_char {
305        WcharString::String(char_vector.into_iter().collect())
306    } else {
307        WcharString::Raw(raw_vector)
308    }
309}
310
311/// Convert the CFFI `HidDeviceInfo` struct to a native `HidDeviceInfo` struct
312unsafe fn conv_hid_device_info(src: *mut ffi::HidDeviceInfo) -> HidResult<DeviceInfo> {
313    Ok(DeviceInfo {
314        path: CStr::from_ptr((*src).path).to_owned(),
315        vendor_id: (*src).vendor_id,
316        product_id: (*src).product_id,
317        serial_number: wchar_to_string((*src).serial_number),
318        release_number: (*src).release_number,
319        manufacturer_string: wchar_to_string((*src).manufacturer_string),
320        product_string: wchar_to_string((*src).product_string),
321        usage_page: (*src).usage_page,
322        usage: (*src).usage,
323        interface_number: (*src).interface_number,
324    })
325}
326
327#[derive(Clone)]
328enum WcharString {
329    String(String),
330    Raw(Vec<wchar_t>),
331    None,
332}
333
334impl Into<Option<String>> for WcharString {
335    fn into(self) -> Option<String> {
336        match self {
337            WcharString::String(s) => Some(s),
338            _ => None,
339        }
340    }
341}
342
343/// Storage for device related information
344///
345/// Deprecated. Use `HidApi::device_list()` instead.
346#[derive(Debug, Clone)]
347#[deprecated]
348pub struct HidDeviceInfo {
349    pub path: CString,
350    pub vendor_id: u16,
351    pub product_id: u16,
352    pub serial_number: Option<String>,
353    pub release_number: u16,
354    pub manufacturer_string: Option<String>,
355    pub product_string: Option<String>,
356    pub usage_page: u16,
357    pub usage: u16,
358    pub interface_number: i32,
359}
360
361/// Device information. Use accessors to extract information about Hid devices.
362///
363/// Note: Methods like `serial_number()` may return None, if the conversion to a
364/// String failed internally. You can however access the raw hid representation of the
365/// string by calling `serial_number_raw()`
366#[derive(Clone)]
367pub struct DeviceInfo {
368    path: CString,
369    vendor_id: u16,
370    product_id: u16,
371    serial_number: WcharString,
372    release_number: u16,
373    manufacturer_string: WcharString,
374    product_string: WcharString,
375    usage_page: u16,
376    usage: u16,
377    interface_number: i32,
378}
379
380impl DeviceInfo {
381    pub fn path(&self) -> &CStr {
382        &self.path
383    }
384    pub fn vendor_id(&self) -> u16 {
385        self.vendor_id
386    }
387    pub fn product_id(&self) -> u16 {
388        self.product_id
389    }
390
391    /// Try to call `serial_number_raw()`, if None is returned.
392    pub fn serial_number(&self) -> Option<&str> {
393        match self.serial_number {
394            WcharString::String(ref s) => Some(s),
395            _ => None,
396        }
397    }
398    pub fn serial_number_raw(&self) -> Option<&[wchar_t]> {
399        match self.serial_number {
400            WcharString::Raw(ref s) => Some(s),
401            _ => None,
402        }
403    }
404
405    pub fn release_number(&self) -> u16 {
406        self.release_number
407    }
408
409    /// Try to call `manufacturer_string_raw()`, if None is returned.
410    pub fn manufacturer_string(&self) -> Option<&str> {
411        match self.manufacturer_string {
412            WcharString::String(ref s) => Some(s),
413            _ => None,
414        }
415    }
416    pub fn manufacturer_string_raw(&self) -> Option<&[wchar_t]> {
417        match self.manufacturer_string {
418            WcharString::Raw(ref s) => Some(s),
419            _ => None,
420        }
421    }
422
423    /// Try to call `product_string_raw()`, if None is returned.
424    pub fn product_string(&self) -> Option<&str> {
425        match self.product_string {
426            WcharString::String(ref s) => Some(s),
427            _ => None,
428        }
429    }
430    pub fn product_string_raw(&self) -> Option<&[wchar_t]> {
431        match self.product_string {
432            WcharString::Raw(ref s) => Some(s),
433            _ => None,
434        }
435    }
436
437    pub fn usage_page(&self) -> u16 {
438        self.usage_page
439    }
440    pub fn usage(&self) -> u16 {
441        self.usage
442    }
443    pub fn interface_number(&self) -> i32 {
444        self.interface_number
445    }
446
447    /// Use the information contained in `DeviceInfo` to open
448    /// and return a handle to a [HidDevice](struct.HidDevice.html).
449    ///
450    /// By default the device path is used to open the device.
451    /// When no path is available, then vid, pid and serial number are used.
452    /// If both path and serial number are not available, then this function will
453    /// fail with [HidError::OpenHidDeviceWithDeviceInfoError](enum.HidError.html#variant.OpenHidDeviceWithDeviceInfoError).
454    ///
455    /// Note, that opening a device could still be done using [HidApi::open()](struct.HidApi.html#method.open) directly.
456    pub fn open_device(&self, hidapi: &HidApi) -> HidResult<HidDevice> {
457        if self.path.as_bytes().len() != 0 {
458            hidapi.open_path(self.path.as_c_str())
459        } else if let Some(ref sn) = self.serial_number() {
460            hidapi.open_serial(self.vendor_id, self.product_id, sn)
461        } else {
462            Err(HidError::OpenHidDeviceWithDeviceInfoError {
463                device_info: Box::new(self.clone().into()),
464            })
465        }
466    }
467}
468
469impl fmt::Debug for DeviceInfo {
470    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
471        f.debug_struct("HidDeviceInfo")
472            .field("vendor_id", &self.vendor_id)
473            .field("product_id", &self.product_id)
474            .finish()
475    }
476}
477
478impl Into<HidDeviceInfo> for DeviceInfo {
479    fn into(self) -> HidDeviceInfo {
480        HidDeviceInfo {
481            path: self.path,
482            vendor_id: self.vendor_id,
483            product_id: self.product_id,
484            serial_number: match self.serial_number {
485                WcharString::String(s) => Some(s),
486                _ => None,
487            },
488            release_number: self.release_number,
489            manufacturer_string: match self.manufacturer_string {
490                WcharString::String(s) => Some(s),
491                _ => None,
492            },
493            product_string: match self.product_string {
494                WcharString::String(s) => Some(s),
495                _ => None,
496            },
497            usage_page: self.usage_page,
498            usage: self.usage,
499            interface_number: self.interface_number,
500        }
501    }
502}
503
504impl HidDeviceInfo {
505    /// Use the information contained in `HidDeviceInfo` to open
506    /// and return a handle to a [HidDevice](struct.HidDevice.html).
507    ///
508    /// By default the device path is used to open the device.
509    /// When no path is available, then vid, pid and serial number are used.
510    /// If both path and serial number are not available, then this function will
511    /// fail with [HidError::OpenHidDeviceWithDeviceInfoError](enum.HidError.html#variant.OpenHidDeviceWithDeviceInfoError).
512    ///
513    /// Note, that opening a device could still be done using [HidApi::open()](struct.HidApi.html#method.open) directly.
514    pub fn open_device(&self, hidapi: &HidApi) -> HidResult<HidDevice> {
515        if self.path.as_bytes().len() != 0 {
516            hidapi.open_path(self.path.as_c_str())
517        } else if let Some(ref sn) = self.serial_number {
518            hidapi.open_serial(self.vendor_id, self.product_id, sn)
519        } else {
520            Err(HidError::OpenHidDeviceWithDeviceInfoError {
521                device_info: Box::new(self.clone()),
522            })
523        }
524    }
525}
526
527/// Object for accessing HID device
528pub struct HidDevice {
529    _hid_device: *mut ffi::HidDevice,
530    /// Prevents this from outliving the api instance that created it
531    _lock: ManuallyDrop<Arc<HidApiLock>>,
532}
533
534unsafe impl Send for HidDevice {}
535
536impl Drop for HidDevice {
537    fn drop(&mut self) {
538        unsafe {
539            ffi::hid_close(self._hid_device);
540            ManuallyDrop::drop(&mut self._lock);
541        };
542    }
543}
544
545impl HidDevice {
546    /// Check size returned by other methods, if it's equal to -1 check for
547    /// error and return Error, otherwise return size as unsigned number
548    fn check_size(&self, res: i32) -> HidResult<usize> {
549        if res == -1 {
550            match self.check_error() {
551                Ok(err) => Err(err),
552                Err(e) => Err(e),
553            }
554        } else {
555            Ok(res as usize)
556        }
557    }
558
559    /// Get the last error, which happened in the underlying hidapi C library.
560    ///
561    /// The `Ok()` variant of the result will contain a [HidError::HidApiError](enum.HidError.html).
562    ///
563    /// When `Err()` is returned, then acquiring the error string from the hidapi C
564    /// library failed. The contained [HidError](enum.HidError.html) is the cause, why no error could
565    /// be fetched.
566    pub fn check_error(&self) -> HidResult<HidError> {
567        Ok(HidError::HidApiError {
568            message: unsafe {
569                match wchar_to_string(ffi::hid_error(self._hid_device)) {
570                    WcharString::String(s) => s,
571                    _ => return Err(HidError::HidApiErrorEmpty),
572                }
573            },
574        })
575    }
576
577    /// The first byte of `data` must contain the Report ID. For
578    /// devices which only support a single report, this must be set
579    /// to 0x0. The remaining bytes contain the report data. Since
580    /// the Report ID is mandatory, calls to `write()` will always
581    /// contain one more byte than the report contains. For example,
582    /// if a hid report is 16 bytes long, 17 bytes must be passed to
583    /// `write()`, the Report ID (or 0x0, for devices with a
584    /// single report), followed by the report data (16 bytes). In
585    /// this example, the length passed in would be 17.
586    /// `write()` will send the data on the first OUT endpoint, if
587    /// one exists. If it does not, it will send the data through
588    /// the Control Endpoint (Endpoint 0).
589    pub fn write(&self, data: &[u8]) -> HidResult<usize> {
590        if data.len() == 0 {
591            return Err(HidError::InvalidZeroSizeData);
592        }
593        let res = unsafe { ffi::hid_write(self._hid_device, data.as_ptr(), data.len() as size_t) };
594        self.check_size(res)
595    }
596
597    /// Input reports are returned to the host through the 'INTERRUPT IN'
598    /// endpoint. The first byte will contain the Report number if the device
599    /// uses numbered reports.
600    pub fn read(&self, buf: &mut [u8]) -> HidResult<usize> {
601        let res = unsafe { ffi::hid_read(self._hid_device, buf.as_mut_ptr(), buf.len() as size_t) };
602        self.check_size(res)
603    }
604
605    /// Input reports are returned to the host through the 'INTERRUPT IN'
606    /// endpoint. The first byte will contain the Report number if the device
607    /// uses numbered reports. Timeout measured in milliseconds, set -1 for
608    /// blocking wait.
609    pub fn read_timeout(&self, buf: &mut [u8], timeout: i32) -> HidResult<usize> {
610        let res = unsafe {
611            ffi::hid_read_timeout(
612                self._hid_device,
613                buf.as_mut_ptr(),
614                buf.len() as size_t,
615                timeout,
616            )
617        };
618        self.check_size(res)
619    }
620
621    /// Send a Feature report to the device.
622    /// Feature reports are sent over the Control endpoint as a
623    /// Set_Report transfer.  The first byte of `data` must contain the
624    /// 'Report ID'. For devices which only support a single report, this must
625    /// be set to 0x0. The remaining bytes contain the report data. Since the
626    /// 'Report ID' is mandatory, calls to `send_feature_report()` will always
627    /// contain one more byte than the report contains. For example, if a hid
628    /// report is 16 bytes long, 17 bytes must be passed to
629    /// `send_feature_report()`: 'the Report ID' (or 0x0, for devices which
630    /// do not use numbered reports), followed by the report data (16 bytes).
631    /// In this example, the length passed in would be 17.
632    pub fn send_feature_report(&self, data: &[u8]) -> HidResult<()> {
633        if data.len() == 0 {
634            return Err(HidError::InvalidZeroSizeData);
635        }
636        let res = unsafe {
637            ffi::hid_send_feature_report(self._hid_device, data.as_ptr(), data.len() as size_t)
638        };
639        let res = self.check_size(res)?;
640        if res != data.len() {
641            Err(HidError::IncompleteSendError {
642                sent: res,
643                all: data.len(),
644            })
645        } else {
646            Ok(())
647        }
648    }
649
650    /// Set the first byte of `buf` to the 'Report ID' of the report to be read.
651    /// Upon return, the first byte will still contain the Report ID, and the
652    /// report data will start in buf[1].
653    pub fn get_feature_report(&self, buf: &mut [u8]) -> HidResult<usize> {
654        let res = unsafe {
655            ffi::hid_get_feature_report(self._hid_device, buf.as_mut_ptr(), buf.len() as size_t)
656        };
657        self.check_size(res)
658    }
659
660    /// Set the device handle to be in blocking or in non-blocking mode. In
661    /// non-blocking mode calls to `read()` will return immediately with an empty
662    /// slice if there is no data to be read. In blocking mode, `read()` will
663    /// wait (block) until there is data to read before returning.
664    /// Modes can be changed at any time.
665    pub fn set_blocking_mode(&self, blocking: bool) -> HidResult<()> {
666        let res = unsafe {
667            ffi::hid_set_nonblocking(self._hid_device, if blocking { 0i32 } else { 1i32 })
668        };
669        if res == -1 {
670            Err(HidError::SetBlockingModeError {
671                mode: match blocking {
672                    true => "blocking",
673                    false => "not blocking",
674                },
675            })
676        } else {
677            Ok(())
678        }
679    }
680
681    /// Get The Manufacturer String from a HID device.
682    pub fn get_manufacturer_string(&self) -> HidResult<Option<String>> {
683        let mut buf = [0 as wchar_t; STRING_BUF_LEN];
684        let res = unsafe {
685            ffi::hid_get_manufacturer_string(
686                self._hid_device,
687                buf.as_mut_ptr(),
688                STRING_BUF_LEN as size_t,
689            )
690        };
691        let res = self.check_size(res)?;
692        unsafe { Ok(wchar_to_string(buf[..res].as_ptr()).into()) }
693    }
694
695    /// Get The Manufacturer String from a HID device.
696    pub fn get_product_string(&self) -> HidResult<Option<String>> {
697        let mut buf = [0 as wchar_t; STRING_BUF_LEN];
698        let res = unsafe {
699            ffi::hid_get_product_string(
700                self._hid_device,
701                buf.as_mut_ptr(),
702                STRING_BUF_LEN as size_t,
703            )
704        };
705        let res = self.check_size(res)?;
706        unsafe { Ok(wchar_to_string(buf[..res].as_ptr()).into()) }
707    }
708
709    /// Get The Serial Number String from a HID device.
710    pub fn get_serial_number_string(&self) -> HidResult<Option<String>> {
711        let mut buf = [0 as wchar_t; STRING_BUF_LEN];
712        let res = unsafe {
713            ffi::hid_get_serial_number_string(
714                self._hid_device,
715                buf.as_mut_ptr(),
716                STRING_BUF_LEN as size_t,
717            )
718        };
719        let res = self.check_size(res)?;
720        unsafe { Ok(wchar_to_string(buf[..res].as_ptr()).into()) }
721    }
722
723    /// Get a string from a HID device, based on its string index.
724    pub fn get_indexed_string(&self, index: i32) -> HidResult<Option<String>> {
725        let mut buf = [0 as wchar_t; STRING_BUF_LEN];
726        let res = unsafe {
727            ffi::hid_get_indexed_string(
728                self._hid_device,
729                index as c_int,
730                buf.as_mut_ptr(),
731                STRING_BUF_LEN,
732            )
733        };
734        let res = self.check_size(res)?;
735        unsafe { Ok(wchar_to_string(buf[..res].as_ptr()).into()) }
736    }
737}