Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/hiderror #15

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions hid.go
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand Down
60 changes: 38 additions & 22 deletions hid_enabled.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -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{
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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
Expand Down