| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- package main
- import (
- "errors"
- "fmt"
- "strconv"
- "github.com/spf13/cobra"
- intrgb "netdome.biz/paul/qmk-rgb/internal/rgb"
- "netdome.biz/paul/qmk-rgb/internal/via"
- )
- // resolveEffectTargets turns a name into one target per channel. Whether the
- // channels were named one by one is passed in, because a name the board does not
- // have on a channel it was not asked about is a skip, and the same name on a
- // channel it was asked about is a mistake worth reporting.
- func resolveEffectTargets(catalog *intrgb.Catalog, name string, channels []via.Channel, named bool) ([]intrgb.EffectTarget, []string, error) {
- return intrgb.ResolveEffect(catalog, name, channels, named)
- }
- func NewEffectCmd() *cobra.Command {
- return withZoneArgs(&cobra.Command{
- Use: "effect <zone> [name|index]",
- Short: "Set or list RGB effects on a zone",
- Long: writesLighting("Set the RGB lighting effect on the named zones. Effect names come from a\n" +
- "per-board catalog; a keyboard without one has no names and is driven with an\n" +
- "effect index instead. A number is an effect ID; anything else is a name.\n" +
- "\n" +
- "Without a name this lists the effects each named zone has. Listing is not a\n" +
- "write, so `effect all` reports every channel the keyboard has."),
- RunE: func(cmd *cobra.Command, args []string) error {
- if len(args) == 1 {
- return listZoneEffects(cmd, args[0])
- }
- return runEffectSet(cmd, args[0], args[1])
- },
- }, 1, 2, "a zone, and an effect name or index to set")
- }
- func listZoneEffects(cmd *cobra.Command, zone string) error {
- proto, target, channels, err := openTarget(zone)
- if err != nil {
- return err
- }
- defer proto.Close()
- catalog, source, err := resolveCatalog(target)
- if err != nil {
- return err
- }
- return printEffectList(cmd, catalog, channels, target.Display, source)
- }
- // printEffectList writes the catalog per channel, as text unless --json asks
- // for the machine shape. The source of the names travels along, because a list
- // without it does not say whether it came from a definition file or from the
- // tool itself.
- func printEffectList(cmd *cobra.Command, catalog *intrgb.Catalog, channels []via.Channel, display map[uint16]string, source string) error {
- if !jsonOutput {
- return printEffectListText(cmd.OutOrStdout(), catalog, channels, display, source)
- }
- return printEffectListJSON(cmd, catalog, channels, display)
- }
- func printEffectListJSON(cmd *cobra.Command, catalog *intrgb.Catalog, channels []via.Channel, display map[uint16]string) error {
- type ZoneEffectList struct {
- Zone string `json:"zone"`
- Channel uint8 `json:"channel"`
- Subsystem string `json:"subsystem"`
- Effect string `json:"effect"`
- ID uint8 `json:"id"`
- }
- type EffectList struct {
- Catalog string `json:"catalog"`
- Zones []ZoneEffectList `json:"zones"`
- }
- list := EffectList{Catalog: catalog.Name(), Zones: []ZoneEffectList{}}
- for _, ch := range channels {
- for _, e := range catalog.Effects(ch) {
- list.Zones = append(list.Zones, ZoneEffectList{
- Zone: channelName(ch, display),
- Channel: uint8(ch),
- Subsystem: ch.Subsystem(),
- Effect: e.Name,
- ID: e.ID,
- })
- }
- }
- return encodeJSON(cmd.OutOrStdout(), list)
- }
- // effectArgument is what was asked for: a name to resolve against the board's
- // definition, or a raw effect ID to write.
- type effectArgument struct {
- Name string
- ID uint8
- IsID bool
- }
- // parseEffectArgument reads the argument of the effect command. A number is an
- // effect ID and nothing else: a name is never written as digits, so the two
- // cannot be confused, and the ID is what a board without a definition needs. A
- // number too large to be one is reported as such rather than looked up as a name.
- func parseEffectArgument(arg string) (effectArgument, error) {
- value, err := strconv.ParseUint(arg, 10, 8)
- if err == nil {
- return effectArgument{ID: uint8(value), IsID: true}, nil
- }
- var numErr *strconv.NumError
- if errors.As(err, &numErr) && errors.Is(numErr.Err, strconv.ErrRange) {
- return effectArgument{}, fmt.Errorf("effect ID %s is out of range: an effect ID is 0-255", arg)
- }
- return effectArgument{Name: arg}, nil
- }
- func runEffectSet(cmd *cobra.Command, zone, arg string) error {
- proto, target, channels, err := openTarget(zone)
- if err != nil {
- return err
- }
- defer proto.Close()
- want, err := parseEffectArgument(arg)
- if err != nil {
- return err
- }
- if want.IsID {
- return setEffectID(cmd, proto, target, channels, want.ID)
- }
- catalog, _, err := resolveCatalog(target)
- if err != nil {
- return err
- }
- // A zone was named, so a channel that cannot do this effect is a mistake and
- // not a reason to write the other ones and report a success.
- targets, _, err := resolveEffectTargets(catalog, want.Name, channels, true)
- if err != nil {
- return err
- }
- results, err := setEffectVerified(proto, targets, target.Display, catalog)
- if err != nil {
- return err
- }
- // Claiming the requested effect when the keyboard kept another one is the
- // contradiction this read-back exists to prevent: on the logo and side
- // channels an effect of ID 0 leaves the previous effect running.
- if anyEffectMismatch(results) {
- fmt.Fprintln(cmd.OutOrStdout(), formatEffectResults(want.Name, results))
- return nil
- }
- fmt.Fprintf(cmd.OutOrStdout(), "Effect set to %q\n", want.Name)
- return nil
- }
- // setEffectID writes a raw effect ID to every selected channel. It needs no
- // catalog, which is the point: an ID is the one thing a board always has, and
- // which ID is which effect is a question the board does not answer.
- func setEffectID(cmd *cobra.Command, proto rgbProtocol, target targetDeviceData, channels []via.Channel, id uint8) error {
- targets := make([]intrgb.EffectTarget, 0, len(channels))
- for _, ch := range channels {
- targets = append(targets, intrgb.EffectTarget{Channel: ch, ID: id})
- }
- results, err := setEffectVerified(proto, targets, target.Display, nil)
- if err != nil {
- return err
- }
- // The numbers are what the request was, so the numbers are what the summary
- // states: a name would be one the board may not even have.
- if anyEffectMismatch(results) {
- fmt.Fprintln(cmd.OutOrStdout(), formatEffectIDResults(results))
- return nil
- }
- fmt.Fprintf(cmd.OutOrStdout(), "Effect set to index %d\n", id)
- return nil
- }
|