help: default the contextual layer off, and carry the help into the chooser

The contextual help was on by default, so a first run met a window with a "?"
beside every surface it had not asked to have explained; `contextual_help` now
defaults to false and the layer is asked for from the Help menu, from the
chooser's new help menu, or with Ctrl+H.

The source chooser runs before `App` exists, which makes it the window a user
who does not know how to start is looking at, so it now carries the same three
help affordances the window's Help view does: Documentation (the manual is
another process and needs nothing of the app), Enable contextual help (a row
whose check *is* the setting, written to the file the app reads a moment
later), and Guided tour. The tour is drawn on the app's own window, so the
chooser can only record the wish: it sets `Config::tour_pending` -- the value
`main()` owns and hands to both `choose_source()` and `run_ui()` -- and
`on_activate()` starts it once the window is up and laid out, through its own
callback so the readings can tell a request from the first-run auto-start.
The rows are the window's own `menu_button()`/`menu_toggle()`; `install_css()`
therefore runs from `choose_source()` (it is idempotent now) so the popover
gets the `.udpscope-menu` rules before it is shown.

Opening the manual could also fail with a message that blamed the handler
whatever was missing. `help_installed_path()` now resolves `help:<id>` the way
the specification does and the failure alert says which of the two it is: with
the set installed it names the viewer, without it it names the identifier, the
expected installed path and the install command. `show_help()` gained an
optional reporter, which `UDPSCOPE_PROBE_HELP_MS` uses for a new reading --
`[help] launch attempted= done= ok= error='' installed=` -- since a successful
launch leaves no window of ours to observe.

Tested: clean --clean-first build, three warnings as documented; test-ui 13/13
and test-help pass; no Gtk-CRITICAL in the help, helpui and chooser runs.
This commit is contained in:
Martino Ferrari committed 2026-10-14 17:22:03 +02:00
1 parent e140642ed0
commit d8a79b96ba
15 files changed
+461 -66

No files matched your search

+37 -4
View File
@@ -4444,16 +4444,33 @@ UiShortcuts ui_shortcut_rows(const char* path) {
return out;
}
/// Prints the manual launch's outcome, which arrives in a callback (the launch is asynchronous and
/// the viewer is another process) -- so the line is written from a timer a moment later rather than
/// from `help_probe`. `done=0` is a launch that never called back, which is a reading of its own.
gboolean help_launch_report(gpointer user) {
App* a = static_cast<App*>(user);
const std::string installed = help_installed_path();
std::fprintf(stderr, "[help] launch attempted=%d done=%d ok=%d error='%s' installed=%s\n",
a->dbg_help_launch_attempted ? 1 : 0, a->dbg_help_launch_done ? 1 : 0,
a->dbg_help_launch_ok ? 1 : 0, a->dbg_help_launch_error.c_str(),
installed.empty() ? "(not found)" : installed.c_str());
std::fflush(stderr);
return G_SOURCE_REMOVE;
}
gboolean help_probe(gpointer user) {
App* a = static_cast<App*>(user);
const HelpReport report = help_self_check();
const std::string session =
layout_path(a->model->config().server, a->model->config().port);
// The manual is not opened here: it lives in another process (Yelp, through the `help:` URI)
// and neither it nor this box can draw a window for it, so there is nothing to open, read
// back or close. `help_self_check()` above is the assertion -- the set is on disk where the
// build says it is. The two AdwDialogs below are still built, read and closed for real.
// The manual is *launched* here and read no further: it opens in another process (Yelp, through
// the `help:` URI) and neither it nor this box can draw a window for it, so there is nothing to
// read back or close. `help_self_check()` above is the assertion on the *set* being on disk where
// the build says it is; the launch is the assertion on the *path*, and its outcome is reported by
// `help_launch_report()` once the launch's callback has run. The failure alert the callback shows
// is deliberately left up -- it is what a user sees, and the probe must not report a failure as a
// silence. The two AdwDialogs below are still built, read and closed for real.
show_about(GTK_WINDOW(a->window), session);
show_shortcuts(GTK_WINDOW(a->window));
// Read while it is open: the icon is the point of the dialog carrying one, and
@@ -4461,6 +4478,18 @@ gboolean help_probe(gpointer user) {
const std::string about_icon = about_icon_name();
close_help_windows();
// The manual's path: on a successful launch no window is left behind, so this cannot be observed
// any other way; `show_help()`'s reporter is the only hook that runs on that path.
a->dbg_help_launch_attempted = true;
a->dbg_help_launch_done = false;
a->dbg_help_launch_ok = false;
a->dbg_help_launch_error.clear();
show_help(GTK_WINDOW(a->window), [a](bool ok, const std::string& error) {
a->dbg_help_launch_done = true;
a->dbg_help_launch_ok = ok;
a->dbg_help_launch_error = error;
});
int accel_count = 0;
int gesture_count = 0;
(void)app_accels(&accel_count);
@@ -4479,6 +4508,10 @@ gboolean help_probe(gpointer user) {
build_info::kCommit, build_info::kTimestamp, config_dir().c_str(),
about_icon.c_str());
std::fflush(stderr);
// The launch's outcome needs its callback to have run; a moment is enough for a local
// `g_app_info_launch_default_for_uri()`, and `done=0` in the line is how a launch that never
// called back reads.
g_timeout_add(600, help_launch_report, a);
return G_SOURCE_REMOVE;
}