diff --git a/modules/picom/README.md b/modules/picom/README.md index 827215ff..546b7abe 100644 --- a/modules/picom/README.md +++ b/modules/picom/README.md @@ -13,6 +13,23 @@ The X compositor as a module (novox/hq ADR 0208, research 026). a process has one starter. Its package's autostart entry is that starter where there is one, and the `xinitrc` slot where there is none. +## Settings + +Two preferences, each a machine's own (the per-node layer of `settings`), because the right values follow +the machine's graphics driver (novox/hq research 037): + +| setting | default | what it changes | +|---|---|---| +| `backend` | `glx` | how picom draws: `glx` on the graphics card, `xrender` on the CPU | +| `use-damage` | `true` | `true` repaints only what changed (picom's own default); `false` repaints the whole screen each frame | + +The defaults are what every machine ran before, so the file filled with them changes nothing. They exist +for hq issue 345: a window that is mapped but shows nothing (a calendar reminder) or stays stale after a +workspace switch (a chat window) is a known failure of glx with damage tracking on some drivers, and +`xrender` or `use-damage` false is how a machine is set to test that. The file is `"trusted": false`: only +the operator's own compositor reads it, so a verb may set these (issue 339). A changed file is read at +the next `picom_restart` or login: the module has no unit to reload. + ## Tools Served by the node's runtime as the operator account (ADR 0175). The tools find the operator's X diff --git a/modules/picom/cmd/picom-tools/manifest_helpers_test.go b/modules/picom/cmd/picom-tools/manifest_helpers_test.go index d4fb76d9..a098c63c 100644 --- a/modules/picom/cmd/picom-tools/manifest_helpers_test.go +++ b/modules/picom/cmd/picom-tools/manifest_helpers_test.go @@ -12,21 +12,29 @@ import ( // every desktop module that carries it. type manifest struct { - Module string `json:"module"` - Version string `json:"version"` - Capabilities []string `json:"capabilities"` - Requires []string `json:"requires"` - Claims []claim `json:"claims"` - Seats []any `json:"seats"` - Tools []string `json:"tools"` - Environment *environment `json:"environment"` - Shell []shellCode `json:"shell"` - Resources []map[string]any `json:"resources"` + Module string `json:"module"` + Version string `json:"version"` + Capabilities []string `json:"capabilities"` + Requires []string `json:"requires"` + Claims []claim `json:"claims"` + Seats []any `json:"seats"` + Tools []string `json:"tools"` + Settings map[string]setting `json:"settings"` + Environment *environment `json:"environment"` + Shell []shellCode `json:"shell"` + Resources []map[string]any `json:"resources"` Build struct { Artifacts []map[string]any `json:"artifacts"` } `json:"build"` } +// setting is one key's default, as the definition gives it (novox/hq ADR 0262). +type setting struct { + Kind string `json:"kind"` + Default any `json:"default"` + Why string `json:"why"` +} + type claim struct { Name string `json:"name"` Scope string `json:"scope"` diff --git a/modules/picom/cmd/picom-tools/manifest_test.go b/modules/picom/cmd/picom-tools/manifest_test.go index 6f67e50b..98b508ad 100644 --- a/modules/picom/cmd/picom-tools/manifest_test.go +++ b/modules/picom/cmd/picom-tools/manifest_test.go @@ -59,3 +59,33 @@ func TestTheToolsAgreeWithTheManifest(t *testing.T) { checkTheToolsAgree(t, m) checkNoSecretsOrInstallationNames(t) } + +// The backend and damage tracking are each machine's (novox/hq research 037: the backend depends on the +// machine's graphics driver; issue 345: a window left unpainted under glx is the first suspect). Both +// are settings whose defaults are what every machine ran before, so the file filled with them is today's. +func TestTheBackendAndDamageTrackingAreSettingsWithTodaysDefaults(t *testing.T) { + m := readManifest(t) + b, d := m.Settings["backend"], m.Settings["use-damage"] + if b.Kind != "preference" || b.Default != "glx" || b.Why == "" || d.Kind != "preference" || d.Default != true || d.Why == "" { + t.Fatalf("settings: %+v", m.Settings) + } + r := m.resource(t, "configuration") + c := r["content"].(string) + for _, want := range []string{`backend = "${setting:backend}";`, "use-damage = ${setting:use-damage};"} { + if !strings.Contains(c, want) { + t.Errorf("the file asks for %q", want) + } + } + if r["trusted"] != false { + t.Fatal("only the operator's own compositor reads the file: trusted false, so a verb may set a machine's backend (issue 339)") + } + got := parseRules("picom.conf", withDefaults(c), nil) + if got.Global["backend"] != "glx" || got.Global["use-damage"] != "true" || got.Global["vsync"] != "true" { + t.Fatalf("filled with the defaults: %v", got.Global) + } +} + +// withDefaults fills the file's settings with their defaults, as a machine without its own values gets it. +func withDefaults(c string) string { + return strings.NewReplacer("${setting:backend}", "glx", "${setting:use-damage}", "true").Replace(c) +} diff --git a/modules/picom/cmd/picom-tools/picom_test.go b/modules/picom/cmd/picom-tools/picom_test.go index 102ad3ea..37061bff 100644 --- a/modules/picom/cmd/picom-tools/picom_test.go +++ b/modules/picom/cmd/picom-tools/picom_test.go @@ -16,7 +16,7 @@ func TestTheModulesOwnConfigurationReadsAsRulesInOrder(t *testing.T) { if err != nil { t.Fatal(err) } - r := parseRules("picom.conf", string(raw), nil) + r := parseRules("picom.conf", withDefaults(string(raw)), nil) if r.Global["backend"] != "glx" || r.Global["vsync"] != "true" || r.Global["shadow"] != "false" { t.Fatalf("global options: %v", r.Global) } diff --git a/modules/picom/files/picom.conf b/modules/picom/files/picom.conf index efe5ff2c..1c49876f 100644 --- a/modules/picom/files/picom.conf +++ b/modules/picom/files/picom.conf @@ -2,7 +2,13 @@ # replaced at every push. Adopted from the two workstations' file of 2026-10-04 and moved to # picom's window rules, which supersede opacity-rule, inactive-opacity and inactive-dim. -backend = "glx"; +# The backend and damage tracking are each machine's settings (backend, use-damage; novox/hq research +# 037, issue 345): the right ones depend on the machine's graphics driver. The defaults are what every +# machine ran before: glx, and picom's own default of repainting only what changed. xrender, or +# use-damage false (repaint the whole screen each frame), is what to try on a machine where a window is +# left unpainted or stale. +backend = "${setting:backend}"; +use-damage = ${setting:use-damage}; vsync = true; fading = true; diff --git a/modules/picom/module.json b/modules/picom/module.json index e7dc836a..cda363c3 100644 --- a/modules/picom/module.json +++ b/modules/picom/module.json @@ -13,6 +13,18 @@ "scope": "node" } ], + "settings": { + "backend": { + "kind": "preference", + "default": "glx", + "why": "glx draws with the graphics card and has served every machine so far; xrender draws on the CPU and is what to try where glx leaves a window unpainted or stale on the machine's driver (novox/hq research 037, issue 345)" + }, + "use-damage": { + "kind": "preference", + "default": true, + "why": "true repaints only what changed, picom's own default; false repaints the whole screen each frame, which costs work and cures a window left stale or unpainted on some drivers (novox/hq issue 345)" + } + }, "tools": [ "picom_restart", "picom_rules", @@ -39,11 +51,12 @@ }, { "id": "configuration", + "trusted": false, "type": "file", "path": "${machine:account-home}/.config/picom/picom.conf", "owner": "${machine:account}", "mode": "0644", - "content": "# picom, the X compositor (module picom, novox/hq ADR 0208). Owned by the mesh: this file is\n# replaced at every push. Adopted from the two workstations' file of 2026-10-04 and moved to\n# picom's window rules, which supersede opacity-rule, inactive-opacity and inactive-dim.\n\nbackend = \"glx\";\nvsync = true;\n\nfading = true;\nfade-in-step = 0.05;\nfade-out-step = 0.05;\n\n# Tiled windows hide their shadows and corners, so neither is drawn.\nshadow = false;\ncorner-radius = 0;\nblur-method = \"none\";\n\n# \"Focused\" is i3's _NET_ACTIVE_WINDOW, not X focus events: under i3 those do not reliably reach\n# the toplevel picom tracks, and a terminal then never lifts to its focused opacity.\nuse-ewmh-active-win = true;\n\n# Window rules are applied in order, and a later match wins. Only terminals are translucent:\n# nothing else on screen (browsers, video, games) is touched. A terminal is matched by its class;\n# the node's terminal emulator today is xterm (class XTerm). A window's own opacity property is\n# used wherever no rule sets one, which is what the window-opacity tool sets.\nrules = (\n { match = \"window_type = 'tooltip'\"; fade = false; opacity = 0.95; },\n { match = \"window_type = 'dock' || window_type = 'desktop'\"; fade = false; },\n { match = \"class_g = 'XTerm' && focused\"; opacity = 0.95; },\n { match = \"class_g = 'XTerm' && !focused\"; opacity = 0.75; },\n # A full-screen window is opaque, a terminal included: a video or a game is never see-through.\n { match = \"fullscreen\"; opacity = 1; }\n);\n" + "content": "# picom, the X compositor (module picom, novox/hq ADR 0208). Owned by the mesh: this file is\n# replaced at every push. Adopted from the two workstations' file of 2026-10-04 and moved to\n# picom's window rules, which supersede opacity-rule, inactive-opacity and inactive-dim.\n\n# The backend and damage tracking are each machine's settings (backend, use-damage; novox/hq research\n# 037, issue 345): the right ones depend on the machine's graphics driver. The defaults are what every\n# machine ran before: glx, and picom's own default of repainting only what changed. xrender, or\n# use-damage false (repaint the whole screen each frame), is what to try on a machine where a window is\n# left unpainted or stale.\nbackend = \"${setting:backend}\";\nuse-damage = ${setting:use-damage};\nvsync = true;\n\nfading = true;\nfade-in-step = 0.05;\nfade-out-step = 0.05;\n\n# Tiled windows hide their shadows and corners, so neither is drawn.\nshadow = false;\ncorner-radius = 0;\nblur-method = \"none\";\n\n# \"Focused\" is i3's _NET_ACTIVE_WINDOW, not X focus events: under i3 those do not reliably reach\n# the toplevel picom tracks, and a terminal then never lifts to its focused opacity.\nuse-ewmh-active-win = true;\n\n# Window rules are applied in order, and a later match wins. Only terminals are translucent:\n# nothing else on screen (browsers, video, games) is touched. A terminal is matched by its class;\n# the node's terminal emulator today is xterm (class XTerm). A window's own opacity property is\n# used wherever no rule sets one, which is what the window-opacity tool sets.\nrules = (\n { match = \"window_type = 'tooltip'\"; fade = false; opacity = 0.95; },\n { match = \"window_type = 'dock' || window_type = 'desktop'\"; fade = false; },\n { match = \"class_g = 'XTerm' && focused\"; opacity = 0.95; },\n { match = \"class_g = 'XTerm' && !focused\"; opacity = 0.75; },\n # A full-screen window is opaque, a terminal included: a video or a game is never see-through.\n { match = \"fullscreen\"; opacity = 1; }\n);\n" } ], "build": {