diff --git a/src/ui_signals.cpp b/src/ui_signals.cpp index 01b9bc9..994b864 100644 --- a/src/ui_signals.cpp +++ b/src/ui_signals.cpp @@ -24,6 +24,15 @@ void on_signal_switch(GObject* obj, GParamSpec*, gpointer user) { const std::vector& signals = app->model->signals(); if (index < signals.size()) name = signals[index].name; } + // Hold a reference on the emitting widget across the rebuild. `rebuild_signal_panels` destroys + // the list that owns `obj` (this check button's row), dropping the row's reference and leaving + // only GLib's own per-emission ref. `gtk_check_button_set_active` emits both `notify::active` + // and `toggled` in sequence: GLib's ref covers only one emission at a time, so after + // `notify::active` finishes GLib unrefs the instance and, without our extra hold, that is the + // last reference -- and the subsequent `toggled` emission is then into freed memory, which is + // the two `GLib-GObject-CRITICAL` diagnostics. Taking our own ref here and releasing it from + // an idle keeps the instance alive until after `set_active` has emitted everything and returned. + g_object_ref(obj); app->model->set_signal_enabled(index, on); if (!on) { // A trace of a signal nobody acquires draws nothing, and holds the slot a live one could take. @@ -48,6 +57,11 @@ void on_signal_switch(GObject* obj, GParamSpec*, gpointer user) { } // The enable state is part of the session (a signal can be on only as a math channel's source). schedule_layout_save(*app); + // Release the extra reference from an idle so that `set_active`'s full emission chain -- + // `notify::active` and `toggled` -- has completed before the instance can be finalized. + g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, + +[](gpointer) -> gboolean { return G_SOURCE_REMOVE; }, + obj, g_object_unref); } /// The palette entry a signal owns: its position among the *plottable* signals, the