xterm: the terminal holds node-terminal-emulator; its resources through xorg's slot (hq ADR 0208)

It requires x11-display, names itself in TERMINAL, and contributes its X resources
to the xresources slot normal: today's palette and clipboard keys, JetBrainsMono
Nerd Font, 10000 lines of scrollback, all scoped to XTerm* instead of every Xt
program. It owns no file.

The tools are the seat's open, which starts a terminal through the account's
service manager so it outlives the runtime's restarts, and font (in force, what
fontconfig resolves it to, set for new terminals) and colours.
This commit is contained in:
jochen
2026-10-04 13:21:47 +02:00
parent 34829e39fe
commit e4abc6eb88
12 changed files with 1750 additions and 0 deletions
+210
View File
@@ -0,0 +1,210 @@
package main
import (
"context"
"encoding/json"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"xterm/internal/desktop"
)
type manifest struct {
Capabilities []string `json:"capabilities"`
Requires []string `json:"requires"`
Claims []struct {
Name string `json:"name"`
Serves []string `json:"serves"`
} `json:"claims"`
Tools []string `json:"tools"`
Environment struct {
Variables map[string]string `json:"variables"`
} `json:"environment"`
Shell []struct {
For, Slot, Code string
} `json:"shell"`
Resources []map[string]any `json:"resources"`
Build struct {
Artifacts []map[string]any `json:"artifacts"`
} `json:"build"`
}
func readManifest(t *testing.T) manifest {
raw, err := os.ReadFile(filepath.Join("..", "..", "module.json"))
if err != nil {
t.Fatal(err)
}
var m manifest
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
return m
}
func TestItHoldsTheTerminalSeatOnAnXDisplayAndNamesItselfTheTerminal(t *testing.T) {
m := readManifest(t)
if len(m.Claims) != 1 || m.Claims[0].Name != "node-terminal-emulator" || strings.Join(m.Claims[0].Serves, ",") != "open" {
t.Fatalf("%+v", m.Claims)
}
if strings.Join(m.Requires, ",") != "x11-display" || m.Environment.Variables["TERMINAL"] != "xterm" || len(m.Environment.Variables) != 1 {
t.Fatalf("%v %v", m.Requires, m.Environment)
}
served := map[string]bool{}
for _, tool := range tools(xterm{}) {
served[tool.Name] = true
}
if !served["node-terminal-emulator.open"] || len(served) != len(m.Tools)+1 {
t.Fatalf("%v %v", served, m.Tools)
}
for _, n := range m.Tools {
if !served[n] {
t.Errorf("%s", n)
}
}
if len(m.Resources) != 1 || m.Resources[0]["package"] != "xterm" {
t.Fatalf("%v", m.Resources)
}
if a := m.Build.Artifacts[0]; a["binary"] != "xterm-tools" || a["from"] != "cmd/xterm-tools" {
t.Fatalf("a binary not named xterm, so nothing looking for xterm by name finds the tools: %v", a)
}
}
func TestItsResourcesAreOneContributionInTheNormalSlotScopedToXTerm(t *testing.T) {
m := readManifest(t)
if len(m.Shell) != 1 || m.Shell[0].For != "xresources" || m.Shell[0].Slot != "normal" {
t.Fatalf("%+v", m.Shell)
}
if m.Shell[0].Code != Resources() {
t.Fatal("the manifest's resources are not the ones the bundle describes: regenerate the manifest from Resources()")
}
for _, l := range strings.Split(strings.TrimSpace(Resources()), "\n") {
switch {
case strings.HasPrefix(l, "!"), strings.HasPrefix(l, " "):
case strings.HasPrefix(l, "#"):
t.Errorf("merged with -nocpp, a # line is skipped: %q", l)
case !strings.HasPrefix(l, "XTerm*"):
t.Errorf("a resource of another program, or of every one: %q", l)
}
}
if !strings.Contains(Resources(), "XTerm*faceName: JetBrainsMono Nerd Font") || !strings.Contains(Resources(), "copy-selection(CLIPBOARD)") {
t.Fatal("the chosen face and the clipboard keys")
}
}
type fake struct {
ran []string
stdin []string
query string
}
func (f *fake) desk(session bool) desktop.Desk {
return desktop.Desk{
Find: func() (*desktop.Session, error) {
if !session {
return nil, &desktop.NoSession{Reason: "none"}
}
return &desktop.Session{Display: ":1", XAuthority: "/h/.Xauthority", Type: "x11", RuntimeDir: "/run/user/1000", Bus: "unix:path=/run/user/1000/bus"}, nil
},
Run: func(_ context.Context, _ []string, stdin []byte, name string, args ...string) desktop.Result {
f.ran = append(f.ran, strings.Join(append([]string{name}, args...), " "))
f.stdin = append(f.stdin, string(stdin))
switch name {
case "xrdb":
return desktop.Result{Stdout: f.query}
case "fc-list":
if strings.Contains(args[0], "Missing") {
return desktop.Result{}
}
return desktop.Result{Stdout: args[0] + "\n"}
case "fc-match":
return desktop.Result{Stdout: "JetBrainsMono Nerd Font|Regular|/usr/share/fonts/TTF/J.ttf"}
}
return desktop.Result{}
},
}
}
func TestOpenHandsTheTerminalToTheAccountsServiceManagerWithTheSessionsDisplay(t *testing.T) {
f := &fake{}
dir := t.TempDir()
got, err := xterm{d: f.desk(true)}.open(context.Background(), desktop.Args{"directory": dir, "command": "htop", "hold": true, "title": "top"})
if err != nil {
t.Fatal(err)
}
if len(f.ran) != 1 || !strings.HasPrefix(f.ran[0], "systemd-run --user --collect --quiet --unit=mesh-xterm-") {
t.Fatalf("%q", f.ran)
}
for _, want := range []string{"--setenv=DISPLAY=:1", "--setenv=XAUTHORITY=/h/.Xauthority", "-- xterm -title top -hold -e /bin/sh -c", "sh " + dir + " htop"} {
if !strings.Contains(f.ran[0], want) {
t.Errorf("lacks %q: %s", want, f.ran[0])
}
}
if !strings.Contains(asJSON(got), `"unit":"mesh-xterm-`) {
t.Fatalf("%s", asJSON(got))
}
if _, err := (xterm{d: f.desk(true)}).open(context.Background(), desktop.Args{"directory": "/no/such/dir"}); err == nil {
t.Fatal("a directory that is not there")
}
if _, err := (xterm{d: (&fake{}).desk(false)}).open(context.Background(), desktop.Args{}); !desktop.IsNoSession(err) {
t.Fatalf("%v", err)
}
}
func TestTheTerminalsScriptStartsInTheDirectoryAndRunsTheCommandThroughTheShell(t *testing.T) {
dir := t.TempDir()
args := OpenArgs(dir, "pwd > out", "", false)
sh := args[len(args)-6:] // /bin/sh -c script sh dir command
cmd := exec.Command(sh[0], sh[1:]...)
cmd.Env = []string{"SHELL=/bin/sh", "PATH=/usr/bin:/bin", "HOME=" + dir}
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("%v %s", err, out)
}
b, _ := os.ReadFile(filepath.Join(dir, "out"))
if strings.TrimSpace(string(b)) != dir {
t.Fatalf("%q", b)
}
}
func TestFontAndColoursAreReadAsAnXTermSeesThem(t *testing.T) {
q := "*faceName:\tHack Nerd Font\nXTerm*faceName:\tJetBrainsMono Nerd Font\nXTerm*faceSize:\t12\n" +
"XTerm*background:\t#000000\n*foreground:\trgb:a8/a8/a8\nXTerm*color1:\trgb:ff/00/00\n"
if Effective(q, "faceName") != "JetBrainsMono Nerd Font" || Effective(q, "foreground") != "rgb:a8/a8/a8" {
t.Fatal("XTerm's own value outranks the global one; the global one stands alone")
}
for in, want := range map[string]string{"rgb:9e/18/28": "#9e1828", "rgb:f/0/8": "#ff0088", "rgb:ffff/0000/8080": "#ff0080", "#ABCDEF": "#abcdef", "red": "red"} {
if got := Hex(in); got != want {
t.Errorf("%s: %s", in, got)
}
}
f := &fake{query: q}
got, err := xterm{d: f.desk(true)}.colours(context.Background(), desktop.Args{})
if err != nil {
t.Fatal(err)
}
j := asJSON(got)
if !strings.Contains(j, `{"name":"color1","value":"#ff0000","default":"#9e1828","differs":true}`) || !strings.Contains(j, `{"name":"foreground","value":"#a8a8a8","default":"#a8a8a8"}`) {
t.Fatalf("%s", j)
}
if _, err := (xterm{d: f.desk(true)}).font(context.Background(), desktop.Args{"face": "Missing Font"}); err == nil {
t.Fatal("a face that is not installed")
}
if _, err := (xterm{d: f.desk(true)}).font(context.Background(), desktop.Args{"size": 99.0}); err == nil {
t.Fatal("size out of range")
}
f.ran, f.stdin = nil, nil
got, err = xterm{d: f.desk(true)}.font(context.Background(), desktop.Args{"face": "Inter", "size": 13.0})
if err != nil {
t.Fatal(err)
}
if f.stdin[1] != "XTerm*faceName: Inter\nXTerm*faceSize: 13\n" || !strings.Contains(asJSON(got), `"lasts"`) {
t.Fatalf("%q %s", f.stdin, asJSON(got))
}
}
func asJSON(v any) string {
b, _ := json.Marshal(v)
return string(b)
}