Translate-C error on 0.17.0-dev.607+456b2ec07 when importing GTK4 libraries

Hello all,

I recently started a new project to rewrite one of my existing C#/.NET/WPF projects into Zig + GTK 4 (project will be 2-BSD licensed so dynamically linking GTK-4 is a requirement since I don’t want it to become LGPL). I’ve written/released and currently maintain a few other Zig projects, but this will be my first GUI project in Zig and also the first one that I’m starting to link with external C libraries. I ran into the same issue that was described here since I first started the project on 0.16.0, after looking at the comments, and the status of the tickets in the upstream translate-c and arocc projects, it seemed like everything was already closed/resolved/merged, so I thought maybe I need to update to latest master for now and then I can chill on 0.17.0 once that gets officially released. However, even on 0.17.0-dev.607+456b2ec07, I get the same issue. So I wanted to confirm if it’s just me wiring things incorrectly, or if the changes aren’t in the latest master yet. I’m also fine downgrading to 0.16.0, and importing translate-c as an external dependency if that makes things any easier since I normally like to track the latest stable release, and upgrade to the next stable when I get a chance. But if that’s too difficult I’m fine with tracking master for this project and stabilizing on 0.17.0 for now. Thank you!

I’m running all of this on FreeBSD 15.0-RELEASE. pkgconf-2.4.3,1 and gtk4-4.20.3 are already installed on the host and Zig is detecting them correctly. Without pkgconf being installed, I was getting an issue about gtk/gtk.h not being found even though the linkSystemLibrary("gtk-4", .{}); call didn’t fail and found the correct library on FreeBSD. Once I switched to pkgconf, and changed it from “gtk-4” to “gtk4”, then it found the rest of the stuff and that’s when I started getting the multi pragma failure.

zig version

jon@leslie:~/Projects/App $ zig version
0.17.0-dev.607+456b2ec07

error when attempting to import gtk/gtk.h

jon@leslie:~/Projects/App $ zig build
install
└─ install App
   └─ compile exe App Debug native
      └─ translate-c 2 errors
error: translation failure
/usr/local/include/gtk-4.0/gdk/version/gdkversionmacros.h:19:2: error: \"Only <gdk/gdk.h> can be included directly.\"
#error "Only <gdk/gdk.h> can be included directly."
 ^
error: 2 compilation errors
failed command: /home/jon/.zig/zig translate-c -lc --cache-dir .zig-cache --global-cache-dir /home/jon/.cache/zig -I/usr/local/include/gtk-4.0 -I/usr/local/include/pango-1.0 -I/usr/local/include/fribidi -I/usr/local/include -I/usr/local/include/harfbuzz -I/usr/local/include/gdk-pixbuf-2.0 -I/usr/local/include/cairo -I/usr/local/include/freetype2 -I/usr/local/include/libpng16 -D_THREAD_SAFE -I/usr/local/include/pixman-1 -I/usr/local/include/graphene-1.0 -I/usr/local/lib/graphene-1.0/include -I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include -L/usr/local/lib -lgtk-4 -lpangocairo-1.0 -lpango-1.0 -lharfbuzz -lgdk_pixbuf-2.0 -lcairo-gobject -lcairo -lvulkan -lgraphene-1.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 -lintl /home/jon/Projects/App/src/c.h --listen=-

Build Summary: 0/4 steps succeeded (1 failed)
install transitive failure
└─ install App transitive failure
   └─ compile exe App Debug native transitive failure
      └─ translate-c 2 errors

error: the following maker command exited with code 1:
/home/jon/.cache/zig/o/201b413da3e9f6d025320505604c88e4/maker --zig /home/jon/.zig/zig --zig-lib-dir /home/jon/.zig/lib --build-root /home/jon/Projects/App --local-cache .zig-cache --global-cache /home/jon/.cache/zig --configuration .zig-cache/c/1efa3748f8b1e0f0168dbc146a0def18 --seed 0xfd57a27f

build.zig

...

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // Translate GTK / C Dependencies
    const translate_c = b.addTranslateC(.{
        .root_source_file = b.path("src/c.h"),
        .target = target,
        .optimize = optimize,
    });
    translate_c.linkSystemLibrary("gtk4", .{});

    const mod = b.addModule("App", .{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
    });

    const exe = b.addExecutable(.{
        .name = "App",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "App", .module = mod },
                .{ .name = "c", .module = translate_c.createModule() },
            },
        }),
    });
 }

...

src/c.h

#include <gtk/gtk.h>

Well, it sure does look like the compile error wants your src/c.h file to be

#include <gdk/gdk.h>

obviously you didn’t need me to tell you that though, so let us know why you’re not doing that :slight_smile:

From what I can tell from the GTK basic tutorial, their documentation just mentions that we need to import gtk/gtk.h for it to work.

#include <gtk/gtk.h>

static void
activate (GtkApplication *app,
          gpointer        user_data)
{
  GtkWidget *window;

  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "Window");
  gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
  gtk_window_present (GTK_WINDOW (window));
}

int
main (int    argc,
      char **argv)
{
  GtkApplication *app;
  int status;

  app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);

  return status;
}

ahh. well, all i’m doing is reading what’s on the screen and offering a suggestion; you don’t have to actually try the suggestion :slight_smile:

Haha. I did try it but it lead to another issue which I feel may be going in the wrong direction given the GTK tutorial. If I do switch to gdk/gdk.h, it yields the following:

run
└─ run exe App
   └─ install
      └─ install App
         └─ compile exe App Debug native 1 errors
.zig-cache/o/51b847d0dde74af4b2dcfa5f8e890da7/c.zig:6320:194: error: expected ';' after statement
        if (__builtin.constant_p(!(@as(c_int, 0) != 0)) != 0) if (!(@as(c_int, 0) != 0)) _ = g_string_free(string, @intFromBool(!(@as(c_int, 0) != 0))) else _ = g_string_free_and_steal(string) else _ = g_string_free(string, @intFromBool(!(@as(c_int, 0) != 0)));
                                                                                                                                                                                                 ^~~~
error: 1 compilation errors
failed command: /home/jon/.zig/zig build-exe -ODebug --dep App --dep c -Mroot=/home/jon/Projects/App/src/main.zig -MApp=/home/jon/Projects/App/src/root.zig -I/usr/local/include/gtk-4.0 -I/usr/local/include/pango-1.0 -I/usr/local/include/fribidi -I/usr/local/include -I/usr/local/include/harfbuzz -I/usr/local/include/gdk-pixbuf-2.0 -I/usr/local/include/cairo -I/usr/local/include/freetype2 -I/usr/local/include/libpng16 -D_THREAD_SAFE -I/usr/local/include/pixman-1 -I/usr/local/include/graphene-1.0 -I/usr/local/lib/graphene-1.0/include -I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include -L/usr/local/lib -lgtk-4 -lpangocairo-1.0 -lpango-1.0 -lharfbuzz -lgdk_pixbuf-2.0 -lcairo-gobject -lcairo -lvulkan -lgraphene-1.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 -lintl -ODebug -Mc=.zig-cache/o/51b847d0dde74af4b2dcfa5f8e890da7/c.zig -lc --cache-dir .zig-cache --global-cache-dir /home/jon/.cache/zig --name App --zig-lib-dir /home/jon/.zig/lib/ --listen=-

Build Summary: 1/6 steps succeeded (1 failed)
run transitive failure
└─ run exe App transitive failure
   ├─ compile exe App Debug native 1 errors
   └─ install transitive failure
      └─ install App transitive failure
         └─ compile exe App Debug native (+1 more reused dependencies)

error: the following maker command exited with code 1:
/home/jon/.cache/zig/o/201b413da3e9f6d025320505604c88e4/maker --zig /home/jon/.zig/zig --zig-lib-dir /home/jon/.zig/lib --build-root /home/jon/Projects/App --local-cache .zig-cache --global-cache /home/jon/.cache/zig --configuration .zig-cache/c/1efa3748f8b1e0f0168dbc146a0def18 --seed 0xf3dc5e45 run
jon@leslie:~/Projects/App $

Currently the only way to correctly include gtk.h is to use version 0.15.x with @cInclude.
Using 0.16.0, both @cInclude and translate-c module fail to translate gtk.h.

0.17.0-dev with translate-c module have the latest fixes for the gtk.h translation problems, but it also fails because the arocc —used in translate-c— is not yet synchronized with the latest zig changes (metadata for struct declarations).because the changes in arocc are ahead of the current zig master.

The translate-c as a module is a great idea because it can have fixes after 0.17.0 is released.

So currently what can we do:

  • Use 0.15.1
  • Wait for 0.17.0
  • Help fix translate-c/arocc
  • Donate some money to zig software foundation, so others can fix the problems.
    :slight_smile:
4 Likes

Thanks for the info @dimdin. I just wanted to make sure the issue wasn’t me before I go on another rabbit hole. I may downgrade from master to 0.15.1, but I’ll see how far I can get since a good chunk of my logic isn’t UI code. And yes, I’ve donated to ZSF multiple times throughout the years. Another donation will be coming soon :).

1 Like

Just wanted to provide a small update. I was able to successfully get GTK 4 working with Zig 0.15.2 and get some self written bindings to work and render on the screen (I know there are existing Zig <> GTK libraries, but I’m also taking this as a learning experience for Zig<>C interop, and I don’t really need all of GTK’s features). However, after playing with GTK, as someone that’s porting an app I wrote about 8 years ago in C#/WPF/XAML (and I <3 C#/WPF/XAML btw), and previously after that having re-written it from Java/Swing (IIRC), I have to say GTK is pretty insane (not in a good way). I’m going to be starting again and going to give one of the newer native Zig UI toolkits a try, and learn about this “Immediate UI” type of architecture, which seems interesting to me. I think I’ll have a better experience overall. I think I’ll give @david_vanderson’s DVUI a shot. Since the UI will most likely now be in DVUI, this means my project can start in 0.16.0 again since DVUI is 0.16.0 compatible :).

For now, I’ll paste my existing GTK<>Zig code I wrote in case anyone needs it or wants to use it as an idea for brainstorming their own stuff.

const std = @import("std");
// const Io = std.Io;
const assert = std.debug.assert;

// const App = @import("App");

const c = @cImport({
    @cInclude("gtk/gtk.h");
});

pub const std_options: std.Options = .{
    .log_level = .debug,
};

fn print_hello(widget: *c.GtkWidget, data: c.gpointer) callconv(.c) void {
    _ = widget;
    _ = data;
    c.g_print("Hello World\n");
}

/// Create application UI and wire signals.
fn activate(app: *c.GtkApplication, _: c.gpointer) callconv(.c) void {
    // Create core application window object.
    const gtk_window_widget: *c.GtkWidget = Binder.GtkApplicationWindow(app);
    const window: *c.GtkWindow = Binder.AsWindow(gtk_window_widget);

    // Window Box
    const windowBox = Binder.Box.New(c.GTK_ORIENTATION_VERTICAL, 10);
    c.gtk_window_set_child(window, windowBox);

    const platformsBox = Binder.Box.New(c.GTK_ORIENTATION_VERTICAL, 10);
    c.gtk_widget_set_vexpand(platformsBox, 1);
    Binder.Box.Append(windowBox, platformsBox);

    // Buttons Box
    const buttonsBox = Binder.Box.New(c.GTK_ORIENTATION_HORIZONTAL, 10);
    Binder.Box.Append(windowBox, buttonsBox);

    // Buttons
    const oneButton = Binder.Button.New("Button 1", &print_hello);
    const twoButton = Binder.Button.New("Button 2", &print_hello);

    // Add buttons to buttons box.
    Binder.Box.Append(buttonsBox, oneButton);
    Binder.Box.Append(buttonsBox, twoButton);

    // Set remaining window properties.
    c.gtk_window_set_title(window, "App");
    c.gtk_window_set_default_size(window, 1150, 650);
    c.gtk_window_set_resizable(window, 0);
    c.gtk_window_present(window);
}

const Binder = struct {
    // TODO: See if we can consolidate the internal logic between normal and swapped.
    pub fn connect(widget: *c.GtkWidget, message: [*]const u8, callback: c.GCallback) void {
        const result = c.g_signal_connect_data(
            widget,
            message,
            callback,
            null,
            null,
            0,
        );

        // The handler ID (always greater than 0).
        // https://docs.gtk.org/gobject/func.signal_connect_data.html
        assert(result > 0);
    }

    // Not Currently Used
    pub fn connectSwapped(widget: *c.GtkWidget, message: [*]const u8, callback: c.GCallback, window: *c.GtkWindow) void {
        const result = c.g_signal_connect_data(
            widget,
            message,
            callback,
            window,
            null,
            c.G_CONNECT_SWAPPED,
        );

        // The handler ID (always greater than 0).
        // https://docs.gtk.org/gobject/func.signal_connect_data.html
        assert(result > 0);
    }

    pub fn GtkApplicationWindow(app: *c.GtkApplication) *c.GtkWidget {
        return c.gtk_application_window_new(app);
    }

    pub fn Callback(func: *const fn (*c.GtkWidget, c.gpointer) callconv(.c) void) c.GCallback {
        return @ptrCast(@alignCast(func));
    }

    // Not Currently Used
    pub fn WindowDestroyCallback() c.GCallback {
        return @ptrCast(@alignCast(&c.gtk_window_destroy));
    }

    pub fn AsWindow(widget: *c.GtkWidget) *c.GtkWindow {
        return @as(*c.GtkWindow, @ptrCast(@alignCast(widget)));
    }

    pub fn AsBox(widget: *c.GtkWidget) *c.GtkBox {
        return @as(*c.GtkBox, @ptrCast(@alignCast(widget)));
    }

    // ----------------- Higher Functions -----------------

    pub const Button = struct {
        // Creates a button and optionally connects a signal to it.
        //
        // Caller owns memory.
        pub fn New(label: [*]const u8, func: ?*const fn (*c.GtkWidget, c.gpointer) callconv(.c) void) *c.GtkWidget {
            const button: *c.GtkWidget = c.gtk_button_new_with_label(label);

            if (func) |signal| {
                Binder.connect(button, "clicked", Binder.Callback(signal));
            }

            return button;
        }
    };

    pub const Box = struct {
        // c_int = gtk orientation enum
        pub fn New(orientation: c.GtkOrientation, spacing: u8) *c.GtkWidget {
            const box: *c.GtkWidget = c.gtk_box_new(orientation, spacing);
            c.gtk_widget_set_halign(box, c.GTK_ALIGN_CENTER);
            c.gtk_widget_set_valign(box, c.GTK_ALIGN_CENTER);
            return box;
        }

        pub fn Append(box: *c.GtkWidget, widget: *c.GtkWidget) void {
            c.gtk_box_append(Binder.AsBox(box), widget);
        }
    };
};

pub fn main() !void {
    var gpa: std.heap.DebugAllocator(.{}) = .init;
    const allocator = gpa.allocator();
    var arena_alloc: std.heap.ArenaAllocator = .init(allocator);
    const arena = arena_alloc.allocator();
    _ = arena;

    std.log.debug("Starting App ...", .{});
    // try App.run(arena);

    //defer allocator.deinit();

    //var arena: std.heap.ArenaAllocator(allocator);
    //const arena: std.mem.Allocator = init.arena.allocator();
    //init: std.process.Init
    // const io = init.io;

    // var stdout_buffer: [1024]u8 = undefined;
    // var stdout_file_writer: Io.File.Writer = .init(.stdout(), io, &stdout_buffer);
    // const stdout_writer = &stdout_file_writer.interface;
    // try stdout_writer.flush(); // Don't forget to flush!

    // Accessing command line arguments:
    // const args = try init.minimal.args.toSlice(arena);
    // for (args) |arg| {
    //     std.log.info("arg: {s}", .{arg});
    // }

    const app = c.gtk_application_new(
        "org.test.app",
        c.G_APPLICATION_DEFAULT_FLAGS,
    );

    _ = c.g_signal_connect_data(
        app,
        "activate",
        @as(c.GCallback, @ptrCast(@alignCast(&activate))),
        null,
        null,
        0,
    );

    _ = c.g_application_run(
        @as(*c.GApplication, @ptrCast(@alignCast(app))),
        0,
        null,
    );

    c.g_object_unref(app);
}

That sounds very similar to my story as well!

A good place to start is watching the zig showtime episode: Zig SHOWTIME

It’s old enough that the code samples aren’t exact, but it’s a good introduction to immediate-mode vs. retained-mode.

1 Like

Thanks @david_vanderson. I’ll take a look and will probably reach out to you in the future privately. I’m currently in the early stages of fully rewriting my Diablo II version switcher / mod manager / character isolator, Cactus, into Zig from scratch, fully built for only FreeBSD/Linux, no Windows support anymore, and fully integrating Wine into it to launch Diablo II (original, not Resurrected). So for the MVP all of the current functionality, safety checks, and UI design is the goal. I think the most complicated component in this app is the list view.

2 Likes