xorg: the display server holds node-display-server and writes the session's start (hq ADR 0208)
The X server, its start and its tools as one module. It provides x11-display with the machine's reach, gated by the host's seat capability. It writes a block at the start of ~/.xinitrc: the account's environment, an explicit import into the user manager, the mesh's X resources merged without cpp, autorandr, the xinitrc slots, ~/.xinitrc.local, and the session's exec from the last slot. Its Go tools serve the seat's displays and layout (autorandr profiles keyed by EDID), and set-mode, primary, dpi, input devices and settings, keyboard, a screenshot (xwd decoded in Go) and the server's log. internal/desktop is how every desktop tool finds the operator's session from the runtime, which has none: from the session's own processes, reading only its words, confirmed with logind.
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
package desktop
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Args reads a tool's arguments as JSON decoded them: strings, float64 numbers, booleans.
|
||||
type Args map[string]any
|
||||
|
||||
// Text is a required string, trimmed.
|
||||
func (a Args) Text(name string) (string, error) {
|
||||
v, ok := a[name].(string)
|
||||
if !ok || strings.TrimSpace(v) == "" {
|
||||
return "", fmt.Errorf("%s is required, as text", name)
|
||||
}
|
||||
return strings.TrimSpace(v), nil
|
||||
}
|
||||
|
||||
// Opt is an optional string, trimmed, or def.
|
||||
func (a Args) Opt(name, def string) string {
|
||||
if v, ok := a[name].(string); ok && strings.TrimSpace(v) != "" {
|
||||
return strings.TrimSpace(v)
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// Has is whether the caller gave the argument at all.
|
||||
func (a Args) Has(name string) bool {
|
||||
v, ok := a[name]
|
||||
return ok && v != nil
|
||||
}
|
||||
|
||||
// Bool is an optional boolean: its value, and whether it was given.
|
||||
func (a Args) Bool(name string) (bool, bool, error) {
|
||||
v, ok := a[name]
|
||||
if !ok || v == nil {
|
||||
return false, false, nil
|
||||
}
|
||||
b, isBool := v.(bool)
|
||||
if !isBool {
|
||||
return false, false, fmt.Errorf("%s is true or false", name)
|
||||
}
|
||||
return b, true, nil
|
||||
}
|
||||
|
||||
// Number is an optional number: its value, and whether it was given.
|
||||
func (a Args) Number(name string) (float64, bool, error) {
|
||||
v, ok := a[name]
|
||||
if !ok || v == nil {
|
||||
return 0, false, nil
|
||||
}
|
||||
f, isNum := v.(float64)
|
||||
if !isNum || math.IsNaN(f) || math.IsInf(f, 0) {
|
||||
return 0, false, fmt.Errorf("%s is a number", name)
|
||||
}
|
||||
return f, true, nil
|
||||
}
|
||||
|
||||
// Whole is an optional whole number within [lo, hi], or def.
|
||||
func (a Args) Whole(name string, def, lo, hi int) (int, error) {
|
||||
f, given, err := a.Number(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !given {
|
||||
return def, nil
|
||||
}
|
||||
if f != math.Trunc(f) || f < float64(lo) || f > float64(hi) {
|
||||
return 0, fmt.Errorf("%s is a whole number from %d to %d", name, lo, hi)
|
||||
}
|
||||
return int(f), nil
|
||||
}
|
||||
|
||||
// OneOf is an optional string that must be one of choices, or def.
|
||||
func (a Args) OneOf(name, def string, choices ...string) (string, error) {
|
||||
v := a.Opt(name, def)
|
||||
for _, c := range choices {
|
||||
if v == c {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("%s is one of %s", name, strings.Join(choices, ", "))
|
||||
}
|
||||
|
||||
// Strings is an optional list of strings.
|
||||
func (a Args) Strings(name string) ([]string, error) {
|
||||
v, ok := a[name]
|
||||
if !ok || v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
list, isList := v.([]any)
|
||||
if !isList {
|
||||
return nil, fmt.Errorf("%s is a list of text", name)
|
||||
}
|
||||
out := make([]string, 0, len(list))
|
||||
for _, x := range list {
|
||||
s, isText := x.(string)
|
||||
if !isText {
|
||||
return nil, fmt.Errorf("%s is a list of text", name)
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Home is the operator account's home: the runtime's word for it, else this process's.
|
||||
func Home() string {
|
||||
if h := os.Getenv("MESH_OPERATOR_HOME"); h != "" {
|
||||
return h
|
||||
}
|
||||
if h, err := os.UserHomeDir(); err == nil {
|
||||
return h
|
||||
}
|
||||
return "/"
|
||||
}
|
||||
|
||||
// InHome resolves a path the caller gave: `~/x` and a relative path are under the home. A path
|
||||
// that leaves the home through `..` is refused, so a tool that writes never writes outside it.
|
||||
func InHome(path string) (string, error) {
|
||||
home := Home()
|
||||
switch {
|
||||
case path == "~":
|
||||
path = home
|
||||
case strings.HasPrefix(path, "~/"):
|
||||
path = filepath.Join(home, path[2:])
|
||||
case !filepath.IsAbs(path):
|
||||
path = filepath.Join(home, path)
|
||||
}
|
||||
path = filepath.Clean(path)
|
||||
if path != home && !strings.HasPrefix(path, home+string(filepath.Separator)) {
|
||||
return "", fmt.Errorf("%s is outside the account's home", path)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// Schema builds a tool's input schema from property descriptions; required names those that must
|
||||
// be given. A property is a string unless its description object says otherwise.
|
||||
func Schema(props map[string]any, required ...string) map[string]any {
|
||||
s := map[string]any{"type": "object", "properties": props}
|
||||
if len(required) > 0 {
|
||||
s["required"] = required
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Str, Num, Flag, List and Enum describe one property.
|
||||
func Str(desc string) map[string]any { return map[string]any{"type": "string", "description": desc} }
|
||||
func Num(desc string) map[string]any { return map[string]any{"type": "number", "description": desc} }
|
||||
func Int(desc string) map[string]any { return map[string]any{"type": "integer", "description": desc} }
|
||||
func Flag(desc string) map[string]any { return map[string]any{"type": "boolean", "description": desc} }
|
||||
func List(desc string) map[string]any {
|
||||
return map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": desc}
|
||||
}
|
||||
func Enum(desc string, values ...string) map[string]any {
|
||||
return map[string]any{"type": "string", "enum": values, "description": desc}
|
||||
}
|
||||
Reference in New Issue
Block a user