device.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. package device
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "sort"
  8. "netdome.biz/paul/impact-80/internal/hid"
  9. )
  10. // Keyboard is a metadata record from keyboards.json.
  11. // It names a known keyboard, but it is not required for discovery: unknown
  12. // QMK keyboards are usable too. Fields the tool does not read are ignored,
  13. // so existing entries may keep carrying them.
  14. type Keyboard struct {
  15. Name string `json:"name"`
  16. VendorID uint16 `json:"vendorId"`
  17. ProductID uint16 `json:"productId"`
  18. }
  19. // Device is a connected QMK Raw HID keyboard.
  20. // Index is 1-based and is what --device accepts.
  21. type Device struct {
  22. Index int `json:"index"`
  23. Path string `json:"path"`
  24. VendorID uint16 `json:"vendorId"`
  25. ProductID uint16 `json:"productId"`
  26. Name string `json:"name,omitempty"`
  27. Known bool `json:"known"`
  28. }
  29. // DiscoverAll returns every connected QMK Raw HID keyboard, ordered
  30. // deterministically and numbered from 1. Keyboards absent from
  31. // keyboards.json are included with Known false and no Name.
  32. func DiscoverAll() ([]Device, error) {
  33. infos, err := hid.DiscoverAll()
  34. if err != nil {
  35. return nil, fmt.Errorf("discover qmk devices: %w", err)
  36. }
  37. known, err := knownKeyboards()
  38. if err != nil {
  39. // Missing or malformed keyboards.json only costs us names, not
  40. // the ability to drive the keyboard.
  41. known = nil
  42. }
  43. devices := make([]Device, 0, len(infos))
  44. for _, info := range infos {
  45. kb, ok := known[deviceKey(info.VendorID, info.ProductID)]
  46. devices = append(devices, Device{
  47. Path: info.Path,
  48. VendorID: info.VendorID,
  49. ProductID: info.ProductID,
  50. Name: kb.Name,
  51. Known: ok,
  52. })
  53. }
  54. return indexDevices(devices), nil
  55. }
  56. // indexDevices sorts devices deterministically and assigns 1-based indexes.
  57. // The input slice is left untouched.
  58. func indexDevices(devices []Device) []Device {
  59. sorted := make([]Device, len(devices))
  60. copy(sorted, devices)
  61. sort.SliceStable(sorted, func(i, j int) bool {
  62. a, b := sorted[i], sorted[j]
  63. switch {
  64. case a.VendorID != b.VendorID:
  65. return a.VendorID < b.VendorID
  66. case a.ProductID != b.ProductID:
  67. return a.ProductID < b.ProductID
  68. default:
  69. return a.Path < b.Path
  70. }
  71. })
  72. for i := range sorted {
  73. sorted[i].Index = i + 1
  74. }
  75. return sorted
  76. }
  77. func deviceKey(vendorID, productID uint16) string {
  78. return fmt.Sprintf("%04x:%04x", vendorID, productID)
  79. }
  80. func knownKeyboards() (map[string]Keyboard, error) {
  81. keyboards, err := LoadKeyboards()
  82. if err != nil {
  83. return nil, err
  84. }
  85. known := make(map[string]Keyboard, len(keyboards))
  86. for _, kb := range keyboards {
  87. known[deviceKey(kb.VendorID, kb.ProductID)] = kb
  88. }
  89. return known, nil
  90. }
  91. // LoadKeyboards reads keyboard definitions from keyboards.json.
  92. func LoadKeyboards() ([]Keyboard, error) {
  93. path, err := findKeyboardsJSON()
  94. if err != nil {
  95. return nil, fmt.Errorf("find keyboards.json: %w", err)
  96. }
  97. data, err := os.ReadFile(path)
  98. if err != nil {
  99. return nil, fmt.Errorf("read keyboards.json: %w", err)
  100. }
  101. var keyboards []Keyboard
  102. if err := json.Unmarshal(data, &keyboards); err != nil {
  103. return nil, fmt.Errorf("parse keyboards.json: %w", err)
  104. }
  105. return keyboards, nil
  106. }
  107. // findKeyboardsJSON searches for keyboards.json in common locations.
  108. var findKeyboardsJSON = func() (string, error) {
  109. exe, err := os.Executable()
  110. if err == nil {
  111. path := filepath.Join(filepath.Dir(exe), "keyboards.json")
  112. if _, err := os.Stat(path); err == nil {
  113. return path, nil
  114. }
  115. }
  116. path := "keyboards.json"
  117. if _, err := os.Stat(path); err == nil {
  118. return path, nil
  119. }
  120. cwd, err := os.Getwd()
  121. if err == nil {
  122. path := filepath.Join(cwd, "keyboards.json")
  123. if _, err := os.Stat(path); err == nil {
  124. return path, nil
  125. }
  126. }
  127. return "", fmt.Errorf("keyboards.json not found")
  128. }