help: a tinier (i), hints beside their controls, and the tour starting in the chooser
The contextual help becomes the affordance the ask names: a plain (i) (`dialog-information-symbolic`) at **16x16**, and one that sits beside the control it explains rather than only in a pane's heading. - The mark: `GtkMenuButton` ignores `min-width` on the widget and a size request, so the size is made in `install_css()` on all three nodes (outer `menubutton`, its `button` subnode, the glyph). Measured 16x16 against the default 34x34; the dump prints it as `hint=WxH`, measured with `gtk_widget_compute_bounds()` on the first *visible* hint. - More sites: `events_record` (the "record events" switch in the trigger panel), `trigger_advanced` (the pre-trigger share row), `signals_config` (the signals pane's hint line, wrapped in a box so a hint can join it). Hints join as siblings, so none can cover a switch, pencil or dropdown. - The source chooser gets its own layer -- `chooser_sources` (a new heading above the saved-source list) and `chooser_waveform` (the waveforms action row) -- built with the one `context_help_button()` both windows use and re-synced by its Help menu's toggle. The form's `AdwEntryRow`s cannot host a sibling hint, so none is invented for the host/port fields. - The tour is one implementation over two windows: `ui_impl::TourView` plus a list of `TourStop`s; each window keeps its own table. A walk that passes its last stop calls `on_done`, and the chooser's sets `Config::tour_pending`, so a first run is one tour that crosses from the chooser into the app. The chooser's tour auto-starts from `settings.tour_seen` exactly as the window's does, and its "Guided tour" row now starts *it* instead of only requesting the window's. - Probes: `UDPSCOPE_PROBE_CHOOSER_TOUR=1` / `UDPSCOPE_PROBE_CHOOSER_TOUR_MS`, the hint size in the `help layer:`/`[helpui] contextual on:` lines, and the chooser's hints in its `chooser ui:` line. Docs updated in invariants-gtk-and-chrome.md, probe-readings.md and verification.md.
This commit is contained in:
1 parent
395c9caec3
commit
829176248a
14 files changed
+911
-303
No files matched your search
+241
-16
@@ -141,11 +141,40 @@ struct Chooser {
|
||||
GtkWidget* help_tour = nullptr;
|
||||
GtkWidget* help_toggle = nullptr;
|
||||
GtkWidget* help_popover = nullptr;
|
||||
/// The contextual help's "(i)" affordances, one per surface *this* window explains. They are not
|
||||
/// the window's table -- `ui_context_help.cpp` names `App` fields, and there is no `App` here yet
|
||||
/// -- so this window holds its own two rows and joins them with `context_help_button()`, which
|
||||
/// draws the *one* "(i)" button both windows use.
|
||||
GtkWidget* sources_head = nullptr;
|
||||
GtkWidget* waves_actions = nullptr;
|
||||
/// The guided tour of *this* window. It walks the chooser's own widgets and, when the walk
|
||||
/// finishes, hands it on to the application through `Config::tour_pending`. The machinery is the
|
||||
/// shared `ui_impl::TourView`; only the stops are this window's, because they name its widgets.
|
||||
ui_impl::TourView tour;
|
||||
/// The timer that auto-starts this tour on a first run, kept so a chooser that closes first can
|
||||
/// cancel it (`&c` is a stack frame, and a timer that outlives it is the defect this project has
|
||||
/// already paid for once).
|
||||
guint tour_source = 0;
|
||||
/// `UDPSCOPE_PROBE_CHOOSER_TOUR_MS`'s own timer, cancelled the same way.
|
||||
guint tour_probe_source = 0;
|
||||
|
||||
};
|
||||
|
||||
void chooser_finish(Chooser& c, bool chosen) {
|
||||
c.chosen = chosen;
|
||||
// The two timers point into this stack frame (`&c`), so they go before the frame does. Both are
|
||||
// cleared first: `g_source_remove()` on an id whose source already returned is a GLib critical.
|
||||
if (c.tour_source != 0) {
|
||||
g_source_remove(c.tour_source);
|
||||
c.tour_source = 0;
|
||||
}
|
||||
if (c.tour_probe_source != 0) {
|
||||
g_source_remove(c.tour_probe_source);
|
||||
c.tour_probe_source = 0;
|
||||
}
|
||||
// The tour's own widgets go with the window; ending the walk first means the spotlight and the
|
||||
// card are hidden rather than finalized by the destroy while still visible.
|
||||
ui_impl::tour_end(c.tour);
|
||||
// Destroy the form first: any path that reaches here while the form is up -- a row click, the
|
||||
// Escape key -- would otherwise leave a window whose four handlers are bound to a dead stack
|
||||
// frame. `gtk_window_destroy()` rather than `gtk_window_close()`: the pointer is already
|
||||
@@ -176,6 +205,10 @@ void chooser_populate_waves(Chooser& c);
|
||||
void report_pick(const Chooser& c);
|
||||
/// Defined below; the row buttons reach it, and the row builder sits above it.
|
||||
void open_form(Chooser& c, int edit_index);
|
||||
/// The chooser's own help surfaces and its own tour, defined below: the help menu's rows and the
|
||||
/// first-run timer call them, and both need the window's widgets resolved first.
|
||||
void chooser_help_sync(Chooser& c);
|
||||
void chooser_tour_begin(Chooser& c);
|
||||
|
||||
void on_row_activated(GtkListBox*, GtkListBoxRow* row, gpointer user) {
|
||||
Chooser& c = *static_cast<Chooser*>(user);
|
||||
@@ -644,16 +677,18 @@ void build_chooser_help(Chooser& c) {
|
||||
|
||||
c.help_tour = ui_impl::menu_button("Guided tour", nullptr);
|
||||
gtk_widget_set_tooltip_text(c.help_tour,
|
||||
"walk over the plot, the side panel, the trigger, the signals and "
|
||||
"the status line, once the window is up");
|
||||
"a walk over this dialog's own widgets, then over the plot, the side "
|
||||
"panel, the trigger, the signals and the status line once a source "
|
||||
"has been opened");
|
||||
g_signal_connect(c.help_tour, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
|
||||
Chooser& ch = *static_cast<Chooser*>(user);
|
||||
gtk_popover_popdown(GTK_POPOVER(ch.help_popover));
|
||||
// The tour lives on the window the app builds, which does not exist yet, so the
|
||||
// request rides on the `Config` this call was handed and the app starts it once
|
||||
// the window is up -- see `Config::tour_pending`.
|
||||
ch.cfg->tour_pending = true;
|
||||
std::fprintf(stderr, "[sources] chooser help: tour requested\n");
|
||||
// The tour *starts here*, on this window: the chooser is what a first run meets
|
||||
// first and what it has no way of knowing about. When this walk finishes it sets
|
||||
// `Config::tour_pending`, so the application continues it once a source has been
|
||||
// picked -- see `chooser_tour_begin()` and `chooser_tour_done()`.
|
||||
chooser_tour_begin(ch);
|
||||
std::fprintf(stderr, "[sources] chooser help: tour started in the chooser\n");
|
||||
std::fflush(stderr);
|
||||
}),
|
||||
&c);
|
||||
@@ -661,14 +696,14 @@ void build_chooser_help(Chooser& c) {
|
||||
|
||||
// The check *is* the setting, exactly as in the window's menu: the row is stated before the handler
|
||||
// is connected, so a row that opens checked does not write the file while the dialog is still being
|
||||
// built. The chooser cannot re-sync anything (there is no "?" layer here), so the handler only has
|
||||
// to leave the setting where the app will read it.
|
||||
// built. The chooser re-syncs its own "(i)" layer here, the way the window re-syncs the window's.
|
||||
c.help_toggle = ui_impl::menu_toggle("Enable contextual help", nullptr,
|
||||
load_settings().contextual_help);
|
||||
gtk_widget_set_tooltip_text(c.help_toggle,
|
||||
"a small \u201c?\u201d beside each surface of the main window, saying "
|
||||
"what that surface is for");
|
||||
g_signal_connect(c.help_toggle, "toggled", G_CALLBACK(+[](GtkToggleButton* button, gpointer) {
|
||||
"a small \u201c(i)\u201d beside each surface of this dialog and of the "
|
||||
"main window, saying what that surface is for");
|
||||
g_signal_connect(c.help_toggle, "toggled", G_CALLBACK(+[](GtkToggleButton* button, gpointer user) {
|
||||
Chooser& ch = *static_cast<Chooser*>(user);
|
||||
Settings s = load_settings();
|
||||
s.contextual_help = gtk_toggle_button_get_active(button) != FALSE;
|
||||
// Written in full, the way the window's toggle writes it: the keys this row does
|
||||
@@ -678,6 +713,7 @@ void build_chooser_help(Chooser& c) {
|
||||
settings_path().c_str());
|
||||
std::fflush(stderr);
|
||||
}
|
||||
chooser_help_sync(ch);
|
||||
}),
|
||||
&c);
|
||||
gtk_box_append(GTK_BOX(box), c.help_toggle);
|
||||
@@ -686,6 +722,159 @@ void build_chooser_help(Chooser& c) {
|
||||
gtk_menu_button_set_popover(GTK_MENU_BUTTON(c.help_button), c.help_popover);
|
||||
}
|
||||
|
||||
/// The chooser's contextual help: the same "(i)" the window draws, on the two surfaces *this*
|
||||
/// window has a row for -- the list of saved sources and the waveforms page's action row. It is
|
||||
/// called once after the window is built and again by the Help menu's toggle, which is the one place
|
||||
/// the setting can change while the dialog is up.
|
||||
///
|
||||
/// The form's rows are deliberately not a host: an `AdwEntryRow` is not a box and its editing widget
|
||||
/// is internal, so a hint could not join it as a sibling -- it would have to be appended to the whole
|
||||
/// `AdwPreferencesGroup` as a row of its own, which is not a hint *beside* the field but a row under
|
||||
/// it. The connection fields are therefore explained by the group's own `description`, which is
|
||||
/// where libadwaita puts a hint that must survive being typed over.
|
||||
void chooser_help_sync(Chooser& c) {
|
||||
const bool on = load_settings().contextual_help;
|
||||
ui_impl::context_help_button(
|
||||
c.sources_head, "chooser_sources", "The sources you have opened",
|
||||
"One row per saved configuration. Clicking one opens it: it becomes the active configuration "
|
||||
"and the stream starts. The pencil edits a row's name, note, buffer size and clock; the cross "
|
||||
"forgets it and deletes its saved session.",
|
||||
"", on);
|
||||
ui_impl::context_help_button(
|
||||
c.waves_actions, "chooser_waveform", "Opening a recording",
|
||||
"A `.npy` an earlier run exported is opened for viewing and analysis instead of a live source: "
|
||||
"there is no acquisition behind it, so the trigger and the rate counters are not there -- only "
|
||||
"the samples, which can be measured, searched and re-exported.",
|
||||
"", on);
|
||||
}
|
||||
|
||||
/// One stop of the *chooser's* tour. Its targets are this window's widgets, which exist before `App`
|
||||
/// does -- which is the whole reason the stops cannot live in the window's table.
|
||||
struct ChooserStop {
|
||||
GtkWidget* Chooser::* target;
|
||||
const char* title;
|
||||
const char* body;
|
||||
};
|
||||
|
||||
constexpr ChooserStop kChooserStops[] = {
|
||||
{&Chooser::list, "Choosing a source",
|
||||
"Every source you have saved, newest first. Clicking one opens it -- that is the choice this "
|
||||
"dialog exists to make -- and the arrows on a row edit or forget its configuration."},
|
||||
{&Chooser::switcher, "Two kinds of input",
|
||||
"A *source* is a stream to receive; a *waveform* is a recording an earlier run exported. They "
|
||||
"are the two answers to \u201cwhat should be on the screen\u201d, and these are the tabs "
|
||||
"between them."},
|
||||
{&Chooser::new_host, "A source that is not listed",
|
||||
"Opens the form for an address that has no configuration yet: host, port, the settings a source "
|
||||
"can only be given before it is opened, and the name its session will be saved under."},
|
||||
{&Chooser::help_button, "The manual, and this tour",
|
||||
"Documentation opens the manual in the desktop help viewer. This tour walks this window and "
|
||||
"then the application's own window. Contextual help is the switch below it: on, every surface "
|
||||
"carries a small \u201c(i)\u201d saying what it is for."},
|
||||
{&Chooser::prefs_button, "Preferences",
|
||||
"The event cache, relative time and the interface theme. This is the last moment relative time "
|
||||
"can still change this run -- the buffers are planned as soon as a source is picked -- which is "
|
||||
"why the button is here and not only in the window's menu."},
|
||||
};
|
||||
constexpr int kChooserStopCount = static_cast<int>(sizeof(kChooserStops) / sizeof(kChooserStops[0]));
|
||||
|
||||
/// The walk is over: the chooser hands the tour on to the application. This is the second half of the
|
||||
/// two-window tour -- `tour_pending` is the value `main()` owns and hands to both `choose_source()`
|
||||
/// and `run_ui()`, and the window starts its own tour from it once a source has been picked.
|
||||
void chooser_tour_done(void* user) {
|
||||
Chooser& c = *static_cast<Chooser*>(user);
|
||||
c.cfg->tour_pending = true;
|
||||
std::fprintf(stderr,
|
||||
"[sources] chooser tour: finished %d stops -- the window continues it "
|
||||
"(tour_pending=1)\n",
|
||||
kChooserStopCount);
|
||||
std::fflush(stderr);
|
||||
}
|
||||
|
||||
/// "The tour has run" is the same `settings.tour_seen` the window's auto-start reads, written when
|
||||
/// the walk *starts*: a user who skips it has still seen it.
|
||||
void chooser_tour_mark_seen() {
|
||||
Settings s = load_settings();
|
||||
if (s.tour_seen) return;
|
||||
s.tour_seen = true;
|
||||
if (!save_settings(s)) {
|
||||
std::fprintf(stderr, "[sources] tour: could not write %s: it will be offered again\n",
|
||||
settings_path().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void chooser_tour_begin(Chooser& c) {
|
||||
if (c.tour.card == nullptr) return;
|
||||
chooser_tour_mark_seen();
|
||||
// The stops whose target is on screen, in table order, the same filter the window's tour uses: a
|
||||
// surface that is not there is not walked over.
|
||||
std::vector<ui_impl::TourStop> stops;
|
||||
for (int i = 0; i < kChooserStopCount; ++i) {
|
||||
GtkWidget* target = c.*(kChooserStops[i].target);
|
||||
if (target != nullptr && gtk_widget_is_visible(target)) {
|
||||
stops.push_back({target, kChooserStops[i].title, kChooserStops[i].body});
|
||||
}
|
||||
}
|
||||
if (stops.empty()) return;
|
||||
ui_impl::tour_begin(c.tour, std::move(stops), chooser_tour_done, &c);
|
||||
}
|
||||
|
||||
/// The tour is drawn over the *whole* window, header bar included: two of its stops name the header's
|
||||
/// own buttons, and an overlay placed inside the content would give them negative coordinates and
|
||||
/// clamp its hole to the top edge. `AdwWindow`'s content takes the overlay and the toolbar view
|
||||
/// becomes its child -- the builder still owns the tree, so there is no reference bookkeeping.
|
||||
void build_chooser_tour(Chooser& c) {
|
||||
GtkWidget* content = adw_window_get_content(ADW_WINDOW(c.window));
|
||||
if (content == nullptr) return;
|
||||
// The overlay has to *become* the window's content before the toolbar view can be adopted by it:
|
||||
// `gtk_overlay_set_child()` refuses a child that still has a parent, so the two calls are in this
|
||||
// order -- handing the window the overlay first is what unparents the toolbar view. The view is
|
||||
// held across the swap rather than left to whatever the builder's object list keeps: it has no id
|
||||
// for the builder to name it by, and the window's content was its only owner.
|
||||
GtkWidget* overlay = gtk_overlay_new();
|
||||
g_object_ref(content);
|
||||
adw_window_set_content(ADW_WINDOW(c.window), overlay);
|
||||
gtk_overlay_set_child(GTK_OVERLAY(overlay), content);
|
||||
g_object_unref(content);
|
||||
ui_impl::tour_build(c.tour, overlay);
|
||||
}
|
||||
|
||||
/// A first run is the run whose settings file does not say the tour has been offered -- the same key
|
||||
/// the window's own auto-start reads. Delayed 1.5 s for the reason the window's is: the window has to
|
||||
/// be up and laid out before anything points at it.
|
||||
gboolean chooser_tour_autostart(gpointer user) {
|
||||
Chooser& c = *static_cast<Chooser*>(user);
|
||||
c.tour_source = 0;
|
||||
std::fprintf(stderr, "[sources] tour: first run -- auto_started=1 in the chooser\n");
|
||||
std::fflush(stderr);
|
||||
chooser_tour_begin(c);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
/// `UDPSCOPE_PROBE_CHOOSER_TOUR_MS`: the chooser's tour, read and walked the way the window's is --
|
||||
/// where it stands, then every stop, then the handoff. It reports the walk by *stepping* it (the
|
||||
/// button the user presses), so what it reads is what a first run sees, not the state the probe set.
|
||||
gboolean chooser_tour_probe(gpointer user) {
|
||||
Chooser& c = *static_cast<Chooser*>(user);
|
||||
c.tour_probe_source = 0;
|
||||
std::fprintf(stderr, "[sources] chooser tour: visible=%d steps=%d step=%d\n",
|
||||
c.tour.running ? 1 : 0, static_cast<int>(c.tour.plan.size()), c.tour.index);
|
||||
while (c.tour.running) {
|
||||
const int before = c.tour.index;
|
||||
ui_impl::tour_advance(c.tour, 1);
|
||||
if (c.tour.running) {
|
||||
std::fprintf(stderr, "[sources] chooser tour: next -> step=%d/%d\n", c.tour.index,
|
||||
static_cast<int>(c.tour.plan.size()));
|
||||
} else {
|
||||
std::fprintf(stderr, "[sources] chooser tour: Next past stop %d finished the walk\n", before + 1);
|
||||
}
|
||||
}
|
||||
std::fprintf(stderr, "[sources] chooser tour: after finish visible=%d tour_pending=%d\n",
|
||||
c.tour.running ? 1 : 0, c.cfg->tour_pending ? 1 : 0);
|
||||
std::fflush(stderr);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
/// `UDPSCOPE_PROBE_PICK_MS`: activate the first row, the way a click does.
|
||||
/// `UDPSCOPE_PROBE_NEWHOST=host:port` drives the form instead of a row. The crash surface of a
|
||||
/// dialog built, filled and destroyed is as much the point as the values, the same way
|
||||
@@ -706,9 +895,14 @@ void report_chooser_ui(const Chooser& c) {
|
||||
adw_view_stack_page_get_title(page),
|
||||
adw_view_stack_page_get_icon_name(page) != nullptr ? "yes" : "NO");
|
||||
}
|
||||
std::string hint_ids;
|
||||
const int hints = ui_impl::context_help_count_in(c.window, &hint_ids);
|
||||
int hint_w = 0;
|
||||
int hint_h = 0;
|
||||
ui_impl::context_help_size_in(c.window, &hint_w, &hint_h);
|
||||
std::fprintf(stderr,
|
||||
" visible='%s' switcher_wired=%d buttons=[prefs=%d new_host=%d open=%d cancel=%d] "
|
||||
"help=[button=%d docs=%d tour=%d toggle=%d on=%d]\n",
|
||||
"help=[button=%d docs=%d tour=%d toggle=%d on=%d] hints=[%d {%s}] hint=%dx%d\n",
|
||||
adw_view_stack_get_visible_child_name(ADW_VIEW_STACK(c.page_stack)),
|
||||
adw_view_switcher_bar_get_stack(ADW_VIEW_SWITCHER_BAR(c.switcher)) ==
|
||||
ADW_VIEW_STACK(c.page_stack)
|
||||
@@ -721,7 +915,8 @@ void report_chooser_ui(const Chooser& c) {
|
||||
c.help_toggle != nullptr &&
|
||||
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(c.help_toggle))
|
||||
? 1
|
||||
: 0);
|
||||
: 0,
|
||||
hints, hint_ids.c_str(), hint_w, hint_h);
|
||||
std::fflush(stderr);
|
||||
}
|
||||
|
||||
@@ -741,9 +936,18 @@ gboolean pick_probe(gpointer user) {
|
||||
// would assert nothing. The tour is read back out of the `Config` the run will be handed, which is
|
||||
// the carrier; the toggle is read back out of the settings file it writes, since the app is built
|
||||
// from that file and not from the dialog.
|
||||
// `UDPSCOPE_PROBE_CHOOSER_TOUR=1` drives the help menu's Guided tour row the way a click does --
|
||||
// which now starts a tour *in this dialog*, not merely a request for the window's -- then walks it
|
||||
// to its last stop and past it, so the handoff is exercised, and then falls through to the pick
|
||||
// below. The point of the tour entry is that it crosses into the run this chooser starts, so a
|
||||
// probe that stopped here would assert nothing; the carrier is `Config::tour_pending`.
|
||||
if (g_getenv("UDPSCOPE_PROBE_CHOOSER_TOUR") != nullptr && c.help_tour != nullptr) {
|
||||
g_signal_emit_by_name(c.help_tour, "clicked");
|
||||
std::fprintf(stderr, "[sources] chooser help: tour_pending=%d\n", c.cfg->tour_pending ? 1 : 0);
|
||||
std::fprintf(stderr, "[sources] chooser tour: visible=%d steps=%d step=%d\n",
|
||||
c.tour.running ? 1 : 0, static_cast<int>(c.tour.plan.size()), c.tour.index);
|
||||
while (c.tour.running) ui_impl::tour_advance(c.tour, 1);
|
||||
std::fprintf(stderr, "[sources] chooser tour: after finish visible=%d tour_pending=%d\n",
|
||||
c.tour.running ? 1 : 0, c.cfg->tour_pending ? 1 : 0);
|
||||
std::fflush(stderr);
|
||||
}
|
||||
if (g_getenv("UDPSCOPE_PROBE_CHOOSER_HELP") != nullptr && c.help_toggle != nullptr) {
|
||||
@@ -1024,9 +1228,12 @@ bool choose_source(Config& cfg) {
|
||||
c.waves_open = GTK_WIDGET(gtk_builder_get_object(c.ui, "waves_open"));
|
||||
c.cancel_button = GTK_WIDGET(gtk_builder_get_object(c.ui, "cancel_button"));
|
||||
c.help_button = GTK_WIDGET(gtk_builder_get_object(c.ui, "help_menu_button"));
|
||||
c.sources_head = GTK_WIDGET(gtk_builder_get_object(c.ui, "sources_head"));
|
||||
c.waves_actions = GTK_WIDGET(gtk_builder_get_object(c.ui, "waves_actions"));
|
||||
if (c.window == nullptr || c.page_stack == nullptr || c.switcher == nullptr || c.list == nullptr ||
|
||||
c.wave_list == nullptr || c.prefs_button == nullptr || c.new_host == nullptr ||
|
||||
c.waves_open == nullptr || c.cancel_button == nullptr || c.help_button == nullptr) {
|
||||
c.waves_open == nullptr || c.cancel_button == nullptr || c.help_button == nullptr ||
|
||||
c.sources_head == nullptr || c.waves_actions == nullptr) {
|
||||
std::fprintf(stderr, "[sources] chooser ui: source-chooser.ui is missing an id this code reads\n");
|
||||
std::fflush(stderr);
|
||||
return false;
|
||||
@@ -1041,6 +1248,11 @@ bool choose_source(Config& cfg) {
|
||||
ui_impl::install_css();
|
||||
build_chooser_help(c);
|
||||
|
||||
// The chooser's own "(i)" layer and its own tour, both over the widgets the block above resolved.
|
||||
// The tour's overlay wraps the whole window, so it is built before the window is presented.
|
||||
chooser_help_sync(c);
|
||||
build_chooser_tour(c);
|
||||
|
||||
g_signal_connect(c.list, "row-activated", G_CALLBACK(on_row_activated), &c);
|
||||
g_signal_connect(c.wave_list, "row-activated", G_CALLBACK(on_wave_activated), &c);
|
||||
g_signal_connect(c.waves_open, "clicked", G_CALLBACK(on_wave_open_clicked), &c);
|
||||
@@ -1078,6 +1290,19 @@ bool choose_source(Config& cfg) {
|
||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_PICK_MS")) {
|
||||
c.pick_source = g_timeout_add(static_cast<guint>(std::atoi(env)), pick_probe, &c);
|
||||
}
|
||||
// A first run gets the tour here, on this window, before a source is picked: the chooser is the
|
||||
// surface a first run meets first, and the window's own auto-start is unreachable until this call
|
||||
// has returned with a source. 1.5 s is the window's own delay and for its reason -- the window has
|
||||
// to be up and laid out before anything points at it -- and the walk's own finish sets
|
||||
// `tour_pending`, which is how it continues into the application's window.
|
||||
if (!load_settings().tour_seen) {
|
||||
c.tour_source = g_timeout_add(1500, chooser_tour_autostart, &c);
|
||||
}
|
||||
// `UDPSCOPE_PROBE_CHOOSER_TOUR_MS`: schedule the tour probe *after* the auto-start above, so the
|
||||
// reading is of a tour the run itself began rather than one the probe started.
|
||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_CHOOSER_TOUR_MS")) {
|
||||
c.tour_probe_source = g_timeout_add(static_cast<guint>(std::atoi(env)), chooser_tour_probe, &c);
|
||||
}
|
||||
|
||||
c.loop = g_main_loop_new(nullptr, FALSE);
|
||||
g_main_loop_run(c.loop);
|
||||
|
||||
Reference in new issue
Block a user