GTK4: Chapter 01
Let's open the text-editor with blank window. Quickly save this blank program with name hello.c. C language has .c extension and hello is the name of the program that satisfies the purpose of code, going to written inside of it. After saving this blank program, I have syntax highlighting. Good!
Next, we are going to print hello, world text in terminal. So, we need to write this text within hello.c. Let me do that.
hello, world
But, this hello, world is string, right? In program, you write string inside of double or single quotes. Agree? But in C, you must write string inside of double quotes. So, let me quickly wrap this text with double quotes and make it string.
"hello, world"
Great! This string is not going to print in terminal on its own. We need to invoke a function on it to print and the function is printf(). This is how we use printf() function.
printf("hello, world")
C is one of those languages the required semicolon to complete the statement. In C, semicolon is not optional but required. And that way I need to write,
printf("hello, world");
Semicolon at the end. Great. But you know, compiled languages like C is different than interpreted language in terms of execution. In interpreted languages, the execution start from top and execute each statement or expression that comes in flow. But, in compiled language you need to tell the starting point - a point from where the compilation process starts. This is where the main() function comes in picture. Your program must contain one main() function and this is the function from where your program will going to compile.
In order to run our printf() statement, we need to put it inside of main() function this way.
main() {
printf("hello, world");
}
This indentation inside of function is not required. It is just for readability purpose.
You are going to use curly braces to define block like I did to define main() function block. Whatever statements we write inside of this curly braces, are considered part of the main() function.
After compilation of this main() function, it returns something. It returns an integer with 0 or greater than 0. Here, 0 means successful compilation and any other value than 0 mean something goes wrong with your program! Let's add the type signature before the main() function to indicate, what this main() is going to be written and also write return 0 explicitly.
int main() { printf("hello, world"); return 0; }
Yes, this return statement should be at the end of main() function.
In C, you don't get a single function by default and printf() is not an exception. You need to include a library in which this function is written. The library or header file is stdio.h and its standard header file that comes with C. You can include this file in our program like -
#include<stdio.h> int main() { printf("hello, world"); return 0; }
You need to write the include statement at the top of your program and the blank line after this include statement is optional. But, again it is strongly recommended.
Note: Although I said that you have to include the stdio.h header file in order to use the printf() function, it is not completely true. You can do a little trick and still run the program (of course, with a warning). One trick is to run the program using an older standard such as C90. The command is:
gcc -std=c90 hello.c -o hello && ./hello
Do this after completing the chapter.
That’s it! This is our hello, world program in C. It's time to execute this program.
Execution
We need a compiler to execute any C program. For this purpose, we are going to use GCC. This is the compiler that comes with most of the GNU/Linux system. A quick --version command will let you know that C is installed on your system or not.
$ gcc --version gcc (Debian 14.2.0-19) 14.2.0 Copyright (C) 2024 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
If you get similar output, then you have GCC on your system else you need to install. In my case, I've gcc so I can go further and execute this program. The execution of C program done in two steps —
- Compiles the program,
- Executes the compiled program.
To compile the program, this command is used.
$ gcc hello.c -o hello
This will compile your hello.c program and after successful completion it will generate output (-o) with name hello. You will get nothing if compilation happen successfully in terminal. In my case, I don't get anything, so that means my program execute successfully and has build hello. A quick ls will show you the same.
$ ls hello.c hello
In order to execute this hello executable, I need to write:
$ ./hello hello, world
And we get the output! But, this is a little clumsy. A \n after the hello, world string will help. Let's add that.
#include<stdio.h> int main() { printf("hello, world\n"); return 0; }
Let's compile and run the program again.
$ gcc hello.c -o hello && ./hello hello, world
Here, I've combines both commands into single one using && operator. You will going to use so many terminal commands in this book. So, be ready!
Note: Returning 0 is important because, if a non-zero value is returned, the OS considers that something went wrong while running the program. Based on that indication/status, the OS will not continue further operations.
Let's understand this by example: I want to print in the console that program ran successfully if the program runs successfully. I can easily add an echo statement with && like this:
$ gcc hello.c -o hello && ./hello && echo "program ran successfully"
If you run this command, you should see hello, world as the output, and program ran successfully on the next line.
Now, change the return value from 0 to, let's say, 1. Run the above command again. You will not see program ran successfully.
---
If you think that I've spent a whole chapter on a topic that I considered as an assumption (yup, I assume that you should know C) about you, then you're right — I've spent the whole chapter on it. But, the idea is to give a glimpse of how the book is written. We'll write the code one or a few lines at a time, with an explanation either before or later.
Because, I personally think that each line in our program should have a reason to have it in our program. If there is a reason, I need to explain the reason unless the framework is not managing the abstraction.
Let's Build Video Player
This is the first article in the series of the articles where we are going to build video player - player. The idea is simple, we will create a minimal video player program and then extend the functionalities of it via "extension articles". Let's see how things will go! I will update the following list as I publish the article(s) in this series.
- Let's Build Video Player
tl;dc: Refer this gist to get the final version of player.c code.
You need following:
- GCC -
sudo apt install build-essential - GTK4 -
sudo apt install libgtk-4-dev - GStreamer & plugins -
sudo apt install libgstreamer1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good
Go ahead and create a file with name player.c and write the following stub code.
#include <gtk/gtk.h> static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Player"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.player", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
If you need brief explanation on this code, refer the Let's Build Terminal Pt. 1 article. Although, it is from "Let's Build Terminal" series but the first part is generic enough to understand such a code.
Use following command to compile and run the program.
gcc player.c `pkg-config --cflags --libs gtk4` && ./a.out
Let's add the header bar. The plan is to have a "Open" button in header bar that allow us to select the video file to open and play.
We can create a header bar using gtk_header_bar_new() function and then give a title to it using gtk_window_set_titlebar() function to set the header bar as title bar of the window.
#include <gtk/gtk.h> static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Player"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Player")); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.player", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Now, go ahead and create a button using gtk_button_new_with_label() function and then pack it to the header bar.
#include <gtk/gtk.h> static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Player"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Player")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.player", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Run the program and it should produce output like this.
When user click on the button, it should open a file dialog to select the video. We need to pass the window instance as user data because it is needed to load the video into the window.
#include <gtk/gtk.h> static void _on_open_clicked(GtkWidget* button, gpointer data) { } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Player"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Player")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.player", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Let's cast and save the window passed as user data and create a file dialog using gtk_file_dialog_new() function.
#include <gtk/gtk.h> static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Player"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Player")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.player", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
In order to actually see the file dialog, we need to call the gtk_file_dialog_open() function. We need to pass 5 arguments - dialog itself, window as parent, NULL as cancellable operation, callback function, and user data.
#include <gtk/gtk.h> static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Player"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Player")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.player", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
We need to define the _on_file_opened() function. But is a asnyc callback. Due to this it accepts these parameters - source object that has started asnyc operation (file dialog) of type GObject*, result of the operation of type GAsyncResult*, and user data.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Player"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Player")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.player", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Again, let's get the values of dialog and window from the function parameters.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Player"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Player")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.player", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
We need to call the gtk_file_dialog_open_finish() function. This function finish the gtk_file_dialog_open() function and return either the selected file or an error.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Player"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Player")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.player", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
If we get the error, we need to stop and free the error memory using g_error_free(). We can also print the error message using g_printerr() function.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); if (error) { g_printerr("Error opening file %s\n", error->message); g_error_free(error); return; } } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Player"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Player")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.player", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
But, if we have file, we can proceed with playing the video on the window. Let's create a video widget using gtk_video_new() function and then set it as child of the window.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); if (error) { g_printerr("Error opening file %s\n", error->message); g_error_free(error); return; } if (file) { GtkWidget* video = gtk_video_new(); gtk_window_set_child(window, video); } } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Player"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Player")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.player", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Let's create a media stream from the given file using gtk_media_fil_new_from_file() and set this steam to the video using gtk_video_set_media_stream(). Don't forget to unref the file object.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); if (error) { g_printerr("Error opening file %s\n", error->message); g_error_free(error); return; } if (file) { GtkWidget* video = gtk_video_new(); GtkMediaStream* media = GTK_MEDIA_STREAM(gtk_media_file_new_for_file(file)); gtk_video_set_media_stream(GTK_VIDEO(video), media); gtk_window_set_child(window, video); g_object_unref(file); } } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Player"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Player")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.player", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
And with these changes, we now have the video player. Go ahead and run the program to confirm the video player!
Yay!
TIL: #include literally copy-paste the content of it
I mean I already knew about this point already but I was still thinking that there will be some magic in-between. It can't be that simple - copy-paste! But, the I watched this video by antirez.
---
Create a file with name print.c and write the following code - yes, just a printf() statement.
printf("hello, world\n");
Create another file with name hello.c and write the following code.
#include <stdio.h> int main(void) { #include "print.c" return 0; }
Now, compile and run the hello.c program (without including print.c in compilation).
gcc hello.c && ./a.out hello, world
It prints the output - hello, world as #include directive copy-paste the content of print.c file.
If you need proof then use -E compilation option. It'll print the preprocessed output. Let's also pass -P option to pretty-print the output.
gcc -E –P hello.c
The output is big. So, I truncated to only last few lines.
// ... // ... extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))); extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))); extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))); extern int __uflow (FILE *); extern int __overflow (FILE *, int); int main(void) { printf("hello, world\n"); return 0; }
There it is!
TIL: puts() is faster than printf()
Create a file with name hello.c and write the following code.
#include <stdio.h> int main(void) { printf("hello, world\n"); return 0; }
We can pass option -S to generate assembly code of this hello.c program using GCC.
gcc –S hello.c
The command generates the hello.s file with following code.
.file "hello.c" .text .section .rodata .LC0: .string "hello, world" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 leaq .LC0(%rip), %rax movq %rax, %rdi call puts@PLT movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Debian 14.2.0-19) 14.2.0" .section .note.GNU-stack,"",@progbits
The above code is generated using GCC v14.2.0. As of writing, in the above, you won't be able to find printf() function. Instead, as you can see in the highlighted (bold) lines, that it use puts() function and \n is removed from string "hello, world" as puts() function add \n at the end.
This is the part of optimization done by GCC because we are not formatting the string with any expression in printf() function. We are just printing the string. If that is the case, GCC is smart enough to replace the printf() call with puts() and remove \n from the string at the end if present. If you really want to keep printf() function, you need to pass -fno-builtin-printf option.
gcc -S -fno-builtin-printf hello.c
Here is the output.
.file "hello.c" .text .section .rodata .LC0: .string "hello, world\n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 leaq .LC0(%rip), %rax movq %rax, %rdi movl $0, %eax call printf@PLT movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Debian 14.2.0-19) 14.2.0" .section .note.GNU-stack,"",@progbits
Furthermore, the optimization shouldn't happen if we do formatting.
#include <stdio.h> int main(void) { int x = 42; printf("hello, world %d\n", x); return 0; }
For you: Generate the assembly code and confirm!
Let's Build Image Viewer
This is the first article in the series of the articles where are are going to build Image viewer - imager. The idea is simple, we will create a minimal image viewer program and then extend the functionalities of it via "extension articles". Let's see how things will go! I'll update the following list as I publish the article(s) in this series.
tl;dc: Refer this gist to get the final version of imager.c code.
Go ahead and create a file with name imager.c and write the following stub code.
#include <gtk/gtk.h> static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
If you need brief explanation on this code, refer the Let's Build Terminal Pt. 1 article. Although, it is from "Let's Build Terminal" series but the first part is generic enough to understand such a code.
Use following command to compile and run the program.
gcc imager.c `pkg-config --cflags --libs gtk4` && ./a.out
Let's add the header bar. The plan is to have a "Open" button in header bar that allow us to select the image file to open and view.
We can create a header bar using gtk_header_bar_new() function and then give a title to it using gtk_header_bar_set_title_widget(). Finally, we can use gtk_window_set_titlebar() function to set the header bar as title bar of the window.
#include <gtk/gtk.h> static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Now, go ahead and create a button using gtk_button_new_with_label() function and then pack it to the header bar.
#include <gtk/gtk.h> static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Run the program and it should produce output like this.
When user click on button, it should open a file dialog to select the image. We need to pass the window instance as user data because it is needed to load the image into the window.
#include <gtk/gtk.h> static void _on_open_clicked(GtkWidget* button, gpointer data) { } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Let's cast and save the window passed as user data and create a file dialog using gtk_file_dialog_new() function.
#include <gtk/gtk.h> static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
In order to actually see the file dialog, we need to call the gtk_file_dialog_open() function. We need to pass 5 arguments - dialog itself, window as parent, NULL as cancellable operation, callback function, and user data.
#include <gtk/gtk.h> static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
We need to define the _on_file_opened() function. But is a asnyc callback. Due to this it accepts these parameters - source object that has started asnyc operation (file dialog) of type GObject*, result of the operation of type GAsyncResult*, and user data.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Again, let's get the values of dialog and window from the function parameters.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
We need to call the gtk_file_dialog_open_finish() function. This function finish the gtk_file_dialog_open() function and return either the selected file or an error.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
If we get the error, we need to stop and free the error memory using g_error_free(). We can also print the error message using g_printerr() function.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); if (error) { g_printerr("Error opening file %s\n", error->message); g_error_free(error); return; } } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
But, if we have file, we can proceed with rendering the image on the window. Let's create a picture widget using gtk_picture_new() function and then set it as child of the window.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); if (error) { g_printerr("Error opening file %s\n", error->message); g_error_free(error); return; } if (file) { GtkWidget* picture = gtk_picture_new(); gtk_window_set_child(window, picture); } } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
In order to actually render the picture in this widget, we need to use texture widget from the GDK library. We can create texture of selected file using gdk_texture_new_from_file() function and then use it with picture instance. The gdk_texture_new_from_file() function accepts file path and error struct. We can get the path of the selected file using g_file_get_path() function.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); if (error) { g_printerr("Error opening file %s\n", error->message); g_error_free(error); return; } if (file) { char* path = g_file_get_path(file); GtkWidget* picture = gtk_picture_new(); gtk_window_set_child(window, picture); GdkTexture* texture = gdk_texture_new_from_file(file, &error); if (error) { } else { } } } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Again, if we get the error, then we need to stop by printing the message and free the error memory.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); if (error) { g_printerr("Error opening file %s\n", error->message); g_error_free(error); return; } if (file) { char* path = g_file_get_path(file); GtkWidget* picture = gtk_picture_new(); gtk_window_set_child(window, picture); GdkTexture* texture = gdk_texture_new_from_file(file, &error); if (error) { g_printerr("Error loading image %s\n", error->message); g_error_free(error); return; } else { } } } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
In else block, we'll call gtk_picture_set_paintable() to set the texture to make it printable by casting it using GDK_PIAINTABLE() macro. We also need to unref the texture as we are done with it.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); if (error) { g_printerr("Error opening file %s\n", error->message); g_error_free(error); return; } if (file) { char* path = g_file_get_path(file); GtkWidget* picture = gtk_picture_new(); gtk_window_set_child(window, picture); GdkTexture* texture = gdk_texture_new_from_file(file, &error); if (error) { g_printerr("Error loading image %s\n", error->message); g_error_free(error); return; } else { gtk_picture_set_paintable(GTK_PICTURE(picture), GDK_PAINTABLE(texture)); g_object_unref(texture); } } } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Don't forget to free the path memory and unref the file object.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); if (error) { g_printerr("Error opening file %s\n", error->message); g_error_free(error); return; } if (file) { char* path = g_file_get_path(file); GtkWidget* picture = gtk_picture_new(); gtk_window_set_child(window, picture); GdkTexture* texture = gdk_texture_new_from_file(file, &error); if (error) { g_printerr("Error loading image %s\n", error->message); g_error_free(error); return; } else { gtk_picture_set_paintable(GTK_PICTURE(picture), GDK_PAINTABLE(texture)); g_object_unref(texture); } g_free(path); g_object_unref(file); } } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
And with these changes, we now have the image viewer. Go ahead and run the program to confirm the image viewer!
I've modified this article a bit and created another article as Let's Build Video Player.
Yay!

