Files
mesh-sdk/go/catalogue/graphical_session.go
T
jschoubben 253bd06970 SDK-9: mesh-sdk takes no new seat-specific name (ADR 0613 §5, design 26 *Contracts modules*)
Every seat constant of mesh-sdk is mesh-contracts' generated name; the four whole seats' (BrokerSeat, CheckerSeat,
asks.Seat, buscompose.VaultSeat) are marked deprecated for their importers. The package walk of mesh-sdk's own check
refuses a string literal naming a defined seat or its subjects, and an exported name given to one, beyond
testdata/seat-names.txt, which lists what stands before the move (module names that equal a seat's among them) and
only shrinks. No new test function.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 12:25:21 +01:00

119 lines
4.5 KiB
Go

package catalogue
import (
"fmt"
"strings"
contractseats "git.novox.be/novox/mesh-contracts/go/seats"
)
// 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 = contractseats.NodeLoginManager
DisplayServerSeat = contractseats.NodeDisplayServer
DisplaySessionSeat = contractseats.NodeDisplaySession
TerminalEmulatorSeat = contractseats.NodeTerminalEmulator
LauncherSeat = contractseats.NodeLauncher
NotifierSeat = contractseats.NodeNotifier
LockScreenSeat = contractseats.NodeLockScreen
ClipboardSeat = contractseats.NodeClipboard
BarSeat = contractseats.NodeBar
CompositorSeat = contractseats.NodeCompositor
SecretServiceSeat = contractseats.NodeSecretService
)
// 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 `<module>.<name>` 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
}
}