Add --renderer for displays with no usable GPU; gate the header reading

GTK4 defaults to Vulkan, and an X11-forwarded display has no device to make a
swapchain from; the plot is a GtkGLArea, so it then asks for a GL context that
is not there either. --renderer cairo is the software preset for that display:
it exports GSK_RENDERER=cairo and LIBGL_ALWAYS_SOFTWARE=1 in main() before
gtk_init()/adw_init(), because both are read when the renderer and the context
are created. --renderer gl is the faster choice when indirect GL works, and
--renderer vulkan makes the default explicit. Nothing is exported, and GTK's
own choice stands, when the option is absent.

The once-a-second header line is now behind UDPSCOPE_PROBE_HEADER, following
the [notrace] rule: its widths are still sampled for the exit dump, but the
line is detail behind that reading rather than output every run prints.
UDPSCOPE_PROBE_RENDERER prints the exported pair before GTK starts and the
GskRenderer GTK actually built.

README, the manual's overview and docs/claude record the two symptoms and the
cure.
This commit is contained in:
Martino Ferrari committed 2026-10-28 16:03:05 +01:00
1 parent ac054a30c9
commit fcf268d249
9 files changed
+151 -7

No files matched your search

+6
View File
@@ -103,6 +103,12 @@ count to *start* with, 1..`RowSet::kMaxRows`; the layout editor owns it afterwar
the value is required, and the popover button uses 1000 when the flag was absent).
The flag is **not** persisted in the session file: persistence of the enable state
belongs with `todo.md` item 3, alongside the relative-time toggle.
`--renderer cairo|gl|vulkan` exports `GSK_RENDERER` **at the top of `main()`** —
before `choose_source()`'s `adw_init()` and `run_ui()`'s, because GSK reads the variable when
it creates its renderer and setting it later is a silent no-op. `cairo` is the software preset
and also exports `LIBGL_ALWAYS_SOFTWARE=1`: the plot is a `GtkGLArea`, so a display with no
usable GPU fails twice, and this is the pair `tools/make-help-shots.sh` already sets. Nothing
is exported when the option is absent, so GTK's own choice stands.
`tools/mock_udpstreamer.py` has waveform-injection options used by the probes:
+16 -2
View File
@@ -450,11 +450,25 @@ Two lines of the self-check output are assertions in disguise:
floor are the two refusals, and because the probe emits `::clicked` straight at the
widget it reaches the handler even when the button is insensitive, so `add above 0` there
must leave `rows=` and `traces=` untouched rather than growing a ninth row.
- `header: window_button_w=W bar_w=B` (stderr, once a second) — **`W` must be
- `[udpscope] header: window_button_w=W bar_w=B` (stderr, once a second, **only when
`UDPSCOPE_PROBE_HEADER` is set**) — **`W` must be
a small constant and must not track `B`**: the window control is a header-bar child,
and an expanding one is measured into the title's centre box, so it stretched from the
label to the title (516 px at a 1270 px bar before the fix, 121 px after). A `W` that
grows with the window width is that bug back.
grows with the window width is that bug back. The widths are still sampled every second
in every run, because the exit dump's `header:` line reads them; only the line is behind
the variable, since a reading printed on every run is a log flood (the `[notrace]` rule).
An unallocated window reads `0` for both, which is the reading for "the toplevel never
got a size", not for a layout drift.
- `[renderer] option=NAME GSK_RENDERER=V LIBGL_ALWAYS_SOFTWARE=V` (stderr, at startup,
before GTK initialises; only when `UDPSCOPE_PROBE_RENDERER` is set) — what `--renderer`
exported, printed from `main()` so the reading is of the environment a pre-init GTK will
see. `option=(unset)` with both variables `(unset)` is the default and the assertion that
the flag changes nothing unless asked.
- `[renderer] realized=TYPE` (stderr, once, from the first tick that finds a window renderer;
same probe) — the class GTK actually built (`GskCairoRenderer` for `--renderer cairo`),
which is the half of the claim the export alone cannot make. It is null until the window is
realized, so a tick that finds none tries again rather than printing a false reading.
- `toolbar icons: trigger=NAME(ok) events=... pause=... cursors=... sidebar=... menu=...` (in the
exit dump, beside `panel controls:`) — the icon name every toolbar button
carries, with `gtk_icon_theme_has_icon()`'s verdict. **Every entry must read `(ok)`.** Three
+11
View File
@@ -155,6 +155,17 @@ precedent, not an exception.
(the frame's ink is the image) and would otherwise be counted as well. The counter itself is in
every build; see [probe-readings.md](probe-readings.md).
- `UDPSCOPE_PROBE_HEADER` (no value; **not** a scheduled probe) — the once-a-second
`[udpscope] header: window_button_w=… bar_w=…` line. The widths are sampled either way (the
exit dump reads them); the line is behind this variable because it is detail behind that
reading, not a line every run should print. An unallocated window reads `0` for both.
- `UDPSCOPE_PROBE_RENDERER` (no value; **not** a scheduled probe) — the renderer pair
`--renderer` should have exported, printed from `main()` before GTK initialises
(`[renderer] option=… GSK_RENDERER=… LIBGL_ALWAYS_SOFTWARE=…`), then the renderer GTK
built (`[renderer] realized=…`) once the window has one. Run it with `--renderer cairo` to
check the option reaches the environment before `gtk_init()`/`adw_init()`; run it without
the option to check the default is untouched.
- `UDPSCOPE_PROBE_TRIGRENUM_MS` — the trigger's capture must survive a **renumbering of its own
channel**. It sets the reported geometry through the real widgets (1 s window, 10 % pre share,
hold-off from `--holdoff`) on the mock's *square* alone, polls for a capture to open, and then