feat: the manual is a Mallard set opened in Yelp, not a GResource in the binary

The help was 14 Pango-markup `.txt` bodies in a second GResource, drawn into a paged `GtkWindow`
built in `help_window.cpp`. That made F1 a viewer of ours with no search index, no cross-references
and no translation, and it kept the manual in the *binary* when every other desktop application's is
a document on disk. It is now a real Mallard set:

  help/C/org.udpscope.app/  index.page + 14 topic pages + figures/*.png

and `show_help()` hands `help:org.udpscope.app` to whatever the desktop registered for the `help:`
scheme -- Yelp on GNOME -- through a `GtkUriLauncher`, reporting a launch failure with a
`GtkAlertDialog` rather than a click that silently does nothing. The paged window's builder, the
page table and `help_self_check()`'s markup validation are gone with the resource.

The document identifier is the desktop file's own basename (its desktop-file id), derived in
CMakeLists from `data/org.udpscope.app.desktop.in`, so the same string is the `.desktop` name, the
GApplication id and the install directory at once. The set is installed by a new `install()` rule to
`<datadir>/help/C/org.udpscope.app/` -- where the freedesktop Help System Specification's `help:`
URI resolves `help:<id>` -- so the manual is read from a datadir by another process even though the
binary has no install step. `UDPSCOPE_HELP_NAME` and `UDPSCOPE_HELP_DIR` are compiled in from that
one place.

The 14 pages were migrated fact for fact, but restructured the Mallard way: a `<desc>` per page
(what a search result shows), `<steps>` for procedures, `<terms>` for option lists,
`<gui>`/`<key>`/`<keyseq>` for interface words and keys, `<note>` for the asides, and a topic graph
via `<link type="guide" xref="index#main"/>`. The figures are annotated screenshots produced by
`tools/make-help-shots.sh`, which drives the app headlessly against `tools/mock_udpstreamer.py` and
draws the boxes with ImageMagick; the side panel's box is measured from the app's own `side=NNNx`
dump reading, the header and status bands come from a documented table, and a crop whose standard
deviation is 0 fails the script.

`cmake --build build --target test-help` checks the set the way `test-ui` checks the `.ui` files:
`yelp-check`'s offline subcommands (links, media, orphans, ids) and a `yelp-build html` render.
`yelp-check validate` is deliberately absent -- it fetches the Mallard RelaxNG schema over the
network and fails offline for that reason, not for a fault in the pages.

The `[help]` probe reading changes with the resource it was written for. `parsed == pages` over
in-binary markup cannot exist now, so `help_self_check()` scans `UDPSCOPE_HELP_DIR` and the line
reads `topics=14 index=1 figures=3`; `topics=0 index=0` is the manual missing from the tree, which
is the finding the old count named.

Verified: clean build with the documented three warnings (the help bundle's `-Woverlength-strings`
is gone, so the baseline moves from four to three); test-ui 14/14; test-help clean (the four
subcommands and 15 pages rendered); a temp-prefix install lands 15 `.page` files and 3 figures at
`share/help/C/org.udpscope.app/`; the headless run reads `[help] topics=14 index=1 figures=3 dir=...`
with 0 Gtk-CRITICAL. Yelp's own window could not be exercised on this box -- it starts under Xvfb,
activates its portals and never maps, the same WebKit/portal limitation that stops a grab -- so its
rendering and its search index are unverified here; the content pipeline was (`yelp-build` renders
the whole set).

Docs: the manual's build/install/open story in `build-and-run.md` and README; the warning baseline
in `verification.md`/CLAUDE.md; the help bullets in `invariants-gtk-and-chrome.md`; the `[help]`
line in `probe-readings.md`.
This commit is contained in:
Martino Ferrari committed 2026-10-14 16:49:30 +02:00
1 parent 620473a5be
commit c1460a6977
47 files changed
+1910 -1056

No files matched your search

+27 -18
View File
@@ -75,7 +75,7 @@ Every size it prints is read from a value the frame tick captured while the wind
from the widget: the dump runs from the destroy handler, where the widget pointer is already freed
memory. It used to ask two of them directly, which is where the long-standing "an exit-dump run
emits exactly two `Gtk-CRITICAL`s" baseline came from — and a critical was only the lucky reading.
`UDPSCOPE_PROBE_HELP_MS` builds and destroys three windows of its own before the dump, so by then
`UDPSCOPE_PROBE_HELP_MS` builds and destroys the About and shortcut dialogs before the dump, so by then
the memory had been reused and the same two calls **crashed in the middle of the dump**, which
looks like a truncated log rather than like a segfault in a debug line. The dump now makes no
`gtk_widget_*` call at all and a clean run carries no critical.
@@ -844,9 +844,10 @@ reporting a problem had to retype. And the string it shows is a `std::string`, w
buffer that no one notices until the path matters.
What it cost: an `AdwAboutDialog` is an `AdwDialog` and **not** a `GtkWindow`, so its close path
differs from the help window sitting beside it -- `adw_dialog_close()` and `::closed`, rather than
differs from the shortcut overview's -- `adw_dialog_close()` and `::closed`, rather than
`gtk_window_close()` and `::close-request`. That is the same trap the shortcut overview already
had, and the two now share one `::closed` handler keyed by the pointer it must null. What was
had; each keeps a close handler that nulls its own pointer -- the About dialog on `::closed`, the
overview on `::close-request` -- so neither can be presented again after it is gone. What was
deliberately left out: credits (one author, already named), release notes (there are no releases)
and an issue-tracker URL (the website link reaches the repository, which carries the tracker).
@@ -1657,11 +1658,12 @@ status bar opens its detail **above** the button: below is off the window's bott
bubble whose body is clipped away while its top edge stays at the button is what "the arrow is
detached from the body" looks like.
The **help window** (`F1`) carries the same screenshots as `docs/`: the overview page shows the
main window and a held capture above its text, and the trigger and math pages carry theirs. A
page's images are `GtkPicture`s **with a height request** — a shrinkable one asks for no height at
all inside a box, so without it the page renders its text with a silent gap where the screenshots
should be. The side column and the two panel toggles stay hidden until a
The **manual** (`F1`, the *Documentation* menu row) opens in the desktop help viewer — Yelp,
through the freedesktop `help:` URI — rather than in a window of udpscope's own: it is a Mallard
set installed under `<datadir>/help/C/org.udpscope.app/`, not a scroll view compiled into the
binary. Its overview page shows the main window and a held capture, and the trigger and math pages
carry their own figures, all produced by `tools/make-help-shots.sh` and embedded as `<figure>`s. The
side column and the two panel toggles stay hidden until a
CONFIG arrives — there is nothing to configure before the stream describes itself —
and the first CONFIG reveals the signal panel (the trigger panel stays behind its
own toggle).
@@ -2332,7 +2334,7 @@ reaches every widget). The full key table:
| `s` | show / hide the side panel |
| `f` | fit to data (the plot's own key, not an application accelerator) |
| `Ctrl+E` | export visible range |
| `F1` | open the help window |
| `F1` | open the manual (`help:org.udpscope.app`) in the desktop help viewer |
| `Ctrl+/` | open the shortcut overview |
| `Ctrl+Q` | quit |
| `+` / `-` | zoom in / out (both axes) |
@@ -2349,15 +2351,22 @@ The six "Plot mouse" rows (wheel, drag, right-click) carry no accelerator key
syntax has no member for pointer gestures — and put the prose in the `subtitle` field instead,
which `AdwShortcutsItem` renders as free text.
**The 14-page help window** (overflow menu or F1) is a scrollable text view that opens on the
Overview page, so it never comes up blank: the help pages
are compiled into the binary as a GResource (`src/help/udpscope_help.gresource.xml`,
`--c-name=udpscope_help`) for the same reason the icons are — udpscope is run straight out of the
build tree with no install step, so a search path would have to be found at runtime and a missing
file fails silently. The pages are Pango markup (`.txt` bodies), validated at load; a page that
fails validation renders a one-line stand-in so the window always opens rather than crashing.
`UDPSCOPE_PROBE_HELP_MS` verifies that `parsed == pages` and that the `closed ok` line carries
no `Gtk-CRITICAL`.
**The manual** (the *Documentation* overflow-menu row, or `F1`) is a Mallard help set opened in
Yelp through the `help:` URI, *not* a scroll view compiled into the binary. `help/C/org.udpscope.app/`
holds `index.page`, one page per subject, and the annotated screenshots the pages embed; the document
identifier `org.udpscope.app` is the desktop file's own basename, so it is the `.desktop` name, the
GApplication id and the install directory at once. The set is installed by an `install()` rule to
`share/help/C/org.udpscope.app/` — the path the freedesktop Help System Specification's `help:` URI
resolves — and `show_help()` hands `help:org.udpscope.app` to whatever the desktop registered for the
scheme (Yelp on GNOME), reporting a launch failure with an alert rather than failing silently.
`cmake --build build --target test-help` checks the set the way `test-ui` checks the `.ui` files:
`yelp-check`'s offline subcommands (links, media, orphans, ids) and a `yelp-build html` render;
`yelp-check validate` is left out because it fetches the Mallard RelaxNG schema over the network.
`tools/make-help-shots.sh` regenerates the figures by driving the app headlessly against the mock
(GTK4 has to be forced to the Cairo renderer or X11 reads the surface back blank).
`UDPSCOPE_PROBE_HELP_MS` asserts the set is *present in the tree* — its
`[help] topics=… index=… figures=…` reading — rather than parsing pages, since they are files now
and not resources. Mallard is translatable with `itstool`, but only the `C` set is authored here.
**The status bar is two tiers.** The bar itself shows one glance line: the activity badge, the
display state (one word from a fixed set — `connecting`, `live`, `paused`, `armed`, `capturing`,