rgb.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. package rgb
  2. import (
  3. "fmt"
  4. "strconv"
  5. "github.com/spf13/cobra"
  6. intdevice "netdome.biz/paul/impact-80/internal/device"
  7. intrgb "netdome.biz/paul/impact-80/internal/rgb"
  8. "netdome.biz/paul/impact-80/internal/via"
  9. )
  10. var (
  11. targetDevice string
  12. targetZone string
  13. )
  14. type zoneProtocol interface {
  15. SetValue(via.LEDType, uint8, uint8) error
  16. SetColor(via.LEDType, uint8, uint8) error
  17. }
  18. type rgbProtocol interface {
  19. zoneProtocol
  20. GetValue(via.LEDType, uint8) ([]byte, error)
  21. Close() error
  22. }
  23. var openRGBProtocol = func() (rgbProtocol, error) {
  24. proto, _, err := OpenDevice()
  25. if err != nil {
  26. return nil, err
  27. }
  28. return proto, nil
  29. }
  30. func forEachSelectedZone(zones []intrgb.Zone, fn func(intrgb.Zone, via.LEDType) error) error {
  31. channels := zoneChannels(zones)
  32. for i, zone := range zones {
  33. if err := fn(zone, channels[i]); err != nil {
  34. return err
  35. }
  36. }
  37. return nil
  38. }
  39. func setValueOnZones(proto zoneProtocol, zones []intrgb.Zone, param, value uint8) error {
  40. return forEachSelectedZone(zones, func(_ intrgb.Zone, channel via.LEDType) error {
  41. return proto.SetValue(channel, param, value)
  42. })
  43. }
  44. func setBrightnessOnZones(proto zoneProtocol, zones []intrgb.Zone, value uint8) error {
  45. return setValueOnZones(proto, zones, uint8(intrgb.Brightness), value)
  46. }
  47. func setSpeedOnZones(proto zoneProtocol, zones []intrgb.Zone, value uint8) error {
  48. return setValueOnZones(proto, zones, uint8(intrgb.Speed), value)
  49. }
  50. func setColorOnZones(proto zoneProtocol, zones []intrgb.Zone, hue, saturation uint8) error {
  51. return forEachSelectedZone(zones, func(_ intrgb.Zone, channel via.LEDType) error {
  52. return proto.SetColor(channel, hue, saturation)
  53. })
  54. }
  55. func disableLightingOnZones(proto zoneProtocol, zones []intrgb.Zone) error {
  56. return forEachSelectedZone(zones, func(_ intrgb.Zone, channel via.LEDType) error {
  57. if err := proto.SetValue(channel, uint8(intrgb.EffectID), 0); err != nil {
  58. return err
  59. }
  60. return proto.SetValue(channel, uint8(intrgb.Brightness), 0)
  61. })
  62. }
  63. func enableLightingOnZones(proto zoneProtocol, zones []intrgb.Zone) error {
  64. return forEachSelectedZone(zones, func(zone intrgb.Zone, channel via.LEDType) error {
  65. if err := proto.SetValue(channel, uint8(intrgb.EffectID), intrgb.DefaultEffect(zone)); err != nil {
  66. return err
  67. }
  68. return proto.SetValue(channel, uint8(intrgb.Brightness), 160)
  69. })
  70. }
  71. func setModeOnZones(proto zoneProtocol, zones []intrgb.Zone, value uint8) error {
  72. return setValueOnZones(proto, zones, uint8(intrgb.EffectID), value)
  73. }
  74. func OpenDevice() (*via.Protocol, intdevice.Keyboard, error) {
  75. if _, err := selectedZones(); err != nil {
  76. return nil, intdevice.Keyboard{}, err
  77. }
  78. keyboards, err := intdevice.LoadKeyboards()
  79. if err != nil {
  80. return nil, intdevice.Keyboard{}, fmt.Errorf("load keyboards: %w", err)
  81. }
  82. devices, err := intdevice.Discover(keyboards)
  83. if err != nil {
  84. return nil, intdevice.Keyboard{}, fmt.Errorf("discover: %w", err)
  85. }
  86. if len(devices) == 0 {
  87. return nil, intdevice.Keyboard{}, fmt.Errorf("no VIA-compatible keyboard found")
  88. }
  89. var dev intdevice.Device
  90. if targetDevice != "" {
  91. for _, d := range devices {
  92. if d.Path == targetDevice {
  93. dev = d
  94. break
  95. }
  96. }
  97. if dev.Path == "" {
  98. return nil, intdevice.Keyboard{}, fmt.Errorf("device %s not found", targetDevice)
  99. }
  100. } else if len(devices) == 1 {
  101. dev = devices[0]
  102. } else {
  103. return nil, intdevice.Keyboard{}, fmt.Errorf("multiple devices found, use --device to specify")
  104. }
  105. proto, err := via.New(dev)
  106. if err != nil {
  107. return nil, intdevice.Keyboard{}, fmt.Errorf("open protocol: %w", err)
  108. }
  109. return proto, dev.Keyboard, nil
  110. }
  111. func lightingEnabled(mode, brightness uint8) bool {
  112. return mode != 0 && brightness > 0
  113. }
  114. func ParseUint8(s string) (uint8, error) {
  115. v, err := strconv.ParseUint(s, 10, 8)
  116. if err != nil {
  117. return 0, fmt.Errorf("invalid value: %w", err)
  118. }
  119. return uint8(v), nil
  120. }
  121. func Init() *cobra.Command {
  122. cmd := &cobra.Command{
  123. Use: "rgb",
  124. Short: "RGB lighting commands",
  125. PersistentPreRunE: func(*cobra.Command, []string) error {
  126. _, err := selectedZones()
  127. return err
  128. },
  129. }
  130. cmd.PersistentFlags().StringVar(&targetDevice, "device", "", "HID device path to use")
  131. cmd.PersistentFlags().StringVar(&targetZone, "zone", "", "RGB lighting zone")
  132. return cmd
  133. }