How to implement Buildable.do_custom_tag_start in Python?

I want to extend XML UI in Python. I created a class that derives from a widget and Gtk.Buildable. The C API has a function: custom_tag_start that takes arguments: buildable, builder, child, tagname, parser and data; and returns bool. After some searching I found that parser and data are actually return arguments and in Python bindings you only receive arguments up to tagname and you are supposed to return 3-tuple: bool, parser, data.

Now my code is:

class MyWidget(Gtk.Widget, Gtk.Buildable):
    __gtype_name__ = 'MyWidget'

    def do_custom_tag_start(self, builder, child, tagname):
        return True, Gtk.BuildableParser(), None

My function gets called but I get the following error:

WARNING **: 15:13:57.002: (gi/pygi-basictype.c:78):marshal_from_py_void: runtime check failed: (arg_cache->transfer == GI_TRANSFER_NOTHING)

In addition to that:

  • If I put anything but None or integer in the last return argument I get an error.
  • Skipping the 3rd return argument altogether causes a segfault.
  • Putting anything but BuildableParser in the 2nd argument causes a warning, but otherwise nothing changes.

Subclassing BuildableParser doesn’t seem to do anything. (Tried start_element and do_start_element.)

How to move forward?

You can’t.

The sub-parser API in GtkBuildable relies of GMarkupParser, which is a very C API, and has no proper introspection, which means it cannot be properly consumed by language bindings.

Is there any way to receive raw XML in Python? I will parse it myself with lxml.

Where is the XML stored? as GResource? or a simple file on the disk?

If GResource, you can use

success, raw_xml_data, etag = Gio.File.new_for_uri("resource:///path/to/file.ui").load_contents()

to get the raw XML data.

I want to receive the XML subtree of the custom element encountered by GtkBuildableParser. I can see GtkBuildableParser is just a flat C structure with 4 callbacks. I’m trying to install Python callbacks using ctypes, then hopefully they will get called.

I don’t know if that’s possible, sorry.

OK, guys, I did it. I was coding all night and I found it.

Gtk.BuildableParser is an unregistered struct that is wrapped in a Python object. The memory layout is like this:

struct PyBuildableParser{
    size_t refcount;
    PyObject* py_class;
    BuildableParser* parser;
    bool free_on_dealloc;
};

BuildableParser has 4 fields that are function pointers and some internal padding. I was able to set those functions from Python and it works.

@CFUNCTYPE(None, c_void_p, c_char_p, POINTER(c_char_p), POINTER(c_char_p), c_void_p, POINTER(c_void_p))
def start_element(context, element_name, attribute_name, attribute_value, user_data, gerror):
    ...

parser = Gtk.BuildableParser()

addr = id(parser)
pyobj = cast(addr, POINTER(c_void_p * 4))[0]
gptr = pyobj[2] # field 2 (counting from 0) is a pointer to BuildableParser struct
parser_struct = cast(gptr, POINTER(c_void_p * 4))[0]
parser_struct[0] = cast(start_element, c_void_p)

My functions get called.

This needs to be pushed to the repo. I want to write an overlay for Gtk.BuildableParser. Could someone guide me a bit?

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.