Zig-gtk3: convenience functions for using Gtk+ in Zig

In Zterm I had been keeping some convenience functions for using Gtk+ in a separate file. Originally this was pretty small, but over time I’ve been adding to it and am now to the point where I think others might find it useful, or I might even want to re-use it myself. So I forked it off into a separate repo and have started expanding it a bit.

Some of this is just the boilerplate that you would need such as importing the headers and fixing a couple functions that zig translate-c can’t currently cope with due to macro-abuse. But I’ve also been adding some nice things like wrappers that take a native Zig type as parameters or return native Zig types. For instance getting the state of a GtkToggleButton returns either 1 or 0 using the C api, but returns a native bool using the wrapper function.

I’ve been considering taking it further, and have a little prototype of the concept for the GtkBox widget, where I have a Box struct with a single member, which is the pointer to the GtkBox widget.

// Creating a GtkBox using the C api
const box = gtk.gtk_box_new(
    @intToEnum(GtkOrientation, GTK_ORIENTATION_HORIZONTAL),
    2,
);
// Same, using the wrapper
const zigbox = gtk.Box.new(gtk.Orientation.vertical, 3);
// gtk.Orientation.vertical is a Zig enum that maps to the C equivalent, no need to do the @intToEnum

// Packing a widget into the box (C api)
gtk.gtk_box_pack_start(@ptrCast(*gtk.GtkBox, box), some_widget, 0, 1, 1);
// bindings api
zigbox.pack_start(some_widget, false, true, 1);

In addition to reducing boilerplate and the number of times it’s neccesary to cal @ptrCast and @intToEnum, I think it makes it way easier to track what the types actually are when you’re using real booleans and such, and less clutter leads to better readability.

I hesitate to call this bindings, and real bindings would be generated automatically using Gir, but hopefully someone will find it useful :slight_smile:
zig-gtk3
zig-vte

6 Likes

Nice! Now this just needs package manager integration :slightly_smiling_face:

I hate the fact that this would be even needed just because of the macro abuse, as you noted, but: definitely GIR bindings would be the most ergonomic choice.

1 Like

This is awesome, great work!

1 Like

I’m planning that actually. I still haven’t tried Zigmod or Gyro, but I intend to support both. Just need to find the time.

Over the last couple days during breaks from my day job I’ve reorganized the code, splitting each widget type into it’s own file and whatnot. Didn’t want the main file to get to huge. I’ve also updated the examples to show how to use the developing new api.

What I think is cool, is that you can use the C api plus some of the convenience functions, the more idiomatic wrappers I’m developing, it any mixture of both. So even if it’s never complete, you should be able to fill in the missing parts by directly calling the C functions.

This is up on Astrolabe as of yesterday, and should be usable with Gyro if I did things right. Still undergoing heavy iteration. There are code examples in the examples subdirectory which can be built with zig build.

1 Like

An example of the fun of navigating the lesser advertised parts of the Gtk api…

fn get_g_type(self: Widget) u64 {
    return self.ptr.*.parent_instance.g_type_instance.g_class.*.g_type;
 }

Gtk defines the functions which tell you whether a given widget is of a certain widget type, but they’re heavily dependent on macro abuse and translate_c can’t cope. So I had to figure out what they were doing inside. The function above gets the numeric identifier for a given widget, which I only found by using std.debug.print over and over again to probe the data structure. Then I have to check it against the identifier returned by gtk_window_get_type(), gtk_box_get_type() etc. Each widget type implements a version of that function.

Zero documentation on that, they just assume that you can use the C macros. This is necessary because Gtk is programed in an OOP style in a language with no OOP features, so they fake it by casting the pointer around to different pointer types.

Anyway, sorry for the ranty post, but in a nutshell this is part of the reason for not using the C api directly. I quickly realized when writing Zterm that the C api kinda sucks…

1 Like

One trick you might considering using is creating one-liner C functions that simply call the corresponding macro. That way you can call these simple wrappers from C and Zig code.

Pretty good amount of updated code just pushed in the last couple days. New examples, lots of bugfixes. There’s a separate repo for Vte wrappers. If you use Vte, you only need that package as it pulls in the Gtk code. Both are published to Astrolabe.

Zterm now uses this branch of the original code, too. But it’s mostly still using the C api directly, need to take some time to use the new functions.

1 Like

The main development branch is odin. There is now a loki branch which tracks zig master, as we’re already having some breaking changes since 0.8.0.

Interesting. I ran translate-c on GTK’s hello_world.c which gave me working but horrendous code by this by expanding all macros in place, like this:

c.gtk_window_set_title(@ptrCast([*c]c.GtkWindow, @alignCast(@import("std").meta.alignment(c.GtkWindow), c.g_type_check_instance_cast(@ptrCast([*c]c.GTypeInstance, @alignCast(@import("std").meta.alignment(c.GTypeInstance), window)), c.gtk_window_get_type()))), "Window");

As I like to have the type-checking at least when debugging I translated the type check macros manually:

pub fn g_cast(comptime T: type, gtk_type: c.GType, value: anytype) *T {
    return @ptrCast(*T, c.g_type_check_instance_cast(@ptrCast(*c.GTypeInstance, value), gtk_type));
}

pub fn GTK_WINDOW(value: anytype) *c.GtkWindow {
    return g_cast(c.GtkWindow, c.gtk_window_get_type(), value);
}

pub fn GTK_BOX(value: anytype) *c.GtkBox {
    return g_cast(c.GtkBox, c.gtk_box_get_type(), value);
}

That is interesting. I’ll have to take a closer look when I have time. I’m mostly curious what the g_type_check_instance_cast function is doing, as that sounds like exactly what might be needed based on the name.