123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- // hid - Gopher Interface Devices (USB HID)
- // Copyright (c) 2017 Péter Szilágyi. All rights reserved.
- //
- // This file is released under the 3-clause BSD license. Note however that Linux
- // support depends on libusb, released under LGNU GPL 2.1 or later.
- // +build linux,cgo darwin,!ios,cgo windows,cgo
- package hid
- /*
- #cgo CFLAGS: -I./hidapi/hidapi
- #cgo linux CFLAGS: -I./libusb/libusb -DDEFAULT_VISIBILITY="" -DOS_LINUX -D_GNU_SOURCE -DPOLL_NFDS_TYPE=int
- #cgo linux,!android LDFLAGS: -lrt
- #cgo darwin CFLAGS: -DOS_DARWIN
- #cgo darwin LDFLAGS: -framework CoreFoundation -framework IOKit
- #cgo windows CFLAGS: -DOS_WINDOWS
- #cgo windows LDFLAGS: -lsetupapi
- #ifdef OS_LINUX
- #include <sys/poll.h>
- #include "os/threads_posix.c"
- #include "os/poll_posix.c"
- #include "os/linux_usbfs.c"
- #include "os/linux_netlink.c"
- #include "core.c"
- #include "descriptor.c"
- #include "hotplug.c"
- #include "io.c"
- #include "strerror.c"
- #include "sync.c"
- #include "hidapi/libusb/hid.c"
- #elif OS_DARWIN
- #include "hidapi/mac/hid.c"
- #elif OS_WINDOWS
- #include "hidapi/windows/hid.c"
- #endif
- */
- import "C"
- import (
- "errors"
- "runtime"
- "sync"
- "unsafe"
- )
- // enumerateLock is a mutex serializing access to USB device enumeration needed
- // by the macOS USB HID system calls, which require 2 consecutive method calls
- // for enumeration, causing crashes if called concurrently.
- //
- // For more details, see:
- // https://developer.apple.com/documentation/iokit/1438371-iohidmanagersetdevicematching
- // > "subsequent calls will cause the hid manager to release previously enumerated devices"
- var enumerateLock sync.Mutex
- func init() {
- // Initialize the HIDAPI library
- C.hid_init()
- }
- // Supported returns whether this platform is supported by the HID library or not.
- // The goal of this method is to allow programatically handling platforms that do
- // not support USB HID and not having to fall back to build constraints.
- func Supported() bool {
- return true
- }
- // Enumerate returns a list of all the HID devices attached to the system which
- // match the vendor and product id:
- // - If the vendor id is set to 0 then any vendor matches.
- // - If the product id is set to 0 then any product matches.
- // - If the vendor and product id are both 0, all HID devices are returned.
- func Enumerate(vendorID uint16, productID uint16) []DeviceInfo {
- enumerateLock.Lock()
- defer enumerateLock.Unlock()
- // Gather all device infos and ensure they are freed before returning
- head := C.hid_enumerate(C.ushort(vendorID), C.ushort(productID))
- if head == nil {
- return nil
- }
- defer C.hid_free_enumeration(head)
- // Iterate the list and retrieve the device details
- var infos []DeviceInfo
- for ; head != nil; head = head.next {
- info := DeviceInfo{
- Path: C.GoString(head.path),
- VendorID: uint16(head.vendor_id),
- ProductID: uint16(head.product_id),
- Release: uint16(head.release_number),
- UsagePage: uint16(head.usage_page),
- Usage: uint16(head.usage),
- Interface: int(head.interface_number),
- }
- if head.serial_number != nil {
- info.Serial, _ = wcharTToString(head.serial_number)
- }
- if head.product_string != nil {
- info.Product, _ = wcharTToString(head.product_string)
- }
- if head.manufacturer_string != nil {
- info.Manufacturer, _ = wcharTToString(head.manufacturer_string)
- }
- infos = append(infos, info)
- }
- return infos
- }
- // Open connects to an HID device by its path name.
- func (info DeviceInfo) Open() (*Device, error) {
- path := C.CString(info.Path)
- defer C.free(unsafe.Pointer(path))
- device := C.hid_open_path(path)
- if device == nil {
- return nil, errors.New("hidapi: failed to open device")
- }
- return &Device{
- DeviceInfo: info,
- device: device,
- }, nil
- }
- // Device is a live HID USB connected device handle.
- type Device struct {
- DeviceInfo // Embed the infos for easier access
- device *C.hid_device // Low level HID device to communicate through
- lock sync.Mutex
- }
- // Close releases the HID USB device handle.
- func (dev *Device) Close() {
- dev.lock.Lock()
- defer dev.lock.Unlock()
- if dev.device != nil {
- C.hid_close(dev.device)
- dev.device = nil
- }
- }
- // Write sends an output report to a HID device.
- //
- // Write will send the data on the first OUT endpoint, if one exists. If it does
- // not, it will send the data through the Control Endpoint (Endpoint 0).
- func (dev *Device) Write(b []byte) (int, error) {
- // Abort if nothing to write
- if len(b) == 0 {
- return 0, nil
- }
- // Abort if device closed in between
- dev.lock.Lock()
- device := dev.device
- dev.lock.Unlock()
- if device == nil {
- return 0, ErrDeviceClosed
- }
- // Prepend a HID report ID on Windows, other OSes don't need it
- var report []byte
- if runtime.GOOS == "windows" {
- report = append([]byte{0x00}, b...)
- } else {
- report = b
- }
- // Execute the write operation
- written := int(C.hid_write(device, (*C.uchar)(&report[0]), C.size_t(len(report))))
- if written == -1 {
- // If the write failed, verify if closed or other error
- dev.lock.Lock()
- device = dev.device
- dev.lock.Unlock()
- if device == nil {
- return 0, ErrDeviceClosed
- }
- // Device not closed, some other error occurred
- message := C.hid_error(device)
- if message == nil {
- return 0, errors.New("hidapi: unknown failure")
- }
- failure, _ := wcharTToString(message)
- return 0, errors.New("hidapi: " + failure)
- }
- return written, nil
- }
- // Read retrieves an input report from a HID device.
- func (dev *Device) Read(b []byte) (int, error) {
- // Aborth if nothing to read
- if len(b) == 0 {
- return 0, nil
- }
- // Abort if device closed in between
- dev.lock.Lock()
- device := dev.device
- dev.lock.Unlock()
- if device == nil {
- return 0, ErrDeviceClosed
- }
- // Execute the read operation
- read := int(C.hid_read(device, (*C.uchar)(&b[0]), C.size_t(len(b))))
- if read == -1 {
- // If the read failed, verify if closed or other error
- dev.lock.Lock()
- device = dev.device
- dev.lock.Unlock()
- if device == nil {
- return 0, ErrDeviceClosed
- }
- // Device not closed, some other error occurred
- message := C.hid_error(device)
- if message == nil {
- return 0, errors.New("hidapi: unknown failure")
- }
- failure, _ := wcharTToString(message)
- return 0, errors.New("hidapi: " + failure)
- }
- return read, nil
- }
|