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 `effect ` 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, // and nothing else. A header naming the keyboard's "mode" was the first zone's // effect, so a board running three different effects printed one of them twice, // as a line above the others that looked like it applied to all of them. The // effect of every channel is on its own line; `mode` remains in the JSON, which // is a shape this tool has always had. func printZoneInfoText(out io.Writer, state infoOutput) error { 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 { line := fmt.Sprintf("%d %s 0x%04X/0x%04X", d.Index, d.Name, d.VendorID, d.ProductID) if d.Effects { line += " (effect names)" } fmt.Fprintln(out, line) } return nil } // keyboardLine is one keyboard as the info command reports it. Whether it has // effect names is a fact about the tool, not about a data file: it is true when // a definition covers the board or a catalog is compiled in for it. Its channels // are deliberately absent, because this command does not open the board and // cannot know which ones it has. type keyboardLine struct { Index int `json:"index"` Path string `json:"path"` VendorID uint16 `json:"vendorId"` ProductID uint16 `json:"productId"` Name string `json:"name"` Effects bool `json:"effects"` } // describeDevices turns discovered devices into what the info command reports: // the name a board answers to and whether this tool has effect names for it. func describeDevices(devices []device.Device) []keyboardLine { lines := make([]keyboardLine, 0, len(devices)) for _, d := range devices { display, _ := applyDefinitionLabels(d.VendorID, d.ProductID) catalog, _, _ := resolveCatalogFor(d.VendorID, d.ProductID) // No channel list here: this command does not open the board, so it // cannot know which channels it has, and reporting the ones its // definition names would claim more than it knows. `info` opens the // keyboard and reports those. lines = append(lines, keyboardLine{ Index: d.Index, Path: d.Path, VendorID: d.VendorID, ProductID: d.ProductID, Name: boardName(d, display), Effects: catalog != nil, }) } return lines }