device.go 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. package device
  2. import (
  3. "fmt"
  4. "sort"
  5. "netdome.biz/paul/qmk-rgb/internal/hid"
  6. )
  7. // Device is a connected QMK Raw HID keyboard.
  8. // Index is 1-based and is what --device accepts.
  9. type Device struct {
  10. Index int `json:"index"`
  11. Path string `json:"path"`
  12. VendorID uint16 `json:"vendorId"`
  13. ProductID uint16 `json:"productId"`
  14. Name string `json:"name,omitempty"`
  15. }
  16. // DiscoverAll returns every connected QMK Raw HID keyboard, ordered
  17. // deterministically and numbered from 1. A keyboard that does not report a USB
  18. // product string comes back with no Name; that costs the name and nothing else.
  19. func DiscoverAll() ([]Device, error) {
  20. infos, err := hid.DiscoverAll()
  21. if err != nil {
  22. return nil, fmt.Errorf("discover qmk devices: %w", err)
  23. }
  24. devices := make([]Device, 0, len(infos))
  25. for _, info := range infos {
  26. devices = append(devices, Device{
  27. Path: info.Path,
  28. VendorID: info.VendorID,
  29. ProductID: info.ProductID,
  30. Name: info.ProductString,
  31. })
  32. }
  33. return indexDevices(devices), nil
  34. }
  35. // indexDevices sorts devices deterministically and assigns 1-based indexes.
  36. // The input slice is left untouched.
  37. func indexDevices(devices []Device) []Device {
  38. sorted := make([]Device, len(devices))
  39. copy(sorted, devices)
  40. sort.SliceStable(sorted, func(i, j int) bool {
  41. a, b := sorted[i], sorted[j]
  42. switch {
  43. case a.VendorID != b.VendorID:
  44. return a.VendorID < b.VendorID
  45. case a.ProductID != b.ProductID:
  46. return a.ProductID < b.ProductID
  47. default:
  48. return a.Path < b.Path
  49. }
  50. })
  51. for i := range sorted {
  52. sorted[i].Index = i + 1
  53. }
  54. return sorted
  55. }