catalog.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. package main
  2. import (
  3. "fmt"
  4. intdevice "netdome.biz/paul/qmk-rgb/internal/device"
  5. intrgb "netdome.biz/paul/qmk-rgb/internal/rgb"
  6. "netdome.biz/paul/qmk-rgb/internal/via"
  7. )
  8. // definitionFlag names a definition file to use instead of looking one up.
  9. var definitionFlag string
  10. // resolveCatalog returns the effect catalog for a board, in the order the tool
  11. // trusts: the file named by --definition, then the definition in the data
  12. // directory that matches the board. The second return value says which of the two
  13. // it was, because a board that has no names at all is a different situation from
  14. // one whose names came from somewhere the user can see.
  15. //
  16. // This is the one place a catalog is looked up. A command that reached for a
  17. // catalog itself would silently ignore a definition file the user had placed,
  18. // which is the whole point of having one.
  19. func resolveCatalog(target targetDeviceData) (*intrgb.Catalog, string, error) {
  20. if definitionFlag != "" {
  21. def, err := intrgb.LoadDefinition(definitionFlag)
  22. if err != nil {
  23. return nil, "", err
  24. }
  25. if !def.Matches(target.Device.VendorID, target.Device.ProductID) {
  26. return nil, "", fmt.Errorf("%s is a definition for %s (0x%04X/0x%04X), not for this keyboard (0x%04X/0x%04X)",
  27. definitionFlag, def.Name, def.VendorID, def.ProductID,
  28. target.Device.VendorID, target.Device.ProductID)
  29. }
  30. return def.Catalog, def.Path, nil
  31. }
  32. catalog, source, err := resolveCatalogFor(target.Device.VendorID, target.Device.ProductID)
  33. return catalog, source, err
  34. }
  35. // resolveCatalogFor is the lookup without a target, for the commands that report
  36. // on a board rather than open it.
  37. func resolveCatalogFor(vendorID, productID uint16) (*intrgb.Catalog, string, error) {
  38. if definitionFlag != "" {
  39. def, err := intrgb.LoadDefinition(definitionFlag)
  40. if err != nil {
  41. return nil, "", err
  42. }
  43. if !def.Matches(vendorID, productID) {
  44. return nil, "", nil
  45. }
  46. return def.Catalog, def.Path, nil
  47. }
  48. if defs, err := intrgb.LoadDefinitionsDir(definitionsPath()); err == nil {
  49. if def := intrgb.FindDefinition(defs, vendorID, productID); def != nil {
  50. return def.Catalog, def.Path, nil
  51. }
  52. }
  53. // No definition for this board, and nothing is compiled in: the keyboard holds
  54. // numbers, not names, and a board with no names is driven through raw IDs.
  55. return nil, "", nil
  56. }
  57. // loadedDefinition returns the definition file for a board, from the data
  58. // directory or from the file --definition names, and nil when there is none.
  59. func loadedDefinition(vendorID, productID uint16) *intrgb.Definition {
  60. if definitionFlag != "" {
  61. if def, err := intrgb.LoadDefinition(definitionFlag); err == nil && def.Matches(vendorID, productID) {
  62. return def
  63. }
  64. return nil
  65. }
  66. defs, err := intrgb.LoadDefinitionsDir(definitionsPath())
  67. if err != nil {
  68. return nil
  69. }
  70. return intrgb.FindDefinition(defs, vendorID, productID)
  71. }
  72. // definitionLabels returns the channel names a definition gives the board, which
  73. // are the names VIA shows. They are the only channel names there are: a board
  74. // without a definition is addressed by its QMK subsystem name.
  75. func definitionLabels(vendorID, productID uint16) map[uint16]string {
  76. def := loadedDefinition(vendorID, productID)
  77. if def == nil {
  78. return nil
  79. }
  80. return def.Labels
  81. }
  82. // applyDefinitionLabels returns the display names a board answers to and, beside
  83. // them, the alternatives each channel keeps. The definition's label is the name
  84. // the board is called in VIA; the QMK subsystem name stays an accepted
  85. // alternative, because it follows from the channel number and is the one a
  86. // document can promise without knowing a board.
  87. //
  88. // Only a channel the definition names gets a display name, and that is
  89. // deliberate. Adding an entry for every QMK lighting channel would put
  90. // "backlight" on channel 1 of a board that has none, where the same word is also
  91. // the board's name for channel 3 — and a name that reaches two channels is
  92. // refused. A channel the definition does not name is addressed by its subsystem
  93. // name, which is what the channel number alone tells us.
  94. func applyDefinitionLabels(vendorID, productID uint16) (map[uint16]string, map[uint16][]string) {
  95. labels := definitionLabels(vendorID, productID)
  96. display := make(map[uint16]string, len(labels))
  97. alternatives := make(map[uint16][]string, len(labels))
  98. for number, label := range labels {
  99. display[number] = label
  100. if subsystem := via.Channel(number).Subsystem(); subsystem != label {
  101. alternatives[number] = []string{subsystem}
  102. }
  103. }
  104. return display, alternatives
  105. }
  106. // boardName is what a board is called: the name its definition gives it, else the
  107. // USB product string the keyboard itself reports, else an honest placeholder.
  108. func boardName(dev intdevice.Device, _ map[uint16]string) string {
  109. if def := loadedDefinition(dev.VendorID, dev.ProductID); def != nil && def.Name != "" {
  110. return def.Name
  111. }
  112. if dev.Name != "" {
  113. return dev.Name
  114. }
  115. return "unknown model"
  116. }
  117. // ensureDefinitionsDir returns the data directory, creating it if it is not
  118. // there yet, so a fetch has somewhere to write to.
  119. func ensureDefinitionsDir() (string, error) {
  120. dir, err := ensureDataDir(definitionsPath())
  121. if err != nil {
  122. return "", fmt.Errorf("create %s: %w", definitionsPath(), err)
  123. }
  124. return dir, nil
  125. }