diff --git a/hid.go b/hid.go index 14e0a73..9ab8f4b 100644 --- a/hid.go +++ b/hid.go @@ -1,13 +1,54 @@ // 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" + "syscall" +) + +type HidError struct { + s string + errno int +} + +func newHidError(text string, errno int) error { + return &HidError{s: text, errno: errno} +} + +func (e *HidError) Error() string { + if e.errno == 0 { + return e.s + } + return fmt.Sprintf("%s (%d)", e.s, e.errno) +} + +// 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 { + return e.errno == int(syscall.EINTR) +} // ErrDeviceClosed is returned for operations where the device closed before or // during the execution. diff --git a/hid_enabled.go b/hid_enabled.go index 2b66266..9997bc0 100644 --- a/hid_enabled.go +++ b/hid_enabled.go @@ -47,11 +47,13 @@ package hid #elif OS_WINDOWS #include "hidapi/windows/hid.c" #endif + +#include +static int _get_errno(void) { return errno; } */ import "C" import ( - "errors" "runtime" "sync" "unsafe" @@ -163,7 +165,7 @@ func OpenByPath(p string) (*Device, error) { 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 +242,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 +293,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 +306,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 +337,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 +351,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 +382,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 +424,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 +467,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 +509,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 +543,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 +599,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