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

+75
View File
@@ -0,0 +1,75 @@
<page xmlns="http://projectmallard.org/1.0/"
type="topic" style="task"
id="source">
<info>
<link type="guide" xref="index#main"/>
<link type="seealso" xref="sessions"/>
<desc>How udpscope is pointed at a source: the command line, the startup chooser, and
opening an exported waveform instead of a live stream.</desc>
<credit type="author copyright">
<name>Martino Ferrari</name>
</credit>
</info>
<title>Choosing a source</title>
<p>A source is a host and a port, optionally reached through a multicast group on a named
interface. Pass it on the command line, or let udpscope ask.</p>
<section id="reach">
<title>How to reach it</title>
<steps>
<item>
<p>Run with <cmd>--server</cmd> and the app connects straight away.</p>
</item>
<item>
<p>Run <em>without</em> <cmd>--server</cmd> and a chooser appears first: one row per
stored <em>configuration</em> (<code>host:port · name</code>, its note, and the
settings it carries), newest first, plus a <gui>New host…</gui> form that takes the
address <em>and</em> the configuration it belongs to — its name and note, the
<gui>buffer size</gui>, and whether time is relative or absolute. A row's pencil
button opens the same form on that configuration, pre-filled, and changes to its name
or address move it (the old file is deleted).</p>
</item>
<item>
<p>The second page, <gui>Waveforms</gui>, opens an <em>exported</em> run instead of a
live source: the files opened before (newest first; one that has since been deleted is
marked <em>missing</em>) and an <gui>Open…</gui> button for a new one. The same file
can be opened straight from the command line with <cmd>--open FILE</cmd>. A waveform
is a source that never arrives — no socket, no acquisition, no starvation notice — and
every instrument works on it: cursors, measurements, math channels and another export.
Only <file>.npy</file> files are read.</p>
</item>
</steps>
<p>The chooser runs before any buffer is allocated and before any socket is opened, so
declining it costs nothing.</p>
</section>
<section id="knowing">
<title>Worth knowing</title>
<list>
<item><p>The list is the sessions that <em>named a signal</em>. A session whose streamer
was down stores empty slot names and is filtered out, which is what keeps the list the
sources you actually configured rather than every port you have ever typed.</p></item>
<item><p>A stored row reconnects the way it was reached. The multicast group and the
interface are written inside the session file, because the file's name
(<file>host_port.toml</file>) cannot carry them.</p></item>
<item><p><cmd>--multicast</cmd> still needs <cmd>--server</cmd>: the CONFIG message is
fetched from the server, not from the group.</p></item>
<item><p>The <gui>×</gui> beside a row deletes that session file. Nothing else is
removed.</p></item>
</list>
</section>
<section id="example">
<title>Example</title>
<p><cmd>udpscope</cmd> with two stored sessions lists
<code>127.0.0.1:8000 plots=1 signals=3</code> and
<code>10.0.0.5:8001 plots=2 signals=4 multicast=239.1.2.3 interface=10.0.0.9</code>.
Activating the first reports <code>[sources] picked 127.0.0.1:8000</code> and continues
into the scope with that session's layout already loaded.</p>
</section>
</page>