Each contribution grain was a manifest field and a renderer of its own; a module now contributes
to any seat with a kind that seat receives, the holder places it with
${contribution:<seat>:<kind>}, and the contribution depends on the seat. node-hotkeys is the
first new seat to receive (triggers); the display session receives window-manager config.
110 lines
6.0 KiB
Go
110 lines
6.0 KiB
Go
package catalogue
|
|
|
|
// 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"
|
|
)
|
|
|
|
// 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
|
|
// 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"
|
|
return []Seat{
|
|
{Name: LoginManagerSeat, Scope: ScopeNode, Decision: decided, Serves: []Verb{
|
|
{Name: "sessions", Description: "The sessions the login manager offers on this machine, and which " +
|
|
"one the operator account starts by default.",
|
|
Input: schema(map[string]string{}, nil)},
|
|
}},
|
|
{Name: DisplayServerSeat, Scope: ScopeNode, Decision: decided, Serves: []Verb{
|
|
{Name: "displays", Description: "The monitors connected now, each with its identity, its modes and " +
|
|
"where it is placed; and the layout profile in force, if one matches.",
|
|
Input: schema(map[string]string{}, nil)},
|
|
// Profiles are keyed by the monitors' identities and are the operator's data (ADR 0208 §6).
|
|
{Name: "layout", Description: "The monitor layout profiles, keyed by the connected monitors' " +
|
|
"identities: list them, save the current arrangement under a name, or apply one.",
|
|
Input: withEnum(schema(map[string]string{
|
|
"action": "list, save or apply",
|
|
"name": "the profile to save or apply (save and apply only)",
|
|
}, []string{"action"}), "action", "list", "save", "apply")},
|
|
}},
|
|
// It receives window-manager configuration lines from every other module (novox/hq ADR 0212):
|
|
// bindings, start-up commands, rules — placed by the session's holder, never written into
|
|
// its directory by the contributor.
|
|
{Name: DisplaySessionSeat, Scope: ScopeNode, Decision: decided,
|
|
Receives: []Receivable{{Kind: "config", Comment: "#"}}, Serves: []Verb{
|
|
{Name: "reload", Description: "Reload the session's configuration in place, keeping its windows.",
|
|
Input: schema(map[string]string{}, nil)},
|
|
{Name: "workspaces", Description: "The session's workspaces: each one's name, output, and whether " +
|
|
"it is visible or focused.",
|
|
Input: schema(map[string]string{}, nil)},
|
|
{Name: "windows", Description: "The session's windows: each one's title, class, workspace and " +
|
|
"whether it has focus; narrowed to one workspace when named.",
|
|
Input: schema(map[string]string{"workspace": "one workspace (optional)"}, nil)},
|
|
}},
|
|
{Name: TerminalEmulatorSeat, Scope: ScopeNode, Decision: decided, Serves: []Verb{
|
|
{Name: "open", Description: "Open a terminal window in the operator's session, running a command " +
|
|
"or the login shell, in a directory or the account's home.",
|
|
Input: schema(map[string]string{
|
|
"command": "what to run in it (optional; the login shell when absent)",
|
|
"directory": "where it starts (optional; the account's home when absent)",
|
|
}, nil)},
|
|
}},
|
|
{Name: LauncherSeat, Scope: ScopeNode, Decision: decided, Serves: []Verb{
|
|
{Name: "menu", Description: "Put a menu of choices in front of the operator and answer with the " +
|
|
"one chosen, or nothing when the menu was dismissed — the dmenu-compatible contract.",
|
|
Input: map[string]any{"type": "object", "required": []string{"choices"},
|
|
"properties": map[string]any{
|
|
"choices": map[string]any{"type": "array", "items": map[string]any{"type": "string"},
|
|
"description": "the lines to choose between, in order"},
|
|
"prompt": map[string]any{"type": "string", "description": "what the menu asks (optional)"},
|
|
}}},
|
|
}},
|
|
{Name: NotifierSeat, Scope: ScopeNode, Decision: decided, Serves: []Verb{
|
|
{Name: "send", Description: "Show the operator a notification.",
|
|
Input: withEnum(schema(map[string]string{
|
|
"title": "the notification's summary",
|
|
"body": "its text (optional)",
|
|
"urgency": "low, normal (the default) or critical",
|
|
}, []string{"title"}), "urgency", "low", "normal", "critical")},
|
|
{Name: "history", Description: "The notifications shown lately, newest first.",
|
|
Input: schema(map[string]string{"limit": "how many (optional, default 20)"}, nil)},
|
|
}},
|
|
{Name: LockScreenSeat, Scope: ScopeNode, Decision: decided, Serves: []Verb{
|
|
{Name: "lock", Description: "Lock the operator's session now.",
|
|
Input: schema(map[string]string{}, nil)},
|
|
}},
|
|
{Name: ClipboardSeat, Scope: ScopeNode, Decision: decided, Serves: []Verb{
|
|
{Name: "history", Description: "What the clipboard held lately, newest first.",
|
|
Input: schema(map[string]string{"limit": "how many (optional, default 20)"}, nil)},
|
|
{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},
|
|
{Name: CompositorSeat, Scope: ScopeNode, Decision: decided},
|
|
{Name: SecretServiceSeat, Scope: ScopeNode, Decision: decided},
|
|
}
|
|
}
|
|
|
|
// withEnum narrows one string property of a schema to the values it may take, so a caller is told
|
|
// the choices by the schema rather than by a refusal.
|
|
func withEnum(s map[string]any, property string, values ...string) map[string]any {
|
|
props := s["properties"].(map[string]any)
|
|
p := props[property].(map[string]any)
|
|
p["enum"] = values
|
|
return s
|
|
}
|