device.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. package device
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "sort"
  8. "netdome.biz/paul/qmk-rgb/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. // Channels maps a VIA channel number to the name this board uses for it.
  19. // It is optional: a board without it is addressed by its QMK subsystem
  20. // name, which follows from the channel number.
  21. Channels map[uint16]string `json:"channels"`
  22. }
  23. // KeyboardFor returns the keyboards.json entry for a device and whether one
  24. // exists. A missing or malformed file yields no entry and no error, because the
  25. // file supplies names only: costing names must not cost the ability to drive the
  26. // keyboard.
  27. func KeyboardFor(vendorID, productID uint16) (Keyboard, bool, error) {
  28. keyboards, err := LoadKeyboards()
  29. if err != nil {
  30. return Keyboard{}, false, nil
  31. }
  32. for _, kb := range keyboards {
  33. if kb.VendorID == vendorID && kb.ProductID == productID {
  34. return kb, true, nil
  35. }
  36. }
  37. return Keyboard{}, false, nil
  38. }
  39. // Device is a connected QMK Raw HID keyboard.
  40. // Index is 1-based and is what --device accepts.
  41. type Device struct {
  42. Index int `json:"index"`
  43. Path string `json:"path"`
  44. VendorID uint16 `json:"vendorId"`
  45. ProductID uint16 `json:"productId"`
  46. Name string `json:"name,omitempty"`
  47. Known bool `json:"known"`
  48. }
  49. // DiscoverAll returns every connected QMK Raw HID keyboard, ordered
  50. // deterministically and numbered from 1. Keyboards absent from
  51. // keyboards.json are included with Known false and no Name.
  52. func DiscoverAll() ([]Device, error) {
  53. infos, err := hid.DiscoverAll()
  54. if err != nil {
  55. return nil, fmt.Errorf("discover qmk devices: %w", err)
  56. }
  57. known, err := knownKeyboards()
  58. if err != nil {
  59. // Missing or malformed keyboards.json only costs us names, not
  60. // the ability to drive the keyboard.
  61. known = nil
  62. }
  63. devices := make([]Device, 0, len(infos))
  64. for _, info := range infos {
  65. kb, ok := known[deviceKey(info.VendorID, info.ProductID)]
  66. devices = append(devices, Device{
  67. Path: info.Path,
  68. VendorID: info.VendorID,
  69. ProductID: info.ProductID,
  70. Name: kb.Name,
  71. Known: ok,
  72. })
  73. }
  74. return indexDevices(devices), nil
  75. }
  76. // indexDevices sorts devices deterministically and assigns 1-based indexes.
  77. // The input slice is left untouched.
  78. func indexDevices(devices []Device) []Device {
  79. sorted := make([]Device, len(devices))
  80. copy(sorted, devices)
  81. sort.SliceStable(sorted, func(i, j int) bool {
  82. a, b := sorted[i], sorted[j]
  83. switch {
  84. case a.VendorID != b.VendorID:
  85. return a.VendorID < b.VendorID
  86. case a.ProductID != b.ProductID:
  87. return a.ProductID < b.ProductID
  88. default:
  89. return a.Path < b.Path
  90. }
  91. })
  92. for i := range sorted {
  93. sorted[i].Index = i + 1
  94. }
  95. return sorted
  96. }
  97. func deviceKey(vendorID, productID uint16) string {
  98. return fmt.Sprintf("%04x:%04x", vendorID, productID)
  99. }
  100. func knownKeyboards() (map[string]Keyboard, error) {
  101. keyboards, err := LoadKeyboards()
  102. if err != nil {
  103. return nil, err
  104. }
  105. known := make(map[string]Keyboard, len(keyboards))
  106. for _, kb := range keyboards {
  107. known[deviceKey(kb.VendorID, kb.ProductID)] = kb
  108. }
  109. return known, nil
  110. }
  111. // LoadKeyboards reads keyboard definitions from keyboards.json.
  112. func LoadKeyboards() ([]Keyboard, error) {
  113. path, err := findKeyboardsJSON()
  114. if err != nil {
  115. return nil, fmt.Errorf("find keyboards.json: %w", err)
  116. }
  117. data, err := os.ReadFile(path)
  118. if err != nil {
  119. return nil, fmt.Errorf("read keyboards.json: %w", err)
  120. }
  121. var keyboards []Keyboard
  122. if err := json.Unmarshal(data, &keyboards); err != nil {
  123. return nil, fmt.Errorf("parse keyboards.json: %w", err)
  124. }
  125. return keyboards, nil
  126. }
  127. // findKeyboardsJSON searches for keyboards.json in common locations.
  128. var findKeyboardsJSON = func() (string, error) {
  129. exe, err := os.Executable()
  130. if err == nil {
  131. path := filepath.Join(filepath.Dir(exe), "keyboards.json")
  132. if _, err := os.Stat(path); err == nil {
  133. return path, nil
  134. }
  135. }
  136. path := "keyboards.json"
  137. if _, err := os.Stat(path); err == nil {
  138. return path, nil
  139. }
  140. cwd, err := os.Getwd()
  141. if err == nil {
  142. path := filepath.Join(cwd, "keyboards.json")
  143. if _, err := os.Stat(path); err == nil {
  144. return path, nil
  145. }
  146. }
  147. return "", fmt.Errorf("keyboards.json not found")
  148. }