| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- package hid
- import (
- "fmt"
- "os"
- "strings"
- "syscall"
- "unsafe"
- )
- // HID device access via /dev/hidraw* using pure Go syscalls.
- // No cgo, no libudev.
- const (
- ioctlRDGET = 0x80000000 + 1
- ioctlWRSET = 0x40000000 + 2
- ioctlGET = 0x30000000 + 3
- ioctlSET = 0x40000000 + 4
- )
- // Device represents a connected HID device.
- type Device struct {
- fd int
- path string
- vendorID uint16
- productID uint16
- }
- // Devices lists all connected HID devices with VID/PID.
- type DevicesInfo struct {
- Devices []DeviceInfo
- }
- // DeviceInfo holds device metadata without open handle.
- type DeviceInfo struct {
- Path string
- VendorID uint16
- ProductID uint16
- }
- // OpenPath opens a HID device by path (e.g. "/dev/hidraw0").
- func OpenPath(path string) (*Device, error) {
- fd, err := syscall.Open(path, syscall.O_RDWR, 0)
- if err != nil {
- return nil, fmt.Errorf("open %s: %w", path, err)
- }
- return &Device{fd: fd, path: path}, nil
- }
- // VID returns the vendor ID.
- func (d *Device) VID() uint16 { return d.vendorID }
- // PID returns the product ID.
- func (d *Device) PID() uint16 { return d.productID }
- // Path returns the device file path.
- func (d *Device) Path() string { return d.path }
- // SendReport sends an HID report.
- // report[0] must be the report ID (0 for single-report devices).
- func (d *Device) SendReport(reportID byte, report []byte) (int, error) {
- buf := make([]byte, 1+len(report))
- buf[0] = reportID
- copy(buf[1:], report)
- n, _, errno := syscall.Syscall(syscall.SYS_WRITE, uintptr(d.fd), uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
- if errno != 0 {
- return 0, fmt.Errorf("write: %w", errno)
- }
- return int(n), nil
- }
- // Read reads a response report.
- func (d *Device) Read(buf []byte) (int, error) {
- n, _, errno := syscall.Syscall(syscall.SYS_READ, uintptr(d.fd), uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
- if errno != 0 {
- return 0, fmt.Errorf("read: %w", errno)
- }
- return int(n), nil
- }
- // Close closes the device.
- func (d *Device) Close() error {
- return syscall.Close(d.fd)
- }
- // DiscoverAll returns a list of HID devices with their VID/PID.
- // It iterates /dev/hidraw* and reads VID/PID from /sys/class/hidraw/*/device/uevent.
- func DiscoverAll() ([]DeviceInfo, error) {
- entries, err := os.ReadDir("/dev")
- if err != nil {
- return nil, fmt.Errorf("read /dev: %w", err)
- }
- var devices []DeviceInfo
- for _, entry := range entries {
- if !strings.HasPrefix(entry.Name(), "hidraw") {
- continue
- }
- hidrawPath := "/dev/" + entry.Name()
- sysEventPath := "/sys/class/hidraw/" + entry.Name() + "/device/uevent"
- data, err := os.ReadFile(sysEventPath)
- if err != nil {
- continue
- }
- vid, pid := parseUEVENT(string(data))
- if vid == 0 || pid == 0 {
- continue
- }
- devices = append(devices, DeviceInfo{
- Path: hidrawPath,
- VendorID: vid,
- ProductID: pid,
- })
- }
- return devices, nil
- }
- func parseUEVENT(s string) (vid, pid uint16) {
- for _, line := range splitLines(s) {
- if len(line) >= 25 && line[:12] == "HID_ID=0003:" {
- data := line[12:]
- colon := -1
- for i, c := range data {
- if c == ':' {
- colon = i
- break
- }
- }
- if colon > 0 && colon+1 < len(data) {
- if v, e := parseHex(data[:colon]); e == nil {
- vid = v
- }
- if p, e := parseHex(data[colon+1:]); e == nil {
- pid = p
- }
- }
- }
- }
- return vid, pid
- }
- func splitLines(s string) []string {
- var lines []string
- var current string
- for _, c := range s {
- if c == '\n' {
- lines = append(lines, current)
- current = ""
- } else {
- current += string(c)
- }
- }
- if current != "" {
- lines = append(lines, current)
- }
- return lines
- }
- func parseHex(s string) (uint16, error) {
- var val uint16
- for _, c := range s {
- val <<= 4
- switch {
- case c >= '0' && c <= '9':
- val |= uint16(c - '0')
- case c >= 'a' && c <= 'f':
- val |= uint16(c - 'a' + 10)
- case c >= 'A' && c <= 'F':
- val |= uint16(c - 'A' + 10)
- default:
- return 0, fmt.Errorf("invalid hex char: %c", c)
- }
- }
- return val, nil
- }
|