| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- package via
- import (
- "errors"
- "fmt"
- )
- // Channel is a VIA lighting channel. The values are QMK's id_qmk_*_channel
- // constants from quantum/via.h.
- type Channel uint8
- const (
- ChannelBacklight Channel = 1
- ChannelRgblight Channel = 2
- ChannelRgbMatrix Channel = 3
- ChannelAudio Channel = 4
- ChannelLedMatrix Channel = 5
- )
- // AssignedChannelMax is the highest channel QMK currently assigns. The probe
- // asks beyond it on purpose, see probeChannelMax.
- const AssignedChannelMax = ChannelLedMatrix
- // probeChannelMax is the highest channel the probe asks about. The QMK enum
- // stops at 5; the extra reads cost one round trip each and let a future QMK
- // that claims a higher channel be found without a code change.
- const probeChannelMax = 15
- // probeValueID is the brightness value ID. It is one byte and valid on every
- // lighting subsystem, so one request per channel finds any kind of channel.
- const probeValueID = 0x01
- // Subsystem returns the QMK name of the channel's lighting subsystem. The name
- // follows from the channel number, so no board file stores it and it is always
- // available for a channel the keyboard has.
- // LightingChannels lists the QMK lighting channels in channel order, which is
- // the order the tool reports them in.
- var LightingChannels = []Channel{ChannelBacklight, ChannelRgblight, ChannelRgbMatrix, ChannelAudio, ChannelLedMatrix}
- func (c Channel) Subsystem() string {
- switch c {
- case ChannelBacklight:
- return "backlight"
- case ChannelRgblight:
- return "rgblight"
- case ChannelRgbMatrix:
- return "rgb_matrix"
- case ChannelAudio:
- return "audio"
- case ChannelLedMatrix:
- return "led_matrix"
- default:
- return ""
- }
- }
- // DetectChannels asks the keyboard which lighting channels it has, in ascending
- // order.
- //
- // The 0xFF answer is the discriminator, not the payload: QMK rejects a channel
- // it does not compile in with id_unhandled, whereas an unknown value ID on a
- // known channel is mirrored back with a zero payload. A read that fails for any
- // other reason aborts the probe, because a channel list missing entries is
- // indistinguishable from a complete one and would silently under-report.
- func (p *Protocol) DetectChannels() ([]Channel, error) {
- var present []Channel
- for c := Channel(1); c <= probeChannelMax; c++ {
- if _, err := p.GetValue(c, probeValueID); err != nil {
- if errors.Is(err, errUnhandled) {
- continue
- }
- return nil, fmt.Errorf("probe channel %d: %w", c, err)
- }
- present = append(present, c)
- }
- return present, nil
- }
- // effectValueID is the effect parameter's value ID. It is the same byte on every
- // lighting subsystem, like brightness.
- const effectValueID = 0x02
- // effectProbeValue is what EffectTop writes to find the top. It is above every
- // effect ID any firmware has, and it is not 0: QMK's VIA handler reads a 0 as
- // "turn this channel off", so a probe that wrote it would switch the lighting
- // off rather than ask a question.
- const effectProbeValue = 0xff
- // EffectTop returns the highest effect ID the keyboard's firmware accepts on a
- // channel, which is the number of effect slots the channel has minus one.
- //
- // It is found by writing above the top and reading back what the firmware kept.
- // QMK clamps rather than rejects: quantum/rgb_matrix/rgb_matrix.c maps a mode at
- // or above RGB_MATRIX_EFFECT_MAX down to EFFECT_MAX - 1, so a single write above
- // the top returns the top itself. A firmware that ignores the write instead
- // returns the value it already held, and that is the same answer the clamp gives
- // when the channel already sat on its last effect, so the two need not be told
- // apart. A returned value of 255 would mean the channel really does take it.
- //
- // The channel is left as it was found, because the probe otherwise leaves it
- // running the last effect. What it cannot restore is a channel that was off: VIA
- // enables the channel for any nonzero effect and exposes no enabled bit to read,
- // so a channel that was disabled comes back enabled. `disable` turns it off
- // again.
- func (p *Protocol) EffectTop(ch Channel) (int, error) {
- previous, err := p.GetValue(ch, effectValueID)
- if err != nil {
- return 0, fmt.Errorf("read the effect on %s: %w", ch.Subsystem(), err)
- }
- if err := p.SetValue(ch, effectValueID, effectProbeValue); err != nil {
- return 0, fmt.Errorf("probe the effect range on %s: %w", ch.Subsystem(), err)
- }
- top, readErr := p.GetValue(ch, effectValueID)
- // The keyboard is holding the probe value until the original goes back, so
- // the restore runs whether the read succeeded or not.
- restoreErr := p.SetValue(ch, effectValueID, previous[0])
- if readErr != nil {
- return 0, fmt.Errorf("read back the effect range on %s: %w", ch.Subsystem(), readErr)
- }
- if restoreErr != nil {
- return 0, fmt.Errorf("restore the effect on %s, which is left on %d: %w",
- ch.Subsystem(), top[0], restoreErr)
- }
- return int(top[0]), nil
- }
- // ChannelFromSubsystem returns the channel a QMK lighting subsystem name stands
- // for, and whether the name is one of them. It is the read side of LightingChannels
- // and lives beside it so that the vocabulary is written down once: a name a file
- // or a command carries is turned into a channel here and nowhere else.
- func ChannelFromSubsystem(name string) (Channel, bool) {
- for _, ch := range LightingChannels {
- if ch.Subsystem() == name {
- return ch, true
- }
- }
- return 0, false
- }
|