package main import ( "fmt" "io" "github.com/spf13/cobra" "netdome.biz/paul/qmk-rgb/internal/device" intrgb "netdome.biz/paul/qmk-rgb/internal/rgb" "netdome.biz/paul/qmk-rgb/internal/via" ) // The commands below print text, because that is what a person reads, and JSON // only when it is asked for. The flag is persistent, so it reads the same // everywhere: `qmk-rgb-tool --json info` and `qmk-rgb-tool info --json` are the // same command. An agent that parses the output passes it; a person does not // have to learn a second shape for the same information. var jsonOutput bool func addJSONFlag(cmd *cobra.Command) { cmd.PersistentFlags().BoolVar(&jsonOutput, "json", false, "Print JSON instead of text") } // printEffectListText writes one effect per line, its ID in brackets, under a // heading per channel. The ID is in the line because it is what a user needs to // check a name against the register, and what `mode ` takes. func printEffectListText(out io.Writer, catalog *intrgb.Catalog, channels []via.Channel, display map[uint16]string, source string) error { for _, ch := range channels { effects := catalog.Effects(ch) if len(effects) == 0 { continue } fmt.Fprintf(out, "%s (%s)\n", channelName(ch, display), ch.Subsystem()) for _, e := range effects { fmt.Fprintf(out, " %s (%d)\n", e.Name, e.ID) } } if source != "" { fmt.Fprintf(out, "\nNames from %s\n", source) } return nil } // printZoneInfoText writes one line per zone, which is what a person compares. func printZoneInfoText(out io.Writer, state infoOutput) error { fmt.Fprintf(out, "%s\n", state.Mode) for _, z := range state.Zones { enabled := "off" if z.Enabled { enabled = "on" } fmt.Fprintf(out, " %-12s %-4s %-28s brightness %3d speed %3d color %s\n", z.Zone, enabled, z.Effect, z.Brightness, z.Speed, formatInfoColor(z.Color)) } return nil } // formatInfoColor renders a hue and saturation the way the color command takes // them, so a value read from the keyboard can be written back unchanged. func formatInfoColor(c infoColor) string { return fmt.Sprintf("hsv:%d,%d", c.Hue, c.Saturation) } // printKeyboardInfoText writes one line per keyboard, with the index --device // takes, because the index is the only part a user has to act on. func printKeyboardInfoText(out io.Writer, devices []keyboardLine) error { if len(devices) == 0 { fmt.Fprintln(out, "No QMK keyboard found.") return nil } for _, d := range devices { known := "" if d.Known { known = " (known model)" } fmt.Fprintf(out, "%d %s 0x%04X/0x%04X%s\n", d.Index, lineName(d), d.VendorID, d.ProductID, known) } return nil } // keyboardLine is one keyboard as the info command reports it. Its channels are // deliberately absent: this command does not open the board, so it cannot know // which ones it has, and reporting the ones a definition names would claim more // than it knows. type keyboardLine struct { Index int `json:"index"` Path string `json:"path"` VendorID uint16 `json:"vendorId"` ProductID uint16 `json:"productId"` Name string `json:"name"` Known bool `json:"known"` } func lineName(d keyboardLine) string { if d.Name != "" { return d.Name } return "unknown model" } // describeDevices turns discovered devices into what the info command reports. func describeDevices(devices []device.Device) []keyboardLine { lines := make([]keyboardLine, 0, len(devices)) for _, d := range devices { lines = append(lines, keyboardLine{ Index: d.Index, Path: d.Path, VendorID: d.VendorID, ProductID: d.ProductID, Name: lineName(keyboardLine{Name: d.Name}), Known: d.Known, }) } return lines }