GTK4, tests/simple.c program does not terminate when closing window with Nim bindings

I was going to provide some more GTK4 examples.

While the headerbar example was working already well, I thought to start with an old gtk2 non app style example and used /tests/simple.c.

The interesting observation is that the program keeps running when we click the x close symbol of the window.

The reason for this is that the Nim bindings ref the widgets and unref them when they go out of scope. So I tested with plain C examples applying g_object_ref_sink(), as that is what the Nim bindings do.

First I took a plain gtk3 non app style example and added g_object_ref_sink():

// http://zetcode.com/gui/gtk2/firstprograms/
// gcc -o zsimple zsimple.c `pkg-config --libs --cflags gtk+-3.0`
#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  g_object_ref_sink(window); // added by salewski for testing
  gtk_widget_show(window);
  
  g_signal_connect(window, "destroy",
      G_CALLBACK(gtk_main_quit), NULL);  

  gtk_main();

  return 0;
}

Seems to work fine still.

Now with GTK4 test/simple.c and g_object_ref_sink() added:

// https://gitlab.gnome.org/GNOME/gtk/-/blob/master/tests/simple.c
// gcc -Wall simple.c -o simple `pkg-config --cflags --libs gtk4`

#include <gtk/gtk.h>

static void
hello (void)
{
  g_print ("hello world\n");
}

static void
quit_cb (GtkWidget *widget,
         gpointer   data)
{
  gboolean *done = data;
  *done = TRUE;
  g_main_context_wakeup (NULL);
}

int
main (int argc, char *argv[])
{
  GtkWidget *window, *button;
  gboolean done = FALSE;
  gtk_init ();
  window = gtk_window_new ();
  g_object_ref_sink(window); // added by salewski for testing
  gtk_window_set_title (GTK_WINDOW (window), "hello world");
  gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
  g_signal_connect (window, "destroy", G_CALLBACK (quit_cb), &done);
  button = gtk_button_new ();
  gtk_button_set_label (GTK_BUTTON (button), "hello world");
  gtk_widget_set_margin_top (button, 10);
  gtk_widget_set_margin_bottom (button, 10);
  gtk_widget_set_margin_start (button, 10);
  gtk_widget_set_margin_end (button, 10);
  g_signal_connect (button, "clicked", G_CALLBACK (hello), NULL);
  gtk_window_set_child (GTK_WINDOW (window), button);
  gtk_widget_show (window);
  while (!done)
    g_main_context_iteration (NULL, TRUE);
  return 0;
}

For me program does not terminate when I close the window. It seems that the quit_cb() function is not executed at all. And when we think about it, it is not too surprising, as we have ref the window. First question is why it works for the zcode example. Next question for myself is how I can fix it for the Nim bindings. Maybe never ref toplevel GTKWindows? Maybe this issue is not that serious, as we generally use app style and not the old gtk2 style.

Hi,
in my Fortran GTK 4 examples, I often use:

  my_gmainloop = g_main_loop_new(c_null_ptr, FALSE)
  call g_main_loop_run(my_gmainloop)

to launch the loop, and:

    call g_main_loop_quit (my_gmainloop)

in my destroy callback function.

But when possible, I now use GtkApplication, as recommended.

Top level windows are owned by GTK, and they don’t have a floating reference—as they cannot be added to a parent widget. So you should never use g_object_ref_sink() on them.

Additionally, we strongly encourage people to stop porting GTK2 examples to GTK4 line by line, and instead use GtkApplication and GtkApplicationWindow wherever possible. The reason why we dropped gtk_main() and gtk_main_quit() is not because we want people to use GMainContext or GMainLoop directly, but because we want people to use GtkApplication.

1 Like

Top level windows are owned by GTK, and they don’t have a floating reference

Good to know that. I guess there is no hint for that in gobject-introspection? But well, when that concerns only GtkWindow I can fix that manually. GTK4 API docs mention that fact is some way, but the wording is not that clear: “gtk_window_new() does not return a reference to the caller.”

I still wonder why the zcode example with gtk3 does not suffer from that.

and instead use GtkApplication and GtkApplicationWindow wherever possible.

Yes, I do that, nearly all my GTK3 Nim examples do that.

The point is, the few newcommers to Gtk generally start with gtk2 examples they found by google, so I have to provide at least one working old style example for Gtk4.

No, it’s basically impossible to provide it because the reference is removed by the toolkit. Annotations are static, so they cannot cope with dynamic/run-time behaviour. We are not returning a floating reference, but the object has a reference like any other instance returned by g_object_new() when used with a type that does not inherit from GInitiallyUnowned.

In theory, we could annotate all widget constructors with (transfer floating), but language bindings do not typically use the C constructor functions, and they use g_object_new() instead, so it doesn’t help them.

It’s pretty easy to write a 20 lines example that uses GtkApplication instead of gtk_init()/gtk_main(); the examples on the GTK website are all in that ballpark figure, including comments. Additionally, it allows you to write examples that are valid in both GTK3 and GTK4.

1 Like

gtk-application-window-new() has the same behaviour:

https://developer.gnome.org/gtk4/stable/GtkApplicationWindow.html#gtk-application-window-new

When I add g_object_ref_sink(window) to

https://gitlab.gnome.org/GNOME/gtk/-/blob/master/examples/hello-world.c

window = gtk_application_window_new (app);
g_object_ref_sink(window);

the program continues to run when the window is closed. For GTK3 that problem did not occur.

So I have to remove the g_object_ref_sink() call also for gtk-application-window-new() in the Nim bindings.

Increase the reference count of object , and possibly remove the floating reference, if object has a floating reference.

If your bindings don’t have a corresponding g_object_unref when window goes out of scope then you’ve got a ref leak and the object lives forever. In the case of a Gtk[Application]Window that also keeps the application alive

The bindings call g_object_unref(), but at least the default Garbage Collector calls it not immediately when the object goes out of scope, but with some delay. But you are right, with the new deterministic ARC Nim memory management it may work. Unfortunately ARC not yet the default for Nim.

Well, yes, since GtkApplicationWindow is a GtkWindow. And so every GtkWindow sub-class.

You will need a check in your bindings that sinks the floating reference unless the object is-a GtkWindow.

This is generally why all bindings tend to use g_object_new() instead of the C constructor wherever possible.

1 Like

Would this be all right for a non-toplevel window?

It’s worth noting that one probably shouldn’t bind directly to g_object_new() or g_object_new_with_properties(). They are annotated with (skip) because no transfer annotation is suitable - see bug #795025. In brief, for a class that is not a subclass of GInitiallyUnowned, (transfer full) is required and for a class that is a subclass of GInitiallyUnowned, (transfer none) is required. As noted in the discussion about floating references in the GObject docs, binding code should wrap these functions in one that performs a ref-sink on the new object if and only if it is of a subclass of GInitiallyUnowned and gives the following example:

GObject *res = g_object_new_with_properties (gtype,
                                             n_props,
                                             prop_names,
                                             prop_values);

// or: if (g_type_is_a (gtype, G_TYPE_INITIALLY_UNOWNED))
if (G_IS_INITIALLY_UNOWNED (res))
  g_object_ref_sink (res);

return res;

As you will see in the bug referenced above, there was a desire to avoid yet another variant of g_object_new to provide this functionality. I have used the following code to ensure that bindings will work with versions of GLib prior to 2.54:

GObject *
giraffe_g_object_new_with_properties (GType          object_type,
                                      guint          n_properties,
                                      const char    *names[],
                                      const GValue   values[])
{
  GObject *object;

#ifdef GLIB_VERSION_2_54
  object = g_object_new_with_properties (object_type, n_properties, names, values);
#else
  guint i;

  GParameter parameters[n_properties];
  for (i = 0; i < n_properties; i++)
    {
      parameters[i].name = names[i];
      parameters[i].value = values[i];
    }
  object = g_object_newv (object_type, n_properties, parameters);
#endif

  /* ensure we own a reference */
  if (G_IS_INITIALLY_UNOWNED (object))
    g_object_ref_sink (object);

  return object;
}

There is a difference between using them in bindings and having them available via bindings

What’s a “non top level” window? Anything that inherits from GtkWindow is a “top level” window, and you cannot have top level windows that don’t inherit from GtkWindow.

In general, language bindings that instantiate objects via g_object_new() and friends do so at their lowest level entry points, not by using a language trampoline into g_object_new_with_properties(), since language bindings still need to handle GObject and GType internally, typically using some ad hoc C code. The idea that all of GObject ought to be introspectable is kind of a fallacy.

Sorry, I meant a window that isn’t the main application window. I’m just trying to think through the implications of a ‘language binding’ not holding its own reference to a GtkWindow.

That has nothing to do with the behaviour of GtkWindow, though. The fact that all GtkWindow instances do not have an initial floating reference is part of the type, not part of its semantics.

Bindings could use the “destroy” signal (use g_signal_connect_after() so that the handler is invoked at the end of the signal emission chain, to avoid interfering with user handlers) and release their own reference at that point.