hugopl / gtk4.cr

GTK4 bindings for Crystal
https://hugopl.github.io/gtk4.cr/
MIT License
99 stars 8 forks source link

GICrystal::ObjectCollectedError when trying to bind to a property from a UiTemplate #69

Open thatcher-gaming opened 3 weeks ago

thatcher-gaming commented 3 weeks ago

Using a property binding from within a .ui file will result in a GICrystal::ObjectCollectedError being raised.

Here's an example:

require "gtk4"

APP_ID = "com.example.test"

@[Gtk::UiTemplate(file: "src/test.ui")]
class TestWindow < Gtk::ApplicationWindow
  include Gtk::WidgetTemplate

  @[GObject::Property]
  property test = "hello"

  def initialize()
    super()
  end
end

app = Gtk::Application.new(APP_ID, Gio::ApplicationFlags::None)

app.activate_signal.connect do
  window = TestWindow.new
  window.application = app

  window.present
end

exit(app.run(ARGV))

test.ui:

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk" version="4.0"/>
  <template class="TestWindow" parent="GtkApplicationWindow">
    <property name="title" bind-source="TestWindow" bind-property="test" bind-flags="sync-create"/>
  </template>
</interface>

Output:

Unhandled exception:  (GICrystal::ObjectCollectedError)
  from src/test.cr:6:1 in '_vfunc_unsafe_get_property'
  from src/test.cr:6:1 in '->'
  from /lib64/libgobject-2.0.so.0 in 'g_object_get_property'
  from /lib64/libgobject-2.0.so.0 in '??'
  from /lib64/libgobject-2.0.so.0 in 'g_object_bind_property_full'
  from /lib64/libgobject-2.0.so.0 in 'g_object_bind_property'
  from /lib64/libgtk-4.so.1 in '??'
  from /lib64/libgtk-4.so.1 in 'gtk_builder_extend_with_template'
  from /lib64/libgtk-4.so.1 in 'gtk_widget_init_template'
  from src/test.cr:7:3 in '_instance_init'
  from src/test.cr:6:1 in '->'
  from /lib64/libgobject-2.0.so.0 in 'g_type_create_instance'
  from /lib64/libgobject-2.0.so.0 in '??'
  from /lib64/libgobject-2.0.so.0 in 'g_object_newv'
  from lib/gi-crystal/src/bindings/g_object/object.cr:607:18 in 'initialize'
  from lib/gi-crystal/src/auto/g_object-2.0/initially_unowned.cr:17:7 in 'initialize'
  from lib/gi-crystal/src/auto/gtk-4.0/widget.cr:26:7 in 'initialize'
  from lib/gi-crystal/src/auto/gtk-4.0/window.cr:35:7 in 'initialize'
  from lib/gi-crystal/src/auto/gtk-4.0/application_window.cr:41:7 in 'initialize'
  from src/test.cr:13:5 in 'initialize'
  from src/test.cr:12:3 in 'new'
  from src/test.cr:20:12 in '->'
  from lib/gi-crystal/src/auto/gio-2.0/application.cr:770:44 in '->'
  from /lib64/libgobject-2.0.so.0 in 'g_closure_invoke'
  from /lib64/libgobject-2.0.so.0 in '??'
  from /lib64/libgobject-2.0.so.0 in '??'
  from /lib64/libgobject-2.0.so.0 in 'g_signal_emit_valist'
  from /lib64/libgobject-2.0.so.0 in 'g_signal_emit'
  from /lib64/libgio-2.0.so.0 in '??'
  from /lib64/libgio-2.0.so.0 in 'g_application_run'
  from lib/gi-crystal/src/auto/gio-2.0/application.cr:544:7 in 'run'
  from src/test.cr:26:6 in '__crystal_main'
  from /usr/share/crystal/src/crystal/main.cr:129:5 in 'main_user_code'
  from /usr/share/crystal/src/crystal/main.cr:115:7 in 'main'
  from /usr/share/crystal/src/crystal/main.cr:141:3 in 'main'
  from /lib64/libc.so.6 in '??'
  from /lib64/libc.so.6 in '__libc_start_main'
  from /home/leah/.cache/crystal/crystal-run-test.tmp in '_start'
  from ???
hugopl commented 3 weeks ago

The problem seems to be the order of creation, so when the C code calls the Crystal property the Crystal object isn't fully initialized yet.

hugopl commented 3 weeks ago

Problem:

GICrystal stores a qdata in each GObject to inform the pointer of the Crystal object for that GObject, so when C code asks for a GObject property defined in Crystal it knows how to get it.

This qdata is set at lib/gi-crystal/src/bindings/g_object/object.cr:609

    def initialize
      @pointer = LibGObject.g_object_newv(self.class.g_type, 0, Pointer(LibGObject::Parameter).null)
      LibGObject.g_object_ref_sink(self) if LibGObject.g_object_is_floating(self) == 1
      LibGObject.g_object_set_qdata(self, GICrystal::INSTANCE_QDATA_KEY, Pointer(Void).new(object_id))
      self._after_init
    end

But... for templates, the GObject property is read before we reach the line 609, it's read when the GObject is created on LibGObject.g_object_newv call. So Crystal code is asked for a property value but it has no means to know the Crystal object instance where it can get this value from.

Possible solution that I did pop up from my head but need more study:

hugopl commented 2 weeks ago

Hi, if you could test https://github.com/hugopl/gi-crystal/pull/153 to double check if the issue was fixed I would appreciate :-)

To use this version of gi-crystal you need to use a shards.override.yml file.

thatcher-gaming commented 1 week ago

@hugopl That does indeed seem to have done the trick. Many thanks for the fix!

hugopl commented 1 week ago

Thanks for testing it! (and also for reporting the issue!)

Good thing about this fix is that now will also be possible to create Crystal deffined widgets from GtkBuild files :-), I didn't tested yet... but in theory it works.

I'll compile Tijolo with this patch and use it some days just to check if it's not doing anything bad before merge it.

This patch and the future patch to have signal connections that doesn't leak objects will grant a good leap in quality for these bindings.