GTK4 Tutorial Pt. 2
Value your freedom, or you will lose it, teaches history. Richard M. Stallman, Linux, GNU, and freedom
This is the second article in the series GTK4 Tutorial. You can read the first article at here.
The program is continue running in terminal because of the while
loop. In order to terminate the loop, we just need to make the loop condition false (only when close button or x
icon is clicked).
GTK is an event driven toolkit, which means it will sleep in while
loop of g_main_context_iteration()
until an event occurs and control is passed to the appropriate function. This passing of control is done using the idea of "signals". When an event occurs, such as the press of a mouse button, the appropriate signal will be "emitted" by the widget that was pressed. This is how GTK does most of its useful work. There are signals that all widgets inherit, such as "destroy", and there are signals that are widget specific, such as "toggled" on a toggle button.
In our case, when we click on close button, “click” event occur and it emit the “destroy” signal. We just need to catch this signal and write a code or function against it. To perform this action, we need set up a signal handler to catch these signals and call the appropriate function. This is done by using a function such as g_signal_connect()
.
g_signal_connect(instance, detailed_signal, c_handler, data);
The first argument is the widget (or an instance) which will be emitting the signal, and the second the name of the signal you wish to catch (mostly in literal string such as, "destroy"). The third is the function (function name or complete function body) you wish to be called when it is caught, and the fourth, the data you wish to have passed to this function.
In order to terminate the process, we need to catch the “destroy” signal from window
widget. The first argument should be window
with type of GtkWidget
.
g_signal_connect(window, detailed_signal, c_handler, data);
I just wrote window
here because window
is GtkWidget
type. Next, lets mention the name of signal which are interested to catch e.g. "destroy".
g_signal_connect(window, "destroy", c_handler, data);
c_handler
is a function or function name that is going to execute when the "destroy" signal is caught from window
instance. In GTK4, we call it callback function and type of this callback function is GCallback
. So, we need to cast the normal function to GCallback
function. This can be done via G_CALLBACK
macro. Just pass name of the function to this macro and rest will be taken care. Let's assume that our function name is quit_app
.
g_signal_connect(window, "destroy", G_CALLBACK(quit_app), data);
No parenthesis after quit_app
as we're going to write either function name or function itself (but not going to call the function).
Finally, we’re going to pass some data to this function which is in our case, the pointer to done
variable. In this way, we can update the value of done
to FALSE
to terminate the loop.
g_signal_connect(window, "destroy", G_CALLBACK(quit_app), &done);
This line is perfect! Let’s add it into our hello.c
program.
#include<gtk/gtk.h>
int main() {
gtk_init();
gboolean done = FALSE;
GtkWidget *window;
window = gtk_window_new();
g_signal_connect(window, "destroy", G_CALLBACK(quit_app), &done);
gtk_widget_show(window);
while(!done) {
g_main_context_iteration(NULL, TRUE);
}
return 0;
}
Our program wouldn’t run. Because, we haven’t define the quit_app
function. This quit_app
function is the special callback function and it should generally be of the form.
void user_function(GtkWidget *object, gpointer user_data);
The first argument will be a pointer to the widget
that emitted the signal, and the last a pointer to the data given as the last argument to the g_signal_connect()
function as shown above.
Let’s update the name from user_function
to quit_app
.
void quit_app(GtkWidget *object, gpointer user_data);
Let’s call the first parameter widget
instead of object
(this is personal preference, you can leave it, if you are okay with default).
void quit_app(GtkWidget *widget, gpointer user_data);
The user_data
is the data we passed as last argument to g_signal_connect()
. Let's change it to simple data
as parameter name (again this is personal preference, you can leave it, if you are okay with default).
void quit_app(GtkWidget *widget, gpointer data)
This is the callback function that will run when destroy
signal is caught from window
instance.
void quit_app(GtkWidget *widget, gpointer data) {
/* Logic should go here. */
}
Perfect! Let’s add this before the main()
function as we're going to call it from the main()
function.
#include<gtk/gtk.h>
void quit_app(GtkWidget *widget, gpointer data) {
/* Logic should go here. */
}
int main() {
gtk_init();
gboolean done = FALSE;
GtkWidget *window;
window = gtk_window_new();
g_signal_connect(window, "destroy", G_CALLBACK(quit_app), &done);
gtk_widget_show(window);
while(!done) {
g_main_context_iteration(NULL, TRUE);
}
return 0;
}
Now, let’s write the logic in quit_app()
function. The logic is simple - we're going to update the value of data
to be TRUE
as follows.
gboolean *done = data;
*done = TRUE;
Let’s put this within quit_app()
function.
#include<gtk/gtk.h>
void quit_app(GtkWidget *widget, gpointer data) {
gboolean *done = data;
*done = TRUE;
}
int main() {
gtk_init();
gboolean done = FALSE;
GtkWidget *window;
window = gtk_window_new();
g_signal_connect(window, "destroy", G_CALLBACK(quit_app), &done);
gtk_widget_show(window);
while(!done) {
g_main_context_iteration(NULL, TRUE);
}
return 0;
}
Finally, add g_main_context_wakeup(NULL)
at the end of quit_app()
function. I'll not going to explain this in more detail and I ask you to go with as it (for now, just remember that you need to write).
#include<gtk/gtk.h>
void quit_app(GtkWidget *widget, gpointer data) {
gboolean *done = data;
*done = TRUE;
g_main_context_wakeup(NULL);
}
int main() {
gtk_init();
gboolean done = FALSE;
GtkWidget *window;
window = gtk_window_new();
g_signal_connect(window, "destroy", G_CALLBACK(quit_app), &done);
gtk_widget_show(window);
while(!done) {
g_main_context_iteration(NULL, TRUE);
}
return 0;
}
This line make sure that the g_main_context_iteration()
is no longer running. And these are the few lines I was talking about to write in order to terminate the process in terminal. Let's run the application and confirm that when we close the application it actually terminate the process (when I say, let's run the program, you need to run these two commands unless I update the instruction on how to run the GTK4 program).
gcc hello.c `pkg-config --cflags --libs gtk4` -o hello
./hello
Perfect! When I click on close button or x
icon, it close the window as well as terminate the process in terminal.
Personally speaking this is an extra coding. I mean we should expect that when we close the application, it should actually close the application (except few applications that doesn’t do. But, they have their own reasons to continue running the application in background)! I’ll cover this topic in next article again. But, now it is time go further and add some functionalities. I don’t consider closing an application is a functionality (even though it is. But, that something we should except. It is like creating a form and then adding a submit button as functionality. So, that user can submit form.)!
Let’s display a simple hello, world text in this blank window. We’re going to use GtkLabel
for displaying the text or label (as they say it in GTK4). Let's define a label
variable with type GtkWidget
(by default, all most all widgets will be GtkWidget
type).
#include<gtk/gtk.h>
void quit_app(GtkWidget *widget, gpointer data) {
gboolean *done = data;
*done = TRUE;
g_main_context_wakeup(NULL);
}
int main() {
gtk_init();
gboolean done = FALSE;
GtkWidget *window;
GtkWidget *label;
window = gtk_window_new();
g_signal_connect(window, "destroy", G_CALLBACK(quit_app), &done);
gtk_widget_show(window);
while(!done) {
g_main_context_iteration(NULL, TRUE);
}
return 0;
}
It is good idea to place all variables declaration nearby. Defining the variable doesn’t make it a label or text. In order to create a label, we actually need to invoke the gtk_label_new()
method.
gtk_label_new (const char *str);
This method accept one argument a label or text that we want to display which is in our case “hello, world”.
#include<gtk/gtk.h>
void quit_app(GtkWidget *widget, gpointer data) {
gboolean *done = data;
*done = TRUE;
g_main_context_wakeup(NULL);
}
int main() {
gtk_init();
gboolean done = FALSE;
GtkWidget *window;
GtkWidget *label;
window = gtk_window_new();
g_signal_connect(window, "destroy", G_CALLBACK(quit_app), &done);
label = gtk_label_new("hello, world");
gtk_widget_show(window);
while(!done) {
g_main_context_iteration(NULL, TRUE);
}
return 0;
}
Perfect! The label is created. But, it’ll not display. Let’s try by running the program again.
gcc hello.c `pkg-config --cflags --libs gtk4` -o hello
./hello

See! Window is displaying blank. In order to display the label, we need to add it into window
instance using gtk_window_set_child()
method.
gtk_window_set_child(GtkWindow *window, GtkWidget *child);
This gtk_window_set_child()
accepts two arguments - The window (e.g. parent window) and the widget (e.g. child widget label
). Let's add this method calling line before we display the window as follows.
#include<gtk/gtk.h>
void quit_app(GtkWidget *widget, gpointer data) {
gboolean *done = data;
*done = TRUE;
g_main_context_wakeup(NULL);
}
int main() {
gtk_init();
gboolean done = FALSE;
GtkWidget *window;
GtkWidget *label;
window = gtk_window_new();
g_signal_connect(window, "destroy", G_CALLBACK(quit_app), &done);
label = gtk_label_new("hello, world");
gtk_window_set_child(window, label);
gtk_widget_show(window);
while(!done) {
g_main_context_iteration(NULL, TRUE);
}
return 0;
}
Let’s run this program and see how our label looks!
gcc hello.c `pkg-config --cflags --libs gtk4` -o hello
hello.c: In function 'main':
hello.c:22:24: warning: passing argument 1 of 'gtk_window_set_child' from incompatible pointer type [-Wincompatible-pointer-types]
22 | gtk_window_set_child(window, label);
| ^~~~~~
| |
| GtkWidget * {aka struct _GtkWidget *}
In file included from /usr/include/gtk-4.0/gtk/gtkaboutdialog.h:30,
from /usr/include/gtk-4.0/gtk/gtk.h:34,
from hello.c:1:
/usr/include/gtk-4.0/gtk/gtkwindow.h:231:59: note: expected 'GtkWindow *' {aka 'struct _GtkWindow *'} but argument is of type 'GtkWidget *' {aka 'struct _GtkWidget *'}
231 | void gtk_window_set_child (GtkWindow *window,
| ~~~~~~~~~~~~~~^~~~~~
Ah! We get the error. Let’s try to read it and see what is is saying (personally speaking, GTK errors are quite useful in almost all cases). As per error, what they’re saying is, gtk_window_set_chid()
first argument should be type of GtkWindow widget whereas we passed GtkWidget type window. In order to pass the window
with GtkWindow
type, we need to cast using GTK_WINDOW
macro. This macro accept one argument which is widget that we want to cast into window. So, let's update our code as follows.
#include<gtk/gtk.h>
void quit_app(GtkWidget *widget, gpointer data) {
gboolean *done = data;
*done = TRUE;
g_main_context_wakeup(NULL);
}
int main() {
gtk_init();
gboolean done = FALSE;
GtkWidget *window;
GtkWidget *label;
window = gtk_window_new();
g_signal_connect(window, "destroy", G_CALLBACK(quit_app), &done);
label = gtk_label_new("hello, world");
gtk_window_set_child(GTK_WINDOW(window), label);
gtk_widget_show(window);
while(!done) {
g_main_context_iteration(NULL, TRUE);
}
return 0;
}
Note: Casting widget from more general widget type (e.g. GtkWidget
) to specific widget type (e.g. GtkWindow
) is quite normal in GTK application. You'll see lots of type conversions or type casting. But, the good news is, GTK has nice support for this type casting. You just need to call the appropriate macro. In GTK, all macro are in capital.
Perfect! Let’s run this updated program now (this is the last time, I’m writing following two run commands. Onward now, when I say run, you need to run these command and I’ll directly talk about the output unless I want talk about the error).
gcc hello.c `pkg-config --cflags --libs gtk4` -o hello
./hello

Notice the window is small. Previously, we had a window with size 200x200. But, now it is small because GTK calculates the size based on what is inside. If there is nothing inside the window, it’ll display 200x200 else based on widget’s require size.
Let’s update the default size of window using gtk_window_set_default_size()
.
gtk_window_set_default_size (GtkWindow *window, int width, int height);
This method accept three arguments — the window, width, and height of window. Pay attention to first argument. It accepts the window with type GtkWidow
not GtkWidget
. So, we need to cast the window
instance to GtkWindow
using GTK_WINDOW
macro.
#include<gtk/gtk.h>
void quit_app(GtkWidget *widget, gpointer data) {
gboolean *done = data;
*done = TRUE;
g_main_context_wakeup(NULL);
}
int main() {
gtk_init();
gboolean done = FALSE;
GtkWidget *window;
GtkWidget *label;
window = gtk_window_new();
gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);
g_signal_connect(window, "destroy", G_CALLBACK(quit_app), &done);
label = gtk_label_new("hello, world");
gtk_window_set_child(GTK_WINDOW(window), label);
gtk_widget_show(window);
while(!done) {
g_main_context_iteration(NULL, TRUE);
}
return 0;
}
Let's run this program and see how our output looks!

Let’s set the title of this window using gtk_window_set_title()
method.
void gtk_window_set_title(GtkWindow *window, const char *title);
This method accept two arguments — The window instance, and title text as string. Again, I’m going to cast the window
variable.
#include<gtk/gtk.h>
void quit_app(GtkWidget *widget, gpointer data) {
gboolean *done = data;
*done = TRUE;
g_main_context_wakeup(NULL);
}
int main() {
gtk_init();
gboolean done = FALSE;
GtkWidget *window;
GtkWidget *label;
window = gtk_window_new();
gtk_window_set_title(GTK_WINDOW(window), "hello, world application");
gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);
g_signal_connect(window, "destroy", G_CALLBACK(quit_app), &done);
label = gtk_label_new("hello, world");
gtk_window_set_child(GTK_WINDOW(window), label);
gtk_widget_show(window);
while(!done) {
g_main_context_iteration(NULL, TRUE);
}
return 0;
}
Let’s run the program and see the output. Looks much better than previously.

GtkWindow
has lots of methods that you can use it on window. I just used only few. But, in future, we'll use many of them. I'll also going to talk more about conventions and how to read the documentation (don't worry if you are still figuring out what is going on).
Before, we go further, let’s do some modification on our label using some of the GtkLabel
methods.
I want to left-align the label in window. I can do it using gtk_label_set_xalign()
method.
void gtk_label_set_xalign(GtkLabel *self, float xalign);
This method accept two arguments — the label and align value between 0
to 1
. Our label is type of GtkWidget
. So, we need to cast it using GTK_LABEL
macro as follows.
#include<gtk/gtk.h>
void quit_app(GtkWidget *widget, gpointer data) {
gboolean *done = data;
*done = TRUE;
g_main_context_wakeup(NULL);
}
int main() {
gtk_init();
gboolean done = FALSE;
GtkWidget *window;
GtkWidget *label;
window = gtk_window_new();
gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);
g_signal_connect(window, "destroy", G_CALLBACK(quit_app), &done);
label = gtk_label_new("hello, world");
gtk_label_set_xalign(GTK_LABEL(label), 0);
gtk_window_set_child(GTK_WINDOW(window), label);
gtk_widget_show(window);
while(!done) {
g_main_context_iteration(NULL, TRUE);
}
return 0;
}
Let’s run this program and see how it looks!

Next, I want to display this label top-left corner. We already left-aligned. We just need to top-aligned and we’re good to go. We can top-align using gtk_label_set_yalign()
method.
void gtk_label_set_yalign(GtkLabel *self, float yalign);
Again, this method accept two arguments — the label and align value between 0
to 1
.
#include<gtk/gtk.h>
void quit_app(GtkWidget *widget, gpointer data) {
gboolean *done = data;
*done = TRUE;
g_main_context_wakeup(NULL);
}
int main() {
gtk_init();
gboolean done = FALSE;
GtkWidget *window;
GtkWidget *label;
window = gtk_window_new();
gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);
g_signal_connect(window, "destroy", G_CALLBACK(quit_app), &done);
label = gtk_label_new("hello, world");
gtk_label_set_xalign(GTK_LABEL(label), 0);
gtk_label_set_yalign(GTK_LABEL(label), 0);
gtk_window_set_child(GTK_WINDOW(window), label);
gtk_widget_show(window);
while(!done) {
g_main_context_iteration(NULL, TRUE);
}
return 0;
}
Let’s run this program and confirm the output (it should be as we expected).

This GtkLabel
has lots of useful methods. We're going to use many of them as we need in future articles (while creating applications). That's it for this article, in next article, I'm going to talk about how to create an application in "modern" GTK way and few naming conversions, and coding styles. From the next article, we'll start to polish the rough edges in learning.