Kiran Chauhan

Cogito, ergo sum // I think, therefore I am.

Showing posts with label gtk. Show all posts

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.

  1. Let's Build Video Player

tl;dc: Refer this gist to get the final version of player.c code.

You need following:

  1. GCC - sudo apt install build-essential
  2. GTK4 - sudo apt install libgtk-4-dev
  3. 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_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), "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!


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.

  1. Let's Build Image Viewer

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!


Let's Build the Terminal Pt. 3

,

This is the third article in the series of the articles where we are going to build the terminal in C using GTK4 and VTE. The series is divided into three articles.

  1. Let's Build the Terminal Pt. 1
  2. Let's Build the Terminal Pt. 2
  3. Let's Build the Terminal Pt. 3

tl;dc: Refer this gist to get the final version of terminal.c code.

---

Let's continue from where we left off in the last article. Following is the code we have in terminal.c file.

#include <gtk/gtk.h>

static void _on_new_tab_clicked(GtkWidget* widget, gpointer data) {
    GtkNotebook* notebook = GTK_NOTEBOOK(data);

    int pages = gtk_notebook_get_n_pages(notebook);

    char title[32];
    g_snprintf(title, sizeof(title), "Tab %d", pages + 1);

    int new_page = gtk_notebook_append_page(notebook, gtk_label_new("New tab content goes here"), gtk_label_new(title)); 

    gtk_notebook_set_current_page(notebook, new_page);
} 

static void activate(GtkApplication* app, gpointer data) {
    GtkWidget* window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "Terminal");
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 800);

    // Container.
    GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);

    // Notebook.
    GtkWidget* notebook = gtk_notebook_new();
    gtk_widget_set_vexpand(notebook, TRUE);
    gtk_widget_set_hexpand(notebook, TRUE);

    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 1 content goes here"), gtk_label_new("Page 1")); 
    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 2 content goes here"), gtk_label_new("Page 2")); 
    gtk_box_append(GTK_BOX(box), notebook);

    gtk_window_set_child(GTK_WINDOW(window), box); 

    // Header bar.
    GtkWidget* header_bar = gtk_header_bar_new();
    gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Terminal"));

    GtkWidget* button = gtk_button_new_with_label("New Tab");
    g_signal_connect(button, "clicked", G_CALLBACK(_on_new_tab_clicked), notebook);
    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.terminal", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return status;
}

And we know the command to compile and run the program.

gcc terminal.c `pkg-config --cflags --libs gtk4` && ./a.out

In this article, we're now going to add actual terminal within Notebook tabs. To load the terminal, we're going to use VTE library header file as vte/vte.h.

#include <gtk/gtk.h>
#include <vte/vte.h>

static void _on_new_tab_clicked(GtkWidget* widget, gpointer data) {
    GtkNotebook* notebook = GTK_NOTEBOOK(data);

    int pages = gtk_notebook_get_n_pages(notebook);

    char title[32];
    g_snprintf(title, sizeof(title), "Tab %d", pages + 1);

    int new_page = gtk_notebook_append_page(notebook, gtk_label_new("New tab content goes here"), gtk_label_new(title)); 

    gtk_notebook_set_current_page(notebook, new_page);
} 

static void activate(GtkApplication* app, gpointer data) {
    GtkWidget* window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "Terminal");
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 800);

    // Container.
    GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);

    // Notebook.
    GtkWidget* notebook = gtk_notebook_new();
    gtk_widget_set_vexpand(notebook, TRUE);
    gtk_widget_set_hexpand(notebook, TRUE);

    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 1 content goes here"), gtk_label_new("Page 1")); 
    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 2 content goes here"), gtk_label_new("Page 2")); 
    gtk_box_append(GTK_BOX(box), notebook);

    gtk_window_set_child(GTK_WINDOW(window), box); 

    // Header bar.
    GtkWidget* header_bar = gtk_header_bar_new();
    gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Terminal"));

    GtkWidget* button = gtk_button_new_with_label("New Tab");
    g_signal_connect(button, "clicked", G_CALLBACK(_on_new_tab_clicked), notebook);
    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.terminal", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return status;
}

When we start the application, it should load with terminal session in the focused tab. When we click on 'Add Tab', we expect that it should add a new tab with terminal session in it. In other words, we are going to create terminal from few places. So, we are going to create a common function create_terminal() for the same and call it from where it is needed. This function should return a widget so that it can be added inside the Notebook page.

#include <gtk/gtk.h>
#include <vte/vte.h>

static GtkWidget* create_terminal() {

}

static void _on_new_tab_clicked(GtkWidget* widget, gpointer data) {
    GtkNotebook* notebook = GTK_NOTEBOOK(data);

    int pages = gtk_notebook_get_n_pages(notebook);

    char title[32];
    g_snprintf(title, sizeof(title), "Tab %d", pages + 1);

    int new_page = gtk_notebook_append_page(notebook, gtk_label_new("New tab content goes here"), gtk_label_new(title)); 

    gtk_notebook_set_current_page(notebook, new_page);
} 

static void activate(GtkApplication* app, gpointer data) {
    GtkWidget* window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "Terminal");
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 800);

    // Container.
    GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);

    // Notebook.
    GtkWidget* notebook = gtk_notebook_new();
    gtk_widget_set_vexpand(notebook, TRUE);
    gtk_widget_set_hexpand(notebook, TRUE);

    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 1 content goes here"), gtk_label_new("Page 1")); 
    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 2 content goes here"), gtk_label_new("Page 2")); 
    gtk_box_append(GTK_BOX(box), notebook);

    gtk_window_set_child(GTK_WINDOW(window), box); 

    // Header bar.
    GtkWidget* header_bar = gtk_header_bar_new();
    gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Terminal"));

    GtkWidget* button = gtk_button_new_with_label("New Tab");
    g_signal_connect(button, "clicked", G_CALLBACK(_on_new_tab_clicked), notebook);
    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.terminal", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return status;
}

Let's create a terminal instance using vte_terminal_new() function.

#include <gtk/gtk.h>
#include <vte/vte.h>

static GtkWidget* create_terminal() {
    VteTerminal* terminal = VTE_TERMINAL(vte_terminal_new());
}

static void _on_new_tab_clicked(GtkWidget* widget, gpointer data) {
    GtkNotebook* notebook = GTK_NOTEBOOK(data);

    int pages = gtk_notebook_get_n_pages(notebook);

    char title[32];
    g_snprintf(title, sizeof(title), "Tab %d", pages + 1);

    int new_page = gtk_notebook_append_page(notebook, gtk_label_new("New tab content goes here"), gtk_label_new(title)); 

    gtk_notebook_set_current_page(notebook, new_page);
} 

static void activate(GtkApplication* app, gpointer data) {
    GtkWidget* window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "Terminal");
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 800);

    // Container.
    GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);

    // Notebook.
    GtkWidget* notebook = gtk_notebook_new();
    gtk_widget_set_vexpand(notebook, TRUE);
    gtk_widget_set_hexpand(notebook, TRUE);

    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 1 content goes here"), gtk_label_new("Page 1")); 
    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 2 content goes here"), gtk_label_new("Page 2")); 
    gtk_box_append(GTK_BOX(box), notebook);

    gtk_window_set_child(GTK_WINDOW(window), box); 

    // Header bar.
    GtkWidget* header_bar = gtk_header_bar_new();
    gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Terminal"));

    GtkWidget* button = gtk_button_new_with_label("New Tab");
    g_signal_connect(button, "clicked", G_CALLBACK(_on_new_tab_clicked), notebook);
    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.terminal", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return status;
}

Terminal start with the default shell and with few other things (theme, font, etc). We can get this info via calling g_get_environ() function.

#include <gtk/gtk.h>
#include <vte/vte.h>

static GtkWidget* create_terminal() {
    VteTerminal* terminal = VTE_TERMINAL(vte_terminal_new());

    char** env = g_get_environ();
}

static void _on_new_tab_clicked(GtkWidget* widget, gpointer data) {
    GtkNotebook* notebook = GTK_NOTEBOOK(data);

    int pages = gtk_notebook_get_n_pages(notebook);

    char title[32];
    g_snprintf(title, sizeof(title), "Tab %d", pages + 1);

    int new_page = gtk_notebook_append_page(notebook, gtk_label_new("New tab content goes here"), gtk_label_new(title)); 

    gtk_notebook_set_current_page(notebook, new_page);
} 

static void activate(GtkApplication* app, gpointer data) {
    GtkWidget* window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "Terminal");
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 800);

    // Container.
    GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);

    // Notebook.
    GtkWidget* notebook = gtk_notebook_new();
    gtk_widget_set_vexpand(notebook, TRUE);
    gtk_widget_set_hexpand(notebook, TRUE);

    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 1 content goes here"), gtk_label_new("Page 1")); 
    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 2 content goes here"), gtk_label_new("Page 2")); 
    gtk_box_append(GTK_BOX(box), notebook);

    gtk_window_set_child(GTK_WINDOW(window), box); 

    // Header bar.
    GtkWidget* header_bar = gtk_header_bar_new();
    gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Terminal"));

    GtkWidget* button = gtk_button_new_with_label("New Tab");
    g_signal_connect(button, "clicked", G_CALLBACK(_on_new_tab_clicked), notebook);
    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.terminal", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return status;
}

From this env, get the default shell by calling g_environ_getenv() function and telling that we're interested in fetching the SHELL value, and if there is no, then default to Bash.

#include <gtk/gtk.h>
#include <vte/vte.h>

static GtkWidget* create_terminal() {
    VteTerminal* terminal = VTE_TERMINAL(vte_terminal_new());

    char** env = g_get_environ();

    const char* shell = g_environ_getenv(env, "SHELL");
    if (!shell) {
        shell = "/bin/bash";
    }
}

static void _on_new_tab_clicked(GtkWidget* widget, gpointer data) {
    GtkNotebook* notebook = GTK_NOTEBOOK(data);

    int pages = gtk_notebook_get_n_pages(notebook);

    char title[32];
    g_snprintf(title, sizeof(title), "Tab %d", pages + 1);

    int new_page = gtk_notebook_append_page(notebook, gtk_label_new("New tab content goes here"), gtk_label_new(title)); 

    gtk_notebook_set_current_page(notebook, new_page);
} 

static void activate(GtkApplication* app, gpointer data) {
    GtkWidget* window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "Terminal");
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 800);

    // Container.
    GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);

    // Notebook.
    GtkWidget* notebook = gtk_notebook_new();
    gtk_widget_set_vexpand(notebook, TRUE);
    gtk_widget_set_hexpand(notebook, TRUE);

    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 1 content goes here"), gtk_label_new("Page 1")); 
    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 2 content goes here"), gtk_label_new("Page 2")); 
    gtk_box_append(GTK_BOX(box), notebook);

    gtk_window_set_child(GTK_WINDOW(window), box); 

    // Header bar.
    GtkWidget* header_bar = gtk_header_bar_new();
    gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Terminal"));

    GtkWidget* button = gtk_button_new_with_label("New Tab");
    g_signal_connect(button, "clicked", G_CALLBACK(_on_new_tab_clicked), notebook);
    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.terminal", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return status;
}

We have all the needed info (surprise!). We are ready to spawn the terminal process using vte_terminal_spawn_async() function, It accept so many arguments (13) that I'll only comments for the important one and leave on you to explore further.

#include <gtk/gtk.h>
#include <vte/vte.h>

static GtkWidget* create_terminal() {
    VteTerminal* terminal = VTE_TERMINAL(vte_terminal_new());

    char** env = g_get_environ();

    const char* shell = g_environ_getenv(env, "SHELL");
    if (!shell) {
        shell = "/bin/bash";
    }

    vte_terminal_spawn_async(
        terminal,
        VTE_PTY_DEFAULT,
        NULL, // working directory, NULL being the current one.
        (char* []){ (char *)shell, NULL },
        env,
        G_SPAWN_DEFAULT,
        NULL, NULL,
        NULL,
        -1,
        NULL,
        NULL,
        NULL // any user data we want to pass.
    );
}

static void _on_new_tab_clicked(GtkWidget* widget, gpointer data) {
    GtkNotebook* notebook = GTK_NOTEBOOK(data);

    int pages = gtk_notebook_get_n_pages(notebook);

    char title[32];
    g_snprintf(title, sizeof(title), "Tab %d", pages + 1);

    int new_page = gtk_notebook_append_page(notebook, gtk_label_new("New tab content goes here"), gtk_label_new(title)); 

    gtk_notebook_set_current_page(notebook, new_page);
} 

static void activate(GtkApplication* app, gpointer data) {
    GtkWidget* window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "Terminal");
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 800);

    // Container.
    GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);

    // Notebook.
    GtkWidget* notebook = gtk_notebook_new();
    gtk_widget_set_vexpand(notebook, TRUE);
    gtk_widget_set_hexpand(notebook, TRUE);

    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 1 content goes here"), gtk_label_new("Page 1")); 
    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 2 content goes here"), gtk_label_new("Page 2")); 
    gtk_box_append(GTK_BOX(box), notebook);

    gtk_window_set_child(GTK_WINDOW(window), box); 

    // Header bar.
    GtkWidget* header_bar = gtk_header_bar_new();
    gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Terminal"));

    GtkWidget* button = gtk_button_new_with_label("New Tab");
    g_signal_connect(button, "clicked", G_CALLBACK(_on_new_tab_clicked), notebook);
    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.terminal", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return status;
}

This create a terminal process and attach it to the terminal widget with given shell.

Finally, we need to free the memory that was allocated to env using g_strfreev() function and return a terminal widget as GtkWidget*. We need to cast the terminal using GTK_WIDGET() macro as it is type of VteTerminal*.

#include <gtk/gtk.h>
#include <vte/vte.h>

static GtkWidget* create_terminal() {
    VteTerminal* terminal = VTE_TERMINAL(vte_terminal_new());

    char** env = g_get_environ();

    const char* shell = g_environ_getenv(env, "SHELL");
    if (!shell) {
        shell = "/bin/bash";
    }

    vte_terminal_spawn_async( 
        terminal,
        VTE_PTY_DEFAULT,
        NULL, // working directory, NULL being the current one.
        (char* []){ (char *)shell, NULL },
        env,
        G_SPAWN_DEFAULT,
        NULL, NULL,
        NULL,
        -1,
        NULL,
        NULL,
        NULL // any user data we want to pass.
    );

    g_strfreev(env);

    return GTK_WIDGET(terminal);
}

static void _on_new_tab_clicked(GtkWidget* widget, gpointer data) {
    GtkNotebook* notebook = GTK_NOTEBOOK(data);

    int pages = gtk_notebook_get_n_pages(notebook);

    char title[32];
    g_snprintf(title, sizeof(title), "Tab %d", pages + 1);

    int new_page = gtk_notebook_append_page(notebook, gtk_label_new("New tab content goes here"), gtk_label_new(title)); 

    gtk_notebook_set_current_page(notebook, new_page);
} 

static void activate(GtkApplication* app, gpointer data) {
    GtkWidget* window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "Terminal");
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 800);

    // Container.
    GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);

    // Notebook.
    GtkWidget* notebook = gtk_notebook_new();
    gtk_widget_set_vexpand(notebook, TRUE);
    gtk_widget_set_hexpand(notebook, TRUE);

    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 1 content goes here"), gtk_label_new("Page 1")); 
    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), gtk_label_new("Page 2 content goes here"), gtk_label_new("Page 2")); 
    gtk_box_append(GTK_BOX(box), notebook);

    gtk_window_set_child(GTK_WINDOW(window), box); 

    // Header bar.
    GtkWidget* header_bar = gtk_header_bar_new();
    gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Terminal"));

    GtkWidget* button = gtk_button_new_with_label("New Tab");
    g_signal_connect(button, "clicked", G_CALLBACK(_on_new_tab_clicked), notebook);
    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.terminal", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return status;
}

With these changes in create_terminal() function, we are now ready to load the terminal. We just need to replace the tab content with this function call at two places - in "Tab 1", and in _on_new_tab_clicked() function. We can remove the "Tab 2" as we don't need it for now.

#include <gtk/gtk.h>
#include <vte/vte.h>

static GtkWidget* create_terminal() {
    VteTerminal* terminal = VTE_TERMINAL(vte_terminal_new());

    char** env = g_get_environ();

    const char* shell = g_environ_getenv(env, "SHELL");
    if (!shell) {
        shell = "/bin/bash";
    }

    vte_terminal_spawn_async( 
        terminal,
        VTE_PTY_DEFAULT,
        NULL, // working directory, NULL being the current one.
        (char* []){ (char *)shell, NULL },
        env,
        G_SPAWN_DEFAULT,
        NULL, NULL,
        NULL,
        -1,
        NULL,
        NULL,
        NULL // any user data we want to pass.
    );

    g_strfreev(env);

    return GTK_WIDGET(terminal);
}

static void _on_new_tab_clicked(GtkWidget* widget, gpointer data) {
    GtkNotebook* notebook = GTK_NOTEBOOK(data);

    int pages = gtk_notebook_get_n_pages(notebook);

    char title[32];
    g_snprintf(title, sizeof(title), "Tab %d", pages + 1);

    int new_page = gtk_notebook_append_page(notebook, create_terminal(), gtk_label_new(title));

    gtk_notebook_set_current_page(notebook, new_page);
} 

static void activate(GtkApplication* app, gpointer data) {
    GtkWidget* window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "Terminal");
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 800);

    // Container.
    GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);

    // Notebook.
    GtkWidget* notebook = gtk_notebook_new();
    gtk_widget_set_vexpand(notebook, TRUE);
    gtk_widget_set_hexpand(notebook, TRUE);

    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), create_terminal(), gtk_label_new("Page 1"));
    gtk_box_append(GTK_BOX(box), notebook);

    gtk_window_set_child(GTK_WINDOW(window), box); 

    // Header bar.
    GtkWidget* header_bar = gtk_header_bar_new();
    gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Terminal"));

    GtkWidget* button = gtk_button_new_with_label("New Tab");
    g_signal_connect(button, "clicked", G_CALLBACK(_on_new_tab_clicked), notebook);
    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.terminal", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return status;
}

Let's run the program and confirm the output. We need to adjust the command as we are now using one more library - VTE - which we need to include in compilation process.

gcc temrinal-4.c `pkg-config --cflags --libs gtk4 vte-2.91-gtk4` && ./a.out

You should see the output like this. Clicking on "New Tab" button should add a new tab with terminal.

Yay!


Subscribe to: Posts (Atom)