From 73f2962bab56eb052177b8920257c24e3802c34e Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 9 Oct 2026 12:25:08 +0200 Subject: [PATCH 1/2] picom: the backend and damage tracking are each machine's settings (hq issue 345) A window mapped but left unpainted, or stale after a workspace switch, is a known failure of picom's glx backend with damage tracking on some drivers, and the first suspect for the empty calendar reminder on the workstation. Research 037 already found the backend to depend on the machine's driver. Both become preferences whose defaults are today's (glx, use-damage true), so nothing changes until a machine is given xrender or use-damage false to test it. --- modules/picom/README.md | 17 +++++++++++ .../cmd/picom-tools/manifest_helpers_test.go | 28 ++++++++++------- .../picom/cmd/picom-tools/manifest_test.go | 30 +++++++++++++++++++ modules/picom/cmd/picom-tools/picom_test.go | 2 +- modules/picom/files/picom.conf | 8 ++++- modules/picom/module.json | 15 +++++++++- 6 files changed, 87 insertions(+), 13 deletions(-) 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": { From ca1bd18de6dbb51e0b0c9fcfdd2dabffeddd2a6a Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 9 Oct 2026 12:33:41 +0200 Subject: [PATCH 2/2] picom: keep the configuration trusted, so only the terminal sets the backend (review of #157) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A setting is pasted into picom.conf as it is, so a backend of xrender"; write-pid-path = "… adds an option of the caller's: picom would then overwrite any file the operator can write. With the file trusted, both settings are set at the controller's terminal alone until settings are typed and quoted (hq issue 346). --- modules/picom/README.md | 5 +++-- modules/picom/cmd/picom-tools/manifest_test.go | 7 +++++-- modules/picom/module.json | 1 - 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/picom/README.md b/modules/picom/README.md index 546b7abe..be9a79ba 100644 --- a/modules/picom/README.md +++ b/modules/picom/README.md @@ -26,8 +26,9 @@ the machine's graphics driver (novox/hq research 037): 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 +`xrender` or `use-damage` false is how a machine is set to test that. The file stays trusted, so both are set at the +controller's terminal alone (issue 339): a value is pasted into picom.conf as it is, and a backend of +`xrender"; write-pid-path = "…` would add an option of the caller's (hq issue 346). A changed file is read at the next `picom_restart` or login: the module has no unit to reload. ## Tools diff --git a/modules/picom/cmd/picom-tools/manifest_test.go b/modules/picom/cmd/picom-tools/manifest_test.go index 98b508ad..b82d8ccc 100644 --- a/modules/picom/cmd/picom-tools/manifest_test.go +++ b/modules/picom/cmd/picom-tools/manifest_test.go @@ -76,8 +76,11 @@ func TestTheBackendAndDamageTrackingAreSettingsWithTodaysDefaults(t *testing.T) 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)") + // Trusted (unmarked): a value is pasted into the file as it is, so `xrender"; write-pid-path = "…` + // would add an option of the caller's. Until settings are typed and quoted (hq issue 346), only the + // controller's terminal sets them. + if _, marked := r["trusted"]; marked { + t.Fatal("the file stays trusted: its settings are set at the terminal alone (hq issue 346)") } got := parseRules("picom.conf", withDefaults(c), nil) if got.Global["backend"] != "glx" || got.Global["use-damage"] != "true" || got.Global["vsync"] != "true" { diff --git a/modules/picom/module.json b/modules/picom/module.json index cda363c3..99d95644 100644 --- a/modules/picom/module.json +++ b/modules/picom/module.json @@ -51,7 +51,6 @@ }, { "id": "configuration", - "trusted": false, "type": "file", "path": "${machine:account-home}/.config/picom/picom.conf", "owner": "${machine:account}",