diff --git a/src/source_dialog.cpp b/src/source_dialog.cpp index 5fa8505..0111f65 100644 --- a/src/source_dialog.cpp +++ b/src/source_dialog.cpp @@ -60,6 +60,10 @@ struct Chooser { GtkWidget* form_group = nullptr; GtkWidget* form_iface = nullptr; GtkWidget* form_connect = nullptr; + /// `UDPSCOPE_PROBE_PICK_MS`'s timeout, kept so a pick made before it fires can cancel it. The + /// probe clears it when it runs: `g_source_remove()` on an id whose source already returned + /// `G_SOURCE_REMOVE` is a GLib critical, not a no-op. + guint pick_source = 0; }; void chooser_finish(Chooser& c, bool chosen) { @@ -294,6 +298,10 @@ gboolean on_chooser_key(GtkEventControllerKey*, guint keyval, guint, GdkModifier /// `UDPSCOPE_PROBE_FIELDS_MS` is in `ui.cpp`. gboolean pick_probe(gpointer user) { Chooser& c = *static_cast(user); + // Every path below returns `G_SOURCE_REMOVE`, so the source is gone the moment this returns and + // its id is stale. Clear it here rather than on each path: `choose_source()`'s cancel-the-pending + // -probe call would otherwise be a GLib critical on every run that let the probe fire. + c.pick_source = 0; // `UDPSCOPE_PROBE_NEWHOST=host:port` drives the form instead of a row. if (const char* spec = g_getenv("UDPSCOPE_PROBE_NEWHOST")) { @@ -397,16 +405,15 @@ bool choose_source(Config& cfg) { // Keep the timeout id so it can be cancelled if the chooser finishes before it fires. A row // clicked before UDPSCOPE_PROBE_PICK_MS elapses would otherwise leave an armed source on the // default main context pointing at the now-gone stack frame. - guint pick_probe_id = 0; if (const char* env = g_getenv("UDPSCOPE_PROBE_PICK_MS")) { - pick_probe_id = g_timeout_add(static_cast(std::atoi(env)), pick_probe, &c); + c.pick_source = g_timeout_add(static_cast(std::atoi(env)), pick_probe, &c); } c.loop = g_main_loop_new(nullptr, FALSE); g_main_loop_run(c.loop); g_main_loop_unref(c.loop); c.loop = nullptr; - if (pick_probe_id != 0) g_source_remove(pick_probe_id); + if (c.pick_source != 0) g_source_remove(c.pick_source); return c.chosen; }