Let a seat receive blocks as data its holder renders, placed where the machine has the hardware (hq ADR 0255)
A module adding a battery to the bar had to write i3status-rust's TOML, so a second bar could not take its place. node-bar now receives a bar-neutral block: the contributor says what it shows, the holder renders it with its own template, places every bar and place once, and a contribution may name a capability the machine must report. The block is offered: the power module runs on servers without a bar.
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
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
|
||||
@@ -19,7 +24,8 @@ const (
|
||||
)
|
||||
|
||||
// graphicalSessionSeats are the eleven, in the order ADR 0208's table reads, each with the verbs
|
||||
// research 026/05 starts it with. Three have none yet: the bar, the compositor and the secret
|
||||
// research 026/05 starts it with. Three have none yet: the bar (which receives blocks instead,
|
||||
// ADR 0255), the compositor and the secret
|
||||
// service are roles a second holder competes for, and nothing has needed to ask them anything.
|
||||
func graphicalSessionSeats() []Seat {
|
||||
const decided = "novox/hq ADR 0208"
|
||||
@@ -93,7 +99,11 @@ func graphicalSessionSeats() []Seat {
|
||||
{Name: "copy", Description: "Put text on the operator's clipboard.",
|
||||
Input: schema(map[string]string{"text": "the text"}, []string{"text"})},
|
||||
}},
|
||||
{Name: BarSeat, Scope: ScopeNode, Decision: decided},
|
||||
// It receives blocks as data (novox/hq ADR 0255): what a block shows, said for any bar, which
|
||||
// the holder renders in its own grammar. A module that knows something about the machine — the
|
||||
// power module its battery — gives the block; the bar knows no machine's hardware.
|
||||
{Name: BarSeat, Scope: ScopeNode, Decision: decided,
|
||||
Receives: []Receivable{{Kind: BarKindBlock, Comment: "#", Offered: true, Shape: barBlockShape()}}},
|
||||
{Name: CompositorSeat, Scope: ScopeNode, Decision: decided},
|
||||
{Name: SecretServiceSeat, Scope: ScopeNode, Decision: decided},
|
||||
}
|
||||
@@ -107,3 +117,91 @@ func withEnum(s map[string]any, property string, values ...string) map[string]an
|
||||
p["enum"] = values
|
||||
return s
|
||||
}
|
||||
|
||||
// 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}},
|
||||
}
|
||||
|
||||
type barOption struct {
|
||||
kind string // text, or seconds (a whole number from 1 to 86400)
|
||||
required bool
|
||||
}
|
||||
|
||||
// barBlockShape is the shape of a block (novox/hq ADR 0255): which bar, where on it, in what order,
|
||||
// what it shows, and that kind's options.
|
||||
//
|
||||
// - bar: top or bottom;
|
||||
// - place: resources (beside the processor, memory and disks) or status (beside the sound, before the
|
||||
// clock) — the two places every holder keeps for contributed blocks;
|
||||
// - order: 0–99 within the place, 50 when absent;
|
||||
// - shows: battery or command; options: what that kind takes.
|
||||
func barBlockShape() *Shape {
|
||||
return &Shape{
|
||||
Places: []PlaceField{{Field: "bar", Values: []string{"bottom", "top"}},
|
||||
{Field: "place", Values: []string{"resources", "status"}}},
|
||||
Order: "order",
|
||||
Check: barBlockProblems,
|
||||
Examples: []map[string]any{
|
||||
{"bar": "bottom", "place": "status", "shows": "battery"},
|
||||
{"bar": "bottom", "place": "status", "shows": "battery", "options": map[string]any{"device": "BAT0"}},
|
||||
{"bar": "bottom", "place": "status", "shows": "command",
|
||||
"options": map[string]any{"command": "echo \"a line\"", "interval": float64(5)}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user