package catalogue import ( "fmt" "strings" ) // The graphical session's seats (novox/hq ADR 0208): one module per piece of software, each piece's // role a node seat in the mesh's own set, so i3 and sway, xterm and foot, rofi and dmenu compete for // a role rather than each inventing one — and a machine running two of one role is refused at // assignment instead of found by two bars on one screen. const ( LoginManagerSeat = "node-login-manager" DisplayServerSeat = "node-display-server" DisplaySessionSeat = "node-display-session" TerminalEmulatorSeat = "node-terminal-emulator" LauncherSeat = "node-launcher" NotifierSeat = "node-notifier" LockScreenSeat = "node-lock-screen" ClipboardSeat = "node-clipboard" BarSeat = "node-bar" CompositorSeat = "node-compositor" SecretServiceSeat = "node-secret-service" ) // BarKindBlock is the bar's one received kind: a block (novox/hq ADR 0255). const BarKindBlock = "block" // barShows is what a contributed block may show, and the options each takes — the bar-neutral // vocabulary every holder of node-bar renders. A new entry is a decision, and every holder's template // renders it (the shape's examples) before it is accepted. var barShows = map[string]map[string]barOption{ // The machine's battery: its charge, and how long it lasts or takes to fill. "battery": {"device": {kind: "text"}}, // One line a command prints, run every interval seconds: what the bar cannot know itself. "command": {"command": {kind: "text", required: true}, "interval": {kind: "seconds", required: true}}, // A value the contributor keeps on the bus (novox/hq ADR 0255): its state `.` and the // key, this machine's name when absent. The value is an object whose `text` is what is shown. The // holder is granted to read it and shows it however its bar shows a changing value; no file and // no command passes between the two modules. "state": {"state": {kind: "text", required: true}, "key": {kind: "text"}}, } // barBlockReads is the state and key a block shows, if any; an empty key is the machine's own. func barBlockReads(data map[string]any) []StateRead { if data["shows"] != "state" { return nil } options, _ := data["options"].(map[string]any) s, ok := options["state"].(string) if !ok || s == "" { return nil } key, _ := options["key"].(string) return []StateRead{{Read: s, Key: key}} } type barOption struct { kind string // text, or seconds (a whole number from 1 to 86400) required bool } func barBlockProblems(data map[string]any) []string { var problems []string for field := range data { if !oneOf([]string{"bar", "place", "order", "shows", "options"}, field) { problems = append(problems, fmt.Sprintf("a block has no field %q; it has bar, place, order, shows and options", field)) } } shows, _ := data["shows"].(string) takes, known := barShows[shows] if !known { return append(problems, fmt.Sprintf("a block shows %v; it shows one of %s", data["shows"], strings.Join(sortedKeys(barShows), ", "))) } options := map[string]any{} if raw, has := data["options"]; has { o, ok := raw.(map[string]any) if !ok { return append(problems, "a block's options are an object") } options = o } for name, v := range options { opt, ok := takes[name] if !ok { problems = append(problems, fmt.Sprintf("a %s block has no option %q; it takes %s", shows, name, orNothing(sortedKeys(takes)))) continue } switch opt.kind { case "text": if t, isText := v.(string); !isText || strings.TrimSpace(t) == "" { problems = append(problems, fmt.Sprintf("a %s block's %s is text", shows, name)) } case "seconds": if _, whole := wholeIn(v, 1, 86400); !whole { problems = append(problems, fmt.Sprintf("a %s block's %s is a whole number of seconds from 1 to 86400", shows, name)) } } } for _, name := range sortedKeys(takes) { if _, has := options[name]; takes[name].required && !has { problems = append(problems, fmt.Sprintf("a %s block needs the option %s", shows, name)) } } return problems } // withShapeCode gives a data-shaped kind of the generated seat list the checks its definition cannot carry, being code: // the bar's block (novox/hq ADR 0255, ADR 0260). func withShapeCode(seat, kind string, sh *Shape) { if seat == BarSeat && kind == BarKindBlock { sh.Check, sh.Reads = barBlockProblems, barBlockReads } }