hid

package module
v0.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 8, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

go-ctap/hid

Go Reference Go

go-ctap/hid is a cgo-free Go library for discovering and communicating with HID devices on Windows, macOS, and Linux. It uses native operating-system facilities and requires neither libhidapi nor a C toolchain.

The library was created primarily as the HID backend for go-ctap/ctap, but it is protocol-agnostic and can be used independently with other HID devices. The module is currently pre-v1, so its API may continue to evolve between minor releases.

Supported platforms

Capability Windows macOS Linux
Enumeration and filtering Yes Yes Yes
Connection events Yes Yes Yes
Input/output reports Yes Yes Yes
Feature reports Yes Yes Yes
Context-aware reads and writes Yes Yes Yes
Configurable read timeout Yes
Native backend HID, SetupAPI, Configuration Manager IOKit and Core Foundation via purego hidraw, sysfs, and kernel uevents

Installation

The module requires Go 1.25 or newer.

go get github.com/telesma-app/hid

Usage

Enumerate returns a Go iterator. Filters are exact matches and can be combined; this example selects the FIDO HID usage collection used by go-ctap:

for info, err := range hid.Enumerate(
	hid.WithUsagePage(0xf1d0),
	hid.WithUsage(0x01),
) {
	if err != nil {
		log.Printf("enumerate HID: %v", err)
		continue
	}

	log.Printf(
		"path=%q vid=%04x pid=%04x product=%q",
		info.Path,
		info.VendorID,
		info.ProductID,
		info.ProductStr,
	)
}

Pass DeviceInfo.Path to OpenPath to get a device with Read, Write, SendFeatureReport, GetFeatureReport, and Close. Output and feature-report buffers begin with the report ID; use 0 for an unnumbered report. Higher-level framing, such as CTAPHID, is intentionally left to packages such as go-ctap/ctap.

Reads and writes accept a context because they can block. Cancellation is best-effort:

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

if _, err := device.Write(ctx, request); err != nil {
	log.Fatal(err)
}
if _, err := device.Read(ctx, response); err != nil {
	log.Fatal(err)
}

After a canceled write, do not assume that the report was not sent and do not automatically retry it. The driver or device may finish an in-flight write after Write returns ctx.Err().

Connection events

Watch captures every HID device already present in an initial snapshot, then publishes live connected and disconnected events.

watcher, err := hid.Watch()
if err != nil {
	log.Fatal(err)
}
defer watcher.Close()

for _, device := range watcher.Snapshot().Devices {
	if device.DeviceInfo != nil {
		log.Printf("present: %s", device.DeviceInfo.Path)
	}
}

for event := range watcher.Listen() {
	if event.DeviceInfo != nil {
		log.Printf("%s: %s", event.Type, event.DeviceInfo.Path)
	}
	if event.MetadataErr != nil {
		log.Printf("HID event metadata: %v", event.MetadataErr)
	}
}

if err := watcher.Close(); err != nil {
	log.Printf("HID watcher stopped: %v", err)
}

Watch covers all HID devices and should be filtered by the caller. Delivery is ordered and queued, so the channel should be consumed continuously or the watcher closed when it is no longer needed. A non-nil DeviceEvent.MetadataErr means that the state change occurred but some metadata may be incomplete. When Listen closes unexpectedly, call Close to retrieve the terminal watcher error.

Platform notes

  • Device paths are opaque and platform-specific. DeviceInfo metadata is best-effort, and fields unavailable on a platform remain empty or zero.
  • Enumeration and event monitoring do not guarantee I/O access; OpenPath remains subject to operating-system, driver, and sandbox policy.
  • Reads block by default. A context deadline is portable; WithReadTimeout remains available in Windows builds.
  • Cancellation is best-effort. Windows requests cancellation of the specific overlapped read or write with CancelIoEx. On macOS, canceling a read stops waiting for the next callback report. Linux I/O and an in-flight macOS write may continue in the driver or device after the method returns; operations of the same kind remain serialized until the native call finishes.
  • Feature-report methods do not accept a context because the synchronous HID APIs used here do not provide a practical, operation-specific cancellation mechanism.
  • On macOS, enumeration and events do not open devices, but opening protected devices for I/O may still be denied by system or sandbox policy.
  • On Linux, access to /dev/hidrawN depends on udev rules and permissions. A connection event may arrive before the device node and its final permissions are ready.

Testing

go test ./...
CGO_ENABLED=0 go test ./...
go vet ./...

License

Licensed under the Apache License 2.0.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Enumerate

func Enumerate(options ...EnumerateOption) iter.Seq2[*DeviceInfo, error]

func WithContext

func WithContext(ctx context.Context, device ContextReadWriter) io.ReadWriter

WithContext binds ctx to device and adapts it to io.ReadWriter. Read and Write delegate directly to device without adding context checks.

Types

type ContextReadWriter

type ContextReadWriter interface {
	Read(context.Context, []byte) (int, error)
	Write(context.Context, []byte) (int, error)
}

ContextReadWriter reads and writes HID reports using a context.

type Device

type Device struct {
	// contains filtered or unexported fields
}

func OpenPath

func OpenPath(path string) (*Device, error)

func (*Device) Close

func (d *Device) Close() error

func (*Device) GetFeatureReport

func (d *Device) GetFeatureReport(report []byte) (int, error)

func (*Device) Read

func (d *Device) Read(ctx context.Context, b []byte) (int, error)

func (*Device) SendFeatureReport

func (d *Device) SendFeatureReport(report []byte) error

func (*Device) Write

func (d *Device) Write(ctx context.Context, b []byte) (int, error)

type DeviceEvent

type DeviceEvent struct {
	Type        DeviceEventType
	DeviceInfo  *DeviceInfo
	MetadataErr error
}

type DeviceEventType

type DeviceEventType string
const (
	DeviceEventConnected    DeviceEventType = "connected"
	DeviceEventDisconnected DeviceEventType = "disconnected"
)

type DeviceInfo

type DeviceInfo struct {
	Path           string // Platform-Specific Device Path
	VendorID       uint16 // Device Vendor ID
	ProductID      uint16 // Device Product ID
	SerialNbr      string // Serial Number
	ReleaseNbr     uint16 // Device Version Number
	MfrStr         string // Manufacturer String
	ProductStr     string // Product String
	UsagePage      uint16 // Usage Page for Device/Interface
	Usage          uint16 // Usage for Device/Interface
	InterfaceNbr   int    // USB Interface Number
	InstanceID     string
	ParentDeviceID string
}

type DeviceSnapshot

type DeviceSnapshot struct {
	DeviceInfo  *DeviceInfo
	MetadataErr error
}

DeviceSnapshot describes one device in a Watcher's initial snapshot. MetadataErr means that the device is present but some metadata is incomplete.

type EnumerateOption

type EnumerateOption func(*enumerateOptions)

func WithInstanceID

func WithInstanceID(instanceID string) EnumerateOption

func WithInterfaceNumber

func WithInterfaceNumber(interfaceNbr int) EnumerateOption

func WithManufacturerString

func WithManufacturerString(mfrStr string) EnumerateOption

func WithParentDeviceID

func WithParentDeviceID(parentDeviceID string) EnumerateOption

func WithPath

func WithPath(path string) EnumerateOption

func WithProductID

func WithProductID(productID uint16) EnumerateOption

func WithProductString

func WithProductString(productStr string) EnumerateOption

func WithReleaseNumber

func WithReleaseNumber(releaseNbr uint16) EnumerateOption

func WithSerialNumber

func WithSerialNumber(serialNbr string) EnumerateOption

func WithUsage

func WithUsage(usage uint16) EnumerateOption

func WithUsagePage

func WithUsagePage(usagePage uint16) EnumerateOption

func WithVendorID

func WithVendorID(vendorID uint16) EnumerateOption

type Snapshot

type Snapshot struct {
	Devices []DeviceSnapshot
}

Snapshot is the initial device state captured by Watch.

type Watcher

type Watcher interface {
	Snapshot() Snapshot
	Listen() <-chan DeviceEvent
	Close() error
}

Watcher publishes changes which happen after Snapshot.

func Watch

func Watch() (Watcher, error)

Watch captures the current HID snapshot and then publishes later connection and removal events.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL