diff --git a/hid.go b/hid.go index 20ab58e..6398598 100644 --- a/hid.go +++ b/hid.go @@ -1,13 +1,58 @@ // hid - Gopher Interface Devices (USB HID) // Copyright (c) 2017 Péter Szilágyi. All rights reserved. +// 2023 Martin Gysel // // This file is released under the 3-clause BSD license. Note however that Linux -// support depends on libusb, released under GNU LGPL 2.1 or later. +// support may depends on libusb, released under GNU LGPL 2.1 or later. // Package hid provides an interface for USB HID devices. package hid -import "errors" +import ( + "errors" + "fmt" + "runtime" + "syscall" +) + +type HidError struct { + s string + code int +} + +func newHidError(text string, code int) error { + return &HidError{s: text, code: code} +} + +func (e *HidError) Error() string { + if e.code == 0 { + return e.s + } + return fmt.Sprintf("%s (%d)", e.s, e.code) +} + +// InterruptedSystemCall returns true if a blocking operation was +// interrupted by a system call. +// +// Since go1.14, goroutines are now asynchronously preemptible: +// A consequence of the implementation of preemption is that on Unix systems, +// including Linux and macOS systems, programs built with Go 1.14 will receive +// more signals than programs built with earlier releases. This means that +// programs that use packages like syscall or golang.org/x/sys/unix will +// see more slow system calls fail with EINTR errors. +// +// The same is true for programs using cgo for system calls. +// Unfortunately we have no control over the underlying hidapi library, but +// we can, on error, read out errno and check for EINTR errors (at least on +// supported platforms). For convenience, this function has been added to quickly +// check if the underlying systemcall got interrupted by a signal. +// AFAIK this only happens on linux with the hidraw backend. +func (e *HidError) InterruptedSystemCall() bool { + if runtime.GOOS != "windows" { + return e.code == int(syscall.EINTR) + } + return false +} // ErrDeviceClosed is returned for operations where the device closed before or // during the execution. @@ -17,6 +62,9 @@ var ErrDeviceClosed = errors.New("hid: device closed") // operating system is not supported by the library. var ErrUnsupportedPlatform = errors.New("hid: unsupported platform") +// ErrOpenDevice is returned if there's an error when opening the device +var ErrOpenDevice = errors.New("hidapi: failed to open device") + const ( BusUnknown = 0x00 BusUSB = 0x01 diff --git a/hid_enabled.go b/hid_enabled.go index 05192a6..fa07266 100644 --- a/hid_enabled.go +++ b/hid_enabled.go @@ -47,11 +47,18 @@ package hid #elif OS_WINDOWS #include "hidapi/windows/hid.c" #endif + +static int get_errno(void) { +#ifdef OS_WINDOWS + return (int)GetLastError(); +#else + return errno; +#endif +} */ import "C" import ( - "errors" "runtime" "sync" "unsafe" @@ -135,7 +142,7 @@ func (info DeviceInfo) Open() (*Device, error) { device := C.hid_open_path(path) if device == nil { - return nil, errors.New("hidapi: failed to open device") + return nil, ErrOpenDevice } return &Device{ DeviceInfo: info, @@ -158,12 +165,12 @@ func OpenByPath(p string) (*Device, error) { device := C.hid_open_path(path) if device == nil { - return nil, errors.New("hidapi: failed to open device") + return nil, ErrOpenDevice } info := C.hid_get_device_info(device) if info == nil { - return nil, errors.New("hidapi: failed to query device info") + return nil, newHidError("hidapi: failed to query device info", (int)(C.get_errno())) } dev := &Device{ @@ -240,13 +247,14 @@ func (dev *Device) Write(b []byte) (int, error) { 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") + return 0, newHidError("hidapi: unknown failure", (int)(C.get_errno())) } failure, _ := wcharTToString(message) - return 0, errors.New("hidapi: " + failure) + return 0, newHidError("hidapi: "+failure, (int)(C.get_errno())) } return written, nil } @@ -290,10 +298,10 @@ func (dev *Device) SendFeatureReport(b []byte) (int, error) { // Device not closed, some other error occurred message := C.hid_error(device) if message == nil { - return 0, errors.New("hidapi: unknown failure") + return 0, newHidError("hidapi: unknown failure", (int)(C.get_errno())) } failure, _ := wcharTToString(message) - return 0, errors.New("hidapi: " + failure) + return 0, newHidError("hidapi: "+failure, (int)(C.get_errno())) } return written, nil } @@ -303,6 +311,10 @@ func (dev *Device) SendFeatureReport(b []byte) (int, error) { // Input reports are returned to the host through the INTERRUPT IN // endpoint. The first byte will contain the Report number if the // device uses numbered reports. +// +// A blocking system call may be interrupted by the go runtime. This +// can be checked by casting the error to an HidError and calling +// the InterruptedSystemCall() method. func (dev *Device) Read(b []byte) (int, error) { // Abort if nothing to read if len(b) == 0 { @@ -330,10 +342,10 @@ func (dev *Device) Read(b []byte) (int, error) { // Device not closed, some other error occurred message := C.hid_error(device) if message == nil { - return 0, errors.New("hidapi: unknown failure") + return 0, newHidError("hidapi: unknown failure", (int)(C.get_errno())) } failure, _ := wcharTToString(message) - return 0, errors.New("hidapi: " + failure) + return 0, newHidError("hidapi: "+failure, (int)(C.get_errno())) } return read, nil } @@ -344,6 +356,10 @@ func (dev *Device) Read(b []byte) (int, error) { // Input reports are returned to the host through the INTERRUPT IN // endpoint. The first byte will contain the Report number if the // device uses numbered reports. +// +// A blocking system call may be interrupted by the go runtime. This +// can be checked by casting the error to an HidError and calling +// the InterruptedSystemCall() method. func (dev *Device) ReadTimeout(b []byte, timeout int) (int, error) { // Abort if nothing to read if len(b) == 0 { @@ -371,10 +387,10 @@ func (dev *Device) ReadTimeout(b []byte, timeout int) (int, error) { // Device not closed, some other error occurred message := C.hid_error(device) if message == nil { - return 0, errors.New("hidapi: unknown failure") + return 0, newHidError("hidapi: unknown failure", (int)(C.get_errno())) } failure, _ := wcharTToString(message) - return 0, errors.New("hidapi: " + failure) + return 0, newHidError("hidapi: "+failure, (int)(C.get_errno())) } return read, nil } @@ -413,10 +429,10 @@ func (dev *Device) GetFeatureReport(b []byte) (int, error) { // Device not closed, some other error occurred message := C.hid_error(device) if message == nil { - return 0, errors.New("hidapi: unknown failure") + return 0, newHidError("hidapi: unknown failure", (int)(C.get_errno())) } failure, _ := wcharTToString(message) - return 0, errors.New("hidapi: " + failure) + return 0, newHidError("hidapi: "+failure, (int)(C.get_errno())) } return read, nil @@ -456,10 +472,10 @@ func (dev *Device) GetInputReport(b []byte) (int, error) { // Device not closed, some other error occurred message := C.hid_error(device) if message == nil { - return 0, errors.New("hidapi: unknown failure") + return 0, newHidError("hidapi: unknown failure", (int)(C.get_errno())) } failure, _ := wcharTToString(message) - return 0, errors.New("hidapi: " + failure) + return 0, newHidError("hidapi: "+failure, (int)(C.get_errno())) } return read, nil @@ -498,10 +514,10 @@ func (dev *Device) SetNonblocking(b bool) error { // Device not closed, some other error occurred message := C.hid_error(device) if message == nil { - return errors.New("hidapi: unknown failure") + return newHidError("hidapi: unknown failure", (int)(C.get_errno())) } failure, _ := wcharTToString(message) - return errors.New("hidapi: " + failure) + return newHidError("hidapi: "+failure, (int)(C.get_errno())) } return nil @@ -532,10 +548,10 @@ func (dev *Device) GetDeviceInfo() (*DeviceInfo, error) { // Device not closed, some other error occurred message := C.hid_error(device) if message == nil { - return nil, errors.New("hidapi: unknown failure") + return nil, newHidError("hidapi: unknown failure", (int)(C.get_errno())) } failure, _ := wcharTToString(message) - return nil, errors.New("hidapi: " + failure) + return nil, newHidError("hidapi: "+failure, (int)(C.get_errno())) } info := &DeviceInfo{ @@ -588,10 +604,10 @@ func (dev *Device) GetReportDescriptor() ([]byte, error) { // Device not closed, some other error occurred message := C.hid_error(device) if message == nil { - return nil, errors.New("hidapi: unknown failure") + return nil, newHidError("hidapi: unknown failure", (int)(C.get_errno())) } failure, _ := wcharTToString(message) - return nil, errors.New("hidapi: " + failure) + return nil, newHidError("hidapi: "+failure, (int)(C.get_errno())) } return b[:read], nil