package main import ( "fmt" "sort" intrgb "netdome.biz/paul/qmk-rgb/internal/rgb" "netdome.biz/paul/qmk-rgb/internal/via" ) // resolveZoneName maps a --zone value to the channels it may mean, without // opening the keyboard. A display name of the connected board wins over a // subsystem name, and a value that would mean two channels is refused rather // than resolved to one of them. // // An empty name means every channel and is reported as a nil slice. func resolveZoneName(name string, display map[uint16]string) ([]via.Channel, error) { if name == "" { return nil, nil } if channels := displayNameChannels(display, name); len(channels) > 1 { return nil, fmt.Errorf("zone %q matches several channels of this keyboard; keyboards.json gives the same name to more than one", name) } else if len(channels) == 1 { return channels, nil } for c := via.Channel(1); c <= via.AssignedChannelMax; c++ { if c.Subsystem() == name { return []via.Channel{c}, nil } } return nil, fmt.Errorf("unknown zone %q: use a channel name such as rgb_matrix, or a name from keyboards.json", name) } // displayNameChannels returns every channel the board names displayName. func displayNameChannels(display map[uint16]string, displayName string) []via.Channel { var channels []via.Channel for number, name := range display { if name == displayName { channels = append(channels, via.Channel(number)) } } sort.Slice(channels, func(i, j int) bool { return channels[i] < channels[j] }) return channels } // displayNameConflicts rejects a board whose display name is also the subsystem // name of a different channel the keyboard actually has. A name that means two // channels is a silent retarget, so the tool refuses it. // // Presence is only known after the probe, which is why this is checked here // rather than when the file is read: a static check would reject the Impact 80, // which calls channel 3 "backlight" while channel 1 is absent. func displayNameConflicts(display map[uint16]string, present []via.Channel) error { names := make([]string, 0, len(display)) for _, name := range display { names = append(names, name) } sort.Strings(names) for _, name := range names { named := via.Channel(0) for _, ch := range displayNameChannels(display, name) { named = ch break } for _, other := range present { if other != named && other.Subsystem() == name { return fmt.Errorf("keyboards.json: channel %d is named %q, which is also the subsystem name of channel %d", named, name, other) } } } return nil } // channelName is how a channel is printed: the board's display name when it has // one, otherwise the QMK subsystem name. func channelName(c via.Channel, display map[uint16]string) string { if name, ok := display[uint16(c)]; ok { return name } return c.Subsystem() } // The zone helpers below survive until the commands speak channels. They are // deleted with their call sites, not before, so every commit in between // compiles and its tests pass. func selectedZones() ([]intrgb.Zone, error) { if targetZone == "" { return intrgb.AllZones(), nil } zone, err := intrgb.ParseZone(targetZone) if err != nil { return nil, err } return []intrgb.Zone{zone}, nil } func zoneChannels(zones []intrgb.Zone) []via.Channel { var channels []via.Channel for _, zone := range zones { channels = append(channels, via.Channel(zone.Channel())) } return channels } // filterZones keeps the zones that appear in selected, preserving the order // of candidates. It neither adds nor removes zones on its own: a zone the // caller does not offer stays absent, so a profile that omits a zone is not // padded with defaults. func filterZones(candidates, selected []intrgb.Zone) []intrgb.Zone { keep := make(map[intrgb.Zone]bool, len(selected)) for _, zone := range selected { keep[zone] = true } filtered := make([]intrgb.Zone, 0, len(candidates)) for _, zone := range candidates { if keep[zone] { filtered = append(filtered, zone) } } return filtered }