package rgb import ( "fmt" "math" ) // Effect defines an RGB lighting effect. type Effect uint8 const ( Static Effect = 0 Breathing Effect = 1 EffectAlmond Effect = 2 BandSat Effect = 3 BandVal Effect = 4 BandHue Effect = 5 Chroma Effect = 6 Rainbow Effect = 7 RainbowWave Effect = 8 Dance Effect = 9 RainbowDance Effect = 10 ChromaDance Effect = 11 Snake Effect = 12 Tail Effect = 13 Split Effect = 14 Heartbeat Effect = 15 Blink Effect = 16 BlinkStatic Effect = 17 Gradient Effect = 18 Testing Effect = 19 ) // EffectName returns the human-readable name of an effect. func (e Effect) String() string { names := map[Effect]string{ Static: "static", Breathing: "breathing", EffectAlmond: "almond", BandSat: "band_sat", BandVal: "band_val", BandHue: "band_hue", Chroma: "chroma", Rainbow: "rainbow", RainbowWave: "rainbow_wave", Dance: "dance", RainbowDance: "rainbow_dance", ChromaDance: "chroma_dance", Snake: "snake", Tail: "tail", Split: "split", Heartbeat: "heartbeat", Blink: "blink", BlinkStatic: "blink_static", Gradient: "gradient", Testing: "testing", } if name, ok := names[e]; ok { return name } return "unknown" } // ParseEffect converts a string name to an Effect. func ParseEffect(name string) (Effect, error) { switch name { case "static": return Static, nil case "breathing": return Breathing, nil case "almond": return EffectAlmond, nil case "band_sat": return BandSat, nil case "band_val": return BandVal, nil case "band_hue": return BandHue, nil case "chroma": return Chroma, nil case "rainbow": return Rainbow, nil case "rainbow_wave": return RainbowWave, nil case "dance": return Dance, nil case "rainbow_dance": return RainbowDance, nil case "chroma_dance": return ChromaDance, nil case "snake": return Snake, nil case "tail": return Tail, nil case "split": return Split, nil case "heartbeat": return Heartbeat, nil case "blink": return Blink, nil case "blink_static": return BlinkStatic, nil case "gradient": return Gradient, nil case "testing": return Testing, nil default: return 0, fmt.Errorf("unknown effect: %s", name) } } // LEDParam defines QMK rgblight parameter indices. type LEDParam uint8 const ( Mode LEDParam = 0 Brightness LEDParam = 1 Speed LEDParam = 2 Enable LEDParam = 3 Hue LEDParam = 26 Saturation LEDParam = 27 EffectID LEDParam = 48 EffectSwitch LEDParam = 49 BrightSet LEDParam = 50 ) // Color represents an RGB color. type Color struct { R uint8 G uint8 B uint8 } // ParseHexColor parses a 6-digit hex color string (e.g. "ff0000"). func ParseHexColor(s string) (Color, error) { if len(s) != 6 { return Color{}, fmt.Errorf("invalid hex color: %s", s) } r := hexDigit(s[0])<<4 + hexDigit(s[1]) g := hexDigit(s[2])<<4 + hexDigit(s[3]) b := hexDigit(s[4])<<4 + hexDigit(s[5]) return Color{R: r, G: g, B: b}, nil } func hexDigit(c byte) uint8 { switch { case c >= '0' && c <= '9': return c - '0' case c >= 'a' && c <= 'f': return c - 'a' + 10 case c >= 'A' && c <= 'F': return c - 'A' + 10 default: return 0 } } // State represents the current RGB state from the keyboard. type State struct { Enabled bool Mode Effect Brightness uint8 Speed uint8 Color Color } // HSV converts the color to HSV (QMK rgblight uses 0-255 ranges for H, S, V). func (c Color) HSV() (h, s, v uint8) { r, g, b := float64(c.R), float64(c.G), float64(c.B) max := math.Max(r, math.Max(g, b)) min := math.Min(r, math.Min(g, b)) v = uint8(max) d := max - min if d == 0 { return 0, 0, v } if max > 0 { s = uint8((d / max) * 255) } else { return 0, 0, 0 } var hFloat float64 switch { case max == r: hFloat = ((g - b) / d) * 60.0 case max == g: hFloat = ((b-r)/d)*60.0 + 120.0 case max == b: hFloat = ((r-g)/d)*60.0 + 240.0 } if hFloat < 0 { hFloat += 360.0 } h = uint8((hFloat / 360.0) * 255.0) if h >= 255 { h = 254 } return h, s, v }