zones.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. package main
  2. import (
  3. "fmt"
  4. "sort"
  5. intrgb "netdome.biz/paul/qmk-rgb/internal/rgb"
  6. "netdome.biz/paul/qmk-rgb/internal/via"
  7. )
  8. // resolveZoneName maps a --zone value to the channels it may mean, without
  9. // opening the keyboard. A display name of the connected board wins over a
  10. // subsystem name, and a value that would mean two channels is refused rather
  11. // than resolved to one of them.
  12. //
  13. // An empty name means every channel and is reported as a nil slice.
  14. func resolveZoneName(name string, display map[uint16]string) ([]via.Channel, error) {
  15. if name == "" {
  16. return nil, nil
  17. }
  18. if channels := displayNameChannels(display, name); len(channels) > 1 {
  19. return nil, fmt.Errorf("zone %q matches several channels of this keyboard; keyboards.json gives the same name to more than one", name)
  20. } else if len(channels) == 1 {
  21. return channels, nil
  22. }
  23. for c := via.Channel(1); c <= via.AssignedChannelMax; c++ {
  24. if c.Subsystem() == name {
  25. return []via.Channel{c}, nil
  26. }
  27. }
  28. return nil, fmt.Errorf("unknown zone %q: use a channel name such as rgb_matrix, or a name from keyboards.json", name)
  29. }
  30. // displayNameChannels returns every channel the board names displayName.
  31. func displayNameChannels(display map[uint16]string, displayName string) []via.Channel {
  32. var channels []via.Channel
  33. for number, name := range display {
  34. if name == displayName {
  35. channels = append(channels, via.Channel(number))
  36. }
  37. }
  38. sort.Slice(channels, func(i, j int) bool { return channels[i] < channels[j] })
  39. return channels
  40. }
  41. // displayNameConflicts rejects a board whose display name is also the subsystem
  42. // name of a different channel the keyboard actually has. A name that means two
  43. // channels is a silent retarget, so the tool refuses it.
  44. //
  45. // Presence is only known after the probe, which is why this is checked here
  46. // rather than when the file is read: a static check would reject the Impact 80,
  47. // which calls channel 3 "backlight" while channel 1 is absent.
  48. func displayNameConflicts(display map[uint16]string, present []via.Channel) error {
  49. names := make([]string, 0, len(display))
  50. for _, name := range display {
  51. names = append(names, name)
  52. }
  53. sort.Strings(names)
  54. for _, name := range names {
  55. named := via.Channel(0)
  56. for _, ch := range displayNameChannels(display, name) {
  57. named = ch
  58. break
  59. }
  60. for _, other := range present {
  61. if other != named && other.Subsystem() == name {
  62. return fmt.Errorf("keyboards.json: channel %d is named %q, which is also the subsystem name of channel %d", named, name, other)
  63. }
  64. }
  65. }
  66. return nil
  67. }
  68. // channelName is how a channel is printed: the board's display name when it has
  69. // one, otherwise the QMK subsystem name.
  70. func channelName(c via.Channel, display map[uint16]string) string {
  71. if name, ok := display[uint16(c)]; ok {
  72. return name
  73. }
  74. return c.Subsystem()
  75. }
  76. // The zone helpers below survive until the commands speak channels. They are
  77. // deleted with their call sites, not before, so every commit in between
  78. // compiles and its tests pass.
  79. func selectedZones() ([]intrgb.Zone, error) {
  80. if targetZone == "" {
  81. return intrgb.AllZones(), nil
  82. }
  83. zone, err := intrgb.ParseZone(targetZone)
  84. if err != nil {
  85. return nil, err
  86. }
  87. return []intrgb.Zone{zone}, nil
  88. }
  89. func zoneChannels(zones []intrgb.Zone) []via.Channel {
  90. var channels []via.Channel
  91. for _, zone := range zones {
  92. channels = append(channels, via.Channel(zone.Channel()))
  93. }
  94. return channels
  95. }
  96. // filterZones keeps the zones that appear in selected, preserving the order
  97. // of candidates. It neither adds nor removes zones on its own: a zone the
  98. // caller does not offer stays absent, so a profile that omits a zone is not
  99. // padded with defaults.
  100. func filterZones(candidates, selected []intrgb.Zone) []intrgb.Zone {
  101. keep := make(map[intrgb.Zone]bool, len(selected))
  102. for _, zone := range selected {
  103. keep[zone] = true
  104. }
  105. filtered := make([]intrgb.Zone, 0, len(candidates))
  106. for _, zone := range candidates {
  107. if keep[zone] {
  108. filtered = append(filtered, zone)
  109. }
  110. }
  111. return filtered
  112. }