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")}, }}, {Name: DisplaySessionSeat, Scope: ScopeNode, Decision: decided, 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 }