Saturday, April 25, 2026

Let's Build the Terminal Pt. 2

This is the second 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.

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

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

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 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); 

    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

We will continue from here and let's start by adding a header bar using gtk_header_bar_new() function.

#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), "Terminal");
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 800);

    // Header bar. 
    GtkWidget* header_bar = gtk_header_bar_new();

    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 set the "Terminal" as title of the header bar. We need to pass the "Terminal" as label because gtk_header_bar_set_title_widget() expect it.

#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), "Terminal");
    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("Terminal"));

    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;
}

The header bar is created. We need to call gtk_window_set_titlebar() function to actually set this header bar as titlebar for 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), "Terminal");
    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("Terminal"));

    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;
}

Run the program using the above command and you should see a titlebar in the window.

We are now going to add a notebook. Notebook help you to have multi-tab terminal. Let's create a notebook using gtk_notebook_new() function. I will write it before the header bar code. I'll tell you the reason why I did this later.

#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), "Terminal");
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 800);

    // Notebook.
    GtkWidget* notebook = gtk_notebook_new();

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

    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;
}

Notebook have pages or tab in our case. I'll use word "tab" or "page" interchangeably. Let's add two pages using gtk_notebook_append_page() function. This function accepts the content of the page and the title of the page.

#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), "Terminal");
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 800);

    // Notebook.
    GtkWidget* notebook = gtk_notebook_new();
    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"));

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

    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;
}

I'm not going to add this notebook directly in the window. Instead, I'll use vertical box as the container and then add notebook to the box container for better layout management.

#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), "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_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"));

    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;
}

Run the program to confirm the output. This is how it looks on my computer. Notice, the notebook is not taking all the available space.

We can fix this issue by expanding on both vertical and horizontal sides using gtk_widget_set_vexpand() and gtk_widget_set_hexpand() functions.

#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), "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"));

    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 make things more interesting by adding a button in header bar on which if we click, it'll add a new tab in notebook. We'll do this in steps.

First, let's add a button to the header bar by creating a button using gtk_button_new_with_label() function and then packing the button to the end in header bar using gtk_header_bar_pack_end() function.

#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), "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")); 

    // Button.
    GtkWidget* button = gtk_button_new_with_label("New Tab");
    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 attach the "clicked" signal to this button and write the stub callback function. We need to pass the notebook instance as the user data as we need to append the new tab to the notebook when clicked. As notebook is created before the header bar, we can directly pass it as user data. Now, you know the reason why I write the Notebook code before the header bar (Yes, this is quick fix but not a hack or anything)!

#include <gtk/gtk.h>  

static void _on_new_tab_clicked(GtkWidget* widget, gpointer data) {

}

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"));

    // Button.
    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;
}

In the callback function, let's first cast the data to notebook type using GTK_NOTEBOOK() macro and then call the gtk_notebook_get_n_pages() function. This function will return total number of pages or tabs exists in given notebook. The reason to plan this function is to give the tab name like "Tab 3", "Tab 4", "Tab 5", .... e.g. increment the number by one and prefix the "Tab " string when clicked on "New Tab" button.

#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);
} 

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 title in plain C code.

#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);
} 

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;
}

You can argue against this code as it is not future proof if someone create X number of tabs that overflow this size. Again, I'm not worry about such a scenario as one need to create many many tabs to overflow this limit and we can always fix such type of issues later.

Let's call gtk_notebook_append_page() to add a new tab with this title and dummy content. This function returns a page number that we need to focus using gtk_notebook_set_current_page() function.

#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;
}

Go ahead and run this program. You should see the following output when you click on 'New Tab' button.

Yay!

Thursday, April 23, 2026

Let's Build the Terminal Pt. 1

This is the first 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.

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

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

At the end of this series, you'll have the terminal like the following.

As you can see in the above screenshot, the terminal is very limited in terms of functionalities - terminal with tabs support only. No menu, no settings, no preference, etc. The plan is to build the terminal as fast as possible and then add such a functionalities as part of "extension articles". Let's see how things will go.

---

You need couple of software on your system.

  1. GCC
  2. GTK4
  3. VTE

If you're using Debian/Ubuntu you can run following command to install the above software.

sudo apt install build-essential
sudo apt install libgtk-4-dev
sudo apt install libvte-2.91-gtk4-dev 

These commands install needed software and few useful utilities.

Go ahead and create a file with name terminal.c and write the following code.

#include <stdio.h> 

int main(void) { 
    printf("hello, world\n");

    return 0; 
} 

Nice little program to confirm the C setup. Run the following command to compile and run the program.

gcc terminal.c && ./a.out
hello, world

Yup! It is working. Let's add GTK header file.

#include <gtk/gtk.h>

int main(void) { 
    printf("hello, world\n");

    return 0; 
}

To create an application using GTK4, gtk_application_new() function is used. We need to pass the unique application id of the application and default flags as follows.

#include <gtk/gtk.h>

int main(void) { 
    GtkApplication* app = gtk_application_new("dev.marichi.terminal", G_APPLICATION_DEFAULT_FLAGS);

    return 0;
}

You can give any id you like but reverse domain naming convention is common nowadays.

Application when run emits the "activate" signal. We need to catch this signal and write our application code against it using g_signal_connect() function.

#include <gtk/gtk.h>

int main(void) { 
    GtkApplication* app = gtk_application_new("dev.marichi.terminal", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);

    return 0;
}

activate is a callback function that will be executed against the "activate" signal. Let's write few more lines of the code in main() function and then we'll create this function.

Our application is now ready to run using g_application_run() function. We need to pass our application instance to this function and command line arguments. In such a case, let's update the main() function signature with argc and argv. g_application_run() function returns an integer status which is a right candidate to return as main() function return value.

#include <gtk/gtk.h> 

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);

    return status;
}

g_application_run() run the application. Anything written after this line of code won't run until the application terminate. In such a case, let's write the unref code to free the memory of the app once we're done with application using g_object_unref() function.

#include <gtk/gtk.h> 

int main(void) { 
    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 is the code we need in main() function. Let's now move the focus to activate() callback function.

#include <gtk/gtk.h> 

static void activate(GtkApplication* app, gpointer data) {

}

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; 
}

A callback function by default can accept two arguments – a widget/application that emitted the signal and any data passed to it as 4th or last argument of g_signal_connect() function.

Let's create a window of the application using gtk_application_window_new() function.

#include <gtk/gtk.h> 

static void activate(GtkApplication* app, gpointer data) { 
    GtkWidget* window = gtk_application_window_new(app);
} 

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 set the window title and size. We need to cast the type using GTK_WINDOW() macro as gtk_window_set_title() and gtk_window_set_default_size() expect to be GtkWindow* type whereas window is type of GtkWidget*.

#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), "Terminal"); 
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 800);
} 

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;
}

Finally, we're ready to present 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), "Terminal"); 
    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.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;
}

The program after these modifications is now executable i.e. it'll run without error and will produce reasonable output. We need to change a command bit to include GTK header as we're using GTK4 library in this program. Following is the modified version of the command to compile and run this program.

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

After running this command, you should see a window like this (might be a bit different based on your distro and DE).

Yay!

Let's stop here and in the next article, we will add support for tabs.

Wednesday, April 15, 2026

मन की चंचलता दूर करने का उपाय

तैलंग स्वामीसे प्रभावित होकर नेपालनरेश उनके पास सत्संग के लिए आए, उसके कुछ अंश - 

गणपति(तैलंग) स्वामी का आकर्षण पुनः तीसरे दिन (नेपाल)नरेश को उनके यहाँ खींच लाया । नरेश के बैठते ही गणपति स्वामी ने कहा- "राजन्, बहुत चंचल दिखाई दे रहे हैं । मन की चंचलता दूर करने के लिए प्रातः और सायं आधा घण्टा स्थिर होकर निश्चिन्त भाव से बैठना पड़ेगा । कुछ दिनों तक यह अभ्यास करते रहोगे तो मन में स्थिरता उत्पन्न होगी । इसके बाद धीरे-धीरे समय बढ़ाते जाना । मन के स्थिर हो जाने पर एक प्रकार का आनन्द अनुभव करोगे ।" 

~ योगिराज तैलंगस्वामी पुस्तक, विश्वााथ मुखर्जी द्वारा लिखित  

Saturday, April 11, 2026

યુધિષ્ઠિરના લોહીનું રહસ્ય

સોનાના પાત્રમાં પાણી લઈને આવી પહોંચેલી સૈરંધ્રી(દ્રૌપદી)એ કંક(યુધિષ્ઠિર)ની વાત સાંભળી હતી. બૃહન્નલા(અર્જુન)ને રોકવાનું કારણ પણ એ સમજી ગઈ. એને ખબર હતી કે અર્જુનનો એ નિયમ હતો કે ‘યુદ્ધભૂમિ સિવાય મોટાભાઈને જે કોઈ માણસ ઘા કરે યા લોહી કાઢે એને પોતે કદી સહી શકશે નહિ- તત્કાલ એનો જીવ લીધે જ છૂટકો કરશે.' તો અત્યારે તો યુધિષ્ઠિરનું અડધું મોં લોહીથી ખરડાયેલું હતું!

કંકે ખોબામાં લોહી ઝીલી લીધું એનું કારણ પણ દ્રૌપદી જાણતી હતી : વિના કસૂરે યુધિષ્ઠિરનું લોહી કાઢવામાં આવ્યું હોય ને એ લોહી જો પૃથ્વી ઉપર પડે તો કસૂરવાન માણસનું ધનોતપનોત નીકળી જાય એ નિશ્ચિત હતું!

~ પાર્થને કહો ચડાવે બાણ - 4માંથી, પન્નાલાલ પટેલ

Wednesday, April 8, 2026

Structure in C

Consider the following code snippet.

struct foo {
    int bar;
    char baz;
};

This code snippet creates a foo structure that has two members bar and baz of type int and char respectively. Using structure, you can combine the data of multiple types under single variable.

Now, I can create a variable of this foo structure type as follows.

struct foo f;

Or I can create as many variables as I want.

struct foo f1;
struct foo f2;
struct foo f3;

// OR

struct foo f1, f2, f3;

This concludes the syntax of structure in C. Now, we are going to talk about the other related to structure concepts and then do composition to use separate concepts of C and programming in general together.

1. Initialize

It is recommended that when you create a structure variable, initialize it with 0 value.

struct foo f = { 0 };

Yes, you don't have to write 0 value for each member of the structure. Just write single 0 and rest will be taken care by the compiler. In this way, you're making sure that your variable is properly initialized and doesn't have any garbage value.

Or if you have, initialize the variable with initial values in sequence.

struct foo f = { 1, 'A' };

The sequence matters. Else write the name by prefixing dot (.) and then assign the value to the member of structure.

struct foo f = {
    .baz = 'A',
    .bar = 1
};

2. Get & Set

To access the member of the structure (get) and modifies (set) them, dot (.) operator is used.

struct foo f;

// set
f.bar = 1;
f.baz = 'A';

// get
printf("%d %c\n", f.bar, f.baz);

3. string as structure member

This is how we can define string as structure member.

struct foo {
    int bar;
    char baz[20];
};

But, we can not assign the value to this string member using dot(.) operator.

struct foo f;

f.bar = 1;

// ERROR
f.baz = "hello, world";

We need to use strcpy() function from string.h header file to assign the string value to the string member of the structure.

struct foo f;

f.bar = 1;
strcpy(f.baz, "hello, world");

Interesting enough, if we have initial value, we can assign directly without use of strcpy() function.

struct foo f = { 1, "hello, world" };

// OR

struct foo f = {
    .baz = "hello, world",
    .bar = 1
};

4. typedef

Consider the following code snippet where I'm creating few structure variables.

struct foo f1;
struct foo f2;
struct foo f3;

Notice, I need to type struct each time? We can eliminate by defining structure with typedef.

typedef struct {
    int bar;
    char baz;
} foo;

We can now define the variable of type foo struct as follows.

foo f1;
foo f2;
foo f3;

5. structure pointer

You can define a pointer that points to structure e.g.

struct foo {
    int bar;
    char baz;
};

struct foo f = { 1, 'A' };

// Pointer to struct
struct foo *ptr = &f;

Now, you can access member of f using *ptr. But, you can't type *ptr.bar as . has high-precedence and ptr.bar will evaluate first and then * on top of the result. So, we need to type (*ptr).bar as follows.

(*ptr).bar = 3;

Or you can use arrow operator -> as follows (and this is recommended).

ptr->bar = 3;

Creating a pointer to the struct is useful when you want to pass the existing structure to the function and want to modifies the member of structure.

struct foo {
    int bar;
    char baz;
};

void updateFoo(struct foo *ptr) {
    ptr->bar = 2;
    ptr->baz = 'B';
}

struct foo f = { 1, 'A' };

updateFoo(&f);

6. malloc() struct

In previous section, we wrote,

struct foo {
    int bar;
    char baz;
};

struct foo = { 1, 'A' };
struct foo *ptr = &f;

The above code works because we have assigned the initial value to the structure variable and memory is being allocated. But, when you don't have the initial value then you need to allocate the memory using malloc() function.

struct foo {
    int bar;
    char baz;
};

struct foo *f = malloc(sizeof(struct foo));

The above code calculate the size of foo struct and allocate the memory. Then it saves the pointer to the location in f variable. After this, we can work with members of structure e.g.

struct foo {
    int bar;
    char baz;
};

struct foo *f = malloc(sizeof(struct foo));

f->bar = 1;
f->baz = 'A';

Once you don't with f, you must need to free the memory using free() function.

free(f);

Thursday, April 2, 2026

યોગીરાજ પાસેથી યોગદીક્ષા પ્રાપ્ત કરનાર કેટલાક મહાનુભાવો

યોગીરાજ સ્વયં અનેક ભક્તોને યોગક્રિયા શીખવીને તેમને આત્મોન્નતિના માર્ગ પર આગળ વધવાની પ્રેરણા આપતા. તેમણે કેટલા લોકોને દીક્ષા આપી છે તેની કોઇ પ્રામાણિત કે નિશ્ચિત સંખ્યા નથી. જે થોડાક વિખ્યાત યોગીઓ રૂપે પરિચિત રહ્યા છે તેમાં યોગીરાજના ઋષિકલ્પ બંને પુત્રો તિનકૌડી લાહિરી અને દુકૌડી લાહિરી, પંચાનન ભટ્ટાચાર્ય, સ્વામી પ્રણવાનંદ ગિરિ, સ્વામી યુક્તેશ્વર ગિરિ, ભૂપેન્દ્રનાથ સાન્યાલ, સ્વામી કેશવાનંદ, સ્વામી કેવલાનંદ, વિશુધ્ધાનંદ સરસ્વતી, કાશીનાથ શાસ્ત્રી, નગેન્દ્રનાથ ભાદુરી, પ્રસાદદાસ ગોસ્વામી', કૈલાશચંદ્ર બન્ધોપાધ્યાય, રામગોપાલ મજુમદાર, મહેન્દ્રનાથ સાન્યાલ, રામદયાલ મજુમદાર, હરિનારાયણ પાલદી વગેરે મુખ્ય છે. તે ઉપરાંત પણ એમ પણ સંભળાય છે કે ભાસ્કરાનંદ સરસ્વતી અને બાલાનંદ બ્રહ્મચારીએ પણ પોતાના સાધનામાર્ગથી અલગ એવા યોગીરાજ દ્વારા પ્રદર્શિત યોગસાધનાનું અનુશીલન કર્યું હતું. તે સમયના કાશીનરેશ, નેપાળ નરેશ, કાશ્મીર નરેશ, બર્દવાનના રાજા, કાલીકૃષ્ણ ઠાકુર અને સર ગુરુદાસ બંધોપાધ્યાય વગેરે સમાજના ઉચ્ચ સ્તરીય અન્ય લોકોએ પણ તેમની પાસેથી યોગની દીક્ષા મેળવી હતી. તે ઉપરાંત સમાજના નિમ્ન સ્તરના હજારો માનવીઓએ પણ તેમની પાસે મુક્તિમાર્ગનું શિક્ષણ મેળવી સ્વયંને કૃતકૃત્ય કર્યા હતા. તેઓ કહ્યા કરતા હતા કે ગૃહસ્થ આશ્રમથી મોટો કોઇ આશ્રમ નથી; કારણકે ગૃહસ્થ આશ્રમના આધાર પર જ અન્ય આશ્રમોની સ્થાપના થાય છે. તે બ્રહ્મચર્ય, વાનપ્રસ્થ અને સંન્યાસ આશ્રમનું ભરણપોષણ કરે છે, એટલે ગૃહસ્થ આશ્રમ જ શ્રેષ્ઠ આશ્રમ છે.

પાછળથી એમ પણ જણાયું છે કે પંચાનન ભટ્ટાચાર્યના શિષ્ય અને લાલગોલા હાઇ સ્કૂલના પ્રધાન અધ્યાપક વરદાચરણ મજુમદાર પાસેથી કાજી નઝરુલ ઇસ્લામ અને નેતાજી સુભાષચંદ્ર બોઝે આ મહાન ક્રિયાયોગની દીક્ષા પ્રાપ્ત કરી હતી. નેતાજીએ ૧૨મી જૂન, ૧૯૩૯ ને સોમવારે વરદાબાબુ પાસેથી દીક્ષા લીધી હતી. તે ઉપરાંત રામદયાલ મજુમદાર પાસેથી શ્રીમાન સીતારામદાસ ઓમકારનાથે આ યોગદીક્ષા પ્રાપ્ત કરી હતી. આ જ યોગના માધ્યમથી તેમણે જીવનને સુંદર બનાવ્યું તથા દરેક પ્રકારની સફળતા પ્રાપ્ત કરી હતી. આ મહાન યોગ જ આ મહાત્માઓના જીવનની પ્રધાન અને ગુપ્ત ચાવી હતી. આ જ ચાવીથી તેમણે પોતપોતાના હૃદયમંદિરનાં પ્રધાન દ્વાર ખોલવાની ક્ષમતા મેળવી હતી. એ જ તેમની ઉન્નતિ અને ચરમ ઉત્કર્ષનું સાધન હતું. આ જ યોગકર્મનું સંપાદન કરીને જ તેમને હૃદયદેવતાની પ્રાપ્તિ થઈ હતી. જેના બળ પર લોકકલ્યાણ પ્રતિ જીવનનો ઉત્સર્ગ જ તેમનું શ્રેય રહ્યું છે. એ દિવસોમાં મોટા ભાગના લોકો આ યોગકર્મ કરી શકતા નહોતા. તેથી જ તેમની ઇંદ્રિયશક્તિઓ સ્વચ્છ થયા વિનાની કે અસ્વચ્છ રહેતી હતી. ધર્મના બાહ્ય દેખાવો તરફ ખેંચાવાથી જ આજે દેશમાં આટલો અન્યાય અને અત્યાચાર પ્રવર્તે છે. એટલા માટે યોગીરાજ કહ્યા કરતા હતા: “આ યોગસાધના કરવાથી જ મનુષ્યનું જીવન સુંદર અને મહિમાવાન કે શ્રેષ્ઠ થાય છે. આત્માના મનને જેઓ જાણે છે કે જે સજાગ છે, સચેતન છે, તેમને જ વસ્તુતઃ મનુષ્ય કહે છે."

~ પુરાણ પુરુષ યોગીરાજ શ્રી શ્યામાચરણ લાહિરી

Monday, March 30, 2026

Decorator Pattern in JavaScript

This article is for someone who is looking for a quick check on the decorator pattern and what to evaluate if it can be added into the toolbox of the daily programming. If you are looking for the formal or professional explanation (e.g. one with definitions and details and examples for interview preps) then refer to this article.

Using the decorator pattern, you can extend the functionalities of existing functions. Consider the following example code.

function greet() {
    console.log('hello, world');
}

Now, we want to extend the functionalities of this greet function by logging before and after the function prints the "hello, world" text. Of course, you can do the following to solve this problem.

function greet() {
    console.log('Before: greet prints');
    console.log('hello, world');
    console.log('After: greet prints');
}

But, if that was the case/question then why someone asked you at the first place in the interview question! In such a scenario, you need to (over) clever and solve this problem in different ways. One way to do it is, by decorator pattern. How? Simple - wrapping the existing function by another new function e.g.

function greet() {
    console.log('hello, world');
}

function greetWithLogs() {
    console.log('Before: greet prints');
    greet();
    console.log('After: greet prints');
}

But, again this is not the pragmatic (extendable) way to do it! Then? Let's do it in hard way -

  1. Create a new function.
  2. The new function returns a function.
  3. Pass the existing function that you want to extend to the new function as an argument.

Step - 1 - Implementation.

function withLogs() {
    
}

Step - 2 - New function returns a function.

function withLogs() {
    return function() {

    }
}

Step - 3 - Pass the existing function that we want to extend.

function withLogs(fn) {
    return function() {
        console.log('Before: greet prints');
        fn();
        console.log('After: greet prints');
    }
}

The implementation is done. We are now ready to use it. Let's create a greetWithLogs() function by passing greet to withLogs() function.

const greetWithLogs = withLogs(greet);

Now, calling the greetWithLogs() returns the desired result! You might have a question like, what are the advantages of doing this? Instead of creating a new function and calling the greet() function in it? withLogs is not tied to greetWithLogs() function only. You can do withLogs() with other functions that fetch the data from the database or call a function that fetches the user data.

const findByManyWithLogs = withLogs(findByMany);

// OR

const getUserInfoWithLogs = withLogs(getUserInfo);

Sunday, March 29, 2026

રેચક, પૂરક અને કુંભક

“કેવળ રેચક ઓ પૂરક આઉર બઢાઓ એ સિધ્ધિ દે. લાગે આઉર સમાધ-રેચક પૂરક બિના જયસે બંધાકૂપ, પ્રાણવાયુ કો બલ લે આઓ એ મન નિશ્ચલ હોય જાય. આયુર બઢાઓએ રોગ ન રહે પાપ જલાઓએ નિર્મલ કરે. જ્ઞાન હોય તિમિર નાશે.” (કોઈ સંશોધન વિના આ જેમનું તેમ ઉદ્ધૃત કરવામાં આવેલું છે.) 

અર્થાત્ પ્રાણાયામમાં રેચક, પૂરક અને કુંભક આ ત્રણ કર્મ છે. યોગીરાજ કહે છે કે રેચક અને પૂરક હજી વધુ વધારો. એમ થતાં પ્રાણ સ્થિર થતાં સમાધિ લાગશે અને સિધ્ધિ પ્રાપ્ત થશે. સ્વતંત્ર કુંભકની આવશ્યકતા નથી. આ રેચક અને પૂરકથી અલગ જે પ્રાણાયામ કે શ્વાસ લેવા કે છોડવા સિવાય જે પ્રાણાયામ છે તે જલહીન કૂવાની જેમ નિષ્ફળ છે. પ્રાણવાયુને બળપૂર્વક ખેંચવા અને ફેંકવાનું કર્મ કરવાથી જ મન નિશ્ચલ થઈ જાય છે. તેને હજી થોડો વધુ વધારો. એવું કરવાથી રોગ નહિ રહે. જન્મ-જન્માંતરના પાપસમૂહોના અગ્નિસાત્ થઈ જતાં મન નિર્મળ થશે. જ્ઞાનની પ્રાપ્તિ થશે અને અજ્ઞાન દૂર થશે.

~ પુરાણ પુરુષ યોગીરાજ શ્રી શ્યામાચરણ લાહિરી 

Saturday, March 28, 2026

જરાસંધ

ભગવાન કૃષ્ણે જરાસંધના જન્મની વાત પણ ટૂંકાણમાં કહી સંભળાવી : 'મહાતપસ્વી ચંડકૌશિક નામના ઋષિએ જરાસંધના નિઃસંતાન પિતાને એક આમ્રફળ આપતાં કહ્યું હતું કે આ ફળ તારી રાણીને ખવરાવીશ એટલે એને મહાબળવાન પુત્ર થશે!

‘રાજાને બે રાણીઓ હતી. બેઉ રાણીઓ એકસરખી પ્રિય હતી. કેરી એણે બેઉ રાણીઓને અડધી અડધી ખવરાવી. સમય જતાં બન્ને રાણીઓને પ્રસવ થયો. પણ બન્યું એવું કે બન્ને રાણીએ અડધું અડધું બાળક એટલે કે એક હાથ એક પગ એ રીતે અક્કેકા ફાડિયાને જન્મ આપ્યો.

‘આનું કારણ રાજા તરત જ પામી ગયો ! પણ હવે શું ? પોતાના તકદીર ઉપર વિલાપ કરતાં દાસીઓને કહ્યું: “ફેંકી દો આ બન્ને ફાડિયાં.”

'દાસીઓએ ફાડિયાં મહેલની પાછળ આવેલી બારી વાટે ફેંકી દીધાં. આ સ્થળની નજીકમાં જ એક જરા નામની રાક્ષસી રહેતી હતી. એણે કશુંક મહેલ ઉપરથી પડતું જોયું. ત્યાં જઈને જુએ છે તો બાળકનાં બે ફાડિયાં હતાં ! રાક્ષસી તો નવજાત શિશુનું કૂણું કૂણું માંસ જોઈને રાજી રાજી થઈ ઊઠી. પછી એણે પોતાના નિવાસ પર જઈને ખાવાના ઇરાદા સાથે બેઉ ફાડિયાં ભેગાં કર્યાં. આ સાથે ચમત્કાર થયો. ફાડિયાં સંધાઈ ગયાં ને બાળકના રુદનનો અવાજ ઊઠ્યો. રાક્ષસીને થયું કે આ બાળક રાજાને પાછું આપીશ તો એ મને સદાને માટે માંસનો આહાર બાંધી આપશે.

'રાક્ષસીની ધારણા સાચી પડી. પોતાના જ આ બાળકને જીવતું થયેલું જોઈને રાજારાણીઓના આનંદનો પાર ન રહ્યો. રાજાએ પેલી રાક્ષસીને રોજના ભક્ષ તરીકે દરરોજ એક પશુ આપવાનો અમલદારોને હુકમ કરી જરા રાક્ષસીને ખુશ કરીને વિદાય આપી ને નગરમાં પછી છ દિવસનો મહોત્સવ મંડાયો...." ને પાંડવો તરફ નજર ફેરવી કૃષ્ણે વાત પૂરી કરી : "આ રીતે જરા નામની રાક્ષસીથી સંધાયેલા જરાસંધમાં રાક્ષસી બળ અને ગુણો હોય એ સ્વાભાવિક છે."

~ પાર્થને કહો ચડાવે બાણ - 3 માંથી, પન્નાલાલ પટેલ

Friday, March 27, 2026

Window in X11

In this article, we are going to create a simple X11 program that display a window. This is the first in the series of articles on X11 and GUI in general.

You need X11 development libraries in order to follow these articles. Go ahead and install the lib based on your distro and/or package manager. As we are going to write the code in C, GCC - a C compiler is needed. Install that one as well.

Go ahead and create a file with the name wm.c (wm - window manager) and write the following code.

#include <stdio.h>

int main(void) {
    printf("hello, world\n");

    return 0;
}

Run the following command to compile and execute the program.

gcc wm.c -o wm && ./wm

You should see "hello, world" as the output.

In order to create a window in X11, we first need to make a connection to the display. To create a connection, XOpenDisplay() function is used. You can pass the name of the display to this function. But, you don't have to. Just pass the NULL value and rest is taken care by XOpenDisplay() function. When you call XOpenDisplay() two things can happen - On success it'll return a pointer to the display or on failure you won't get anything. If we get the pointer as a connection to display, we should save it into the variable of type Display. But, if it fails, we need to terminate the program as if we don't have a connection to display, how should we render a window and interact with it?!

XOpenDisplay() function is declared in the X11/Xlib.h header file. So, we need to include this header file in order to use the XOpenDisplay() function.

Let's update the wm.c program with what we just discussed.

#include <stdio.h>
#include <X11/Xlib.h>

int main(void) {
    Display* display = XOpenDisplay(NULL);

    if (!display) {
       fprintf(stderr, "Unable to open X display\n");
       return 1;
    }

    return 0;
}

In X11, display doesn't mean a single monitor or the screen. If you have multiple screens attached to a single computer and you can interact with the same keyboard and/or mouse with all the screens, you still have a single display. As official definitions says,

"In X11, display is defined as a workstation consisting of a keyboard, a pointing device such as mouse, and one or more screens. Multiple screens can work together, with mouse movement allowed to cross physical screen boundaries. As long as multiple screens are controlled by single user with single keyboard and pointing device, they comprise only a single display."

As we can have multiple screens, we need to select the screen on which we need to display our window. For that, we need to call the DefaultScreen() function and pass the display connection to this function call. This function returns a screen number (int) on which we can display a window later on.

#include <stdio.h>
#include <X11/Xlib.h>

int main(void) {
    Display* display = XOpenDisplay(NULL);

    if (!display) {
       fprintf(stderr, "Unable to open X display\n");
       return 1;
    }

    int screen = XDefaultScreen(display);

    return 0;
}

We are now ready to create a window. We need to call the XCreateSimpleWindow() function to create a window. We need to pass NINE arguments to this function call - a display, a root window, x, y (as coordinate), width, height (dimensions), border width, border color, background color (of window). On successful call, this function returns a window of type Window. Let me call this function by passing the values to it and then we can discuss again.

#include <stdio.h>
#include <X11/Xlib.h>

int main(void) {
    Display* display = XOpenDisplay(NULL);

    if (!display) {
       fprintf(stderr, "Unable to open X display\n");
       return 1;
    }

    int screen = XDefaultScreen(display);

    Window window = XCreateSimpleWindow(
           display,
           RootWindow(display, screen),
           100, 100,                        // x, y
           400, 200,                        // width, height
           1,                               // border width
           BlackPixel(display, screen),     // border color
           WhitePixel(display, screen)      // background color
    );

    return 0;
}

Every window in X11 always has a parent window (except the root window) as windows in X11 are always in hierarchy form. To make this hierarchy form, we need to add a parent of this window. We can fetch the root or parent window using the RootWindow() function. This function accepts display and screen as the two arguments. x and y are the position of the window. In X11, the (0, 0) starts from the top-left corner. Then we defined the size of the window as width and height in pixels. We also need to tell the border width and color of the window which are 1 and black respectively. Finally, the background of the window is white that you should see when we run the program and render the window on screen. BlackPixel() and WhitePixel() are the macros that return default black and white color on the specified screen (more on this in future articles).

We can simplify this XCreateSimpleWindow() function call by putting these functions outside as follows.

#include <stdio.h>
#include <X11/Xlib.h>

int main(void) {
    Display* display = XOpenDisplay(NULL);

    if (!display) {
       fprintf(stderr, "Unable to open X display\n");
       return 1;
    }

    int screen = XDefaultScreen(display);

    Window root = RootWindow(display, screen);

    unsigned long black = BlackPixel(display, screen);
    unsigned long white = WhitePixel(display, screen);

    Window window = XCreateSimpleWindow(
           display, 
           root,
           100, 100,
           400, 200,
           1,
           black,
           white
    );

    return 0;
}

I've also removed the comments as explanation is provided.

XCreateSimpleWindow() creates a window. But, creating a window is not enough. We need to display it or as X11 says, we need to map it. To map it, we need to call the XMapWindow() function. This function accepts the display and a window that we want to map.

#include <stdio.h>
#include <X11/Xlib.h>

int main(void) {
    Display* display = XOpenDisplay(NULL);

    if (!display) {
       fprintf(stderr, "Unable to open X display\n");
       return 1;
    }

    int screen = XDefaultScreen(display);

    Window root = RootWindow(display, screen);

    unsigned long black = BlackPixel(display, screen);
    unsigned long white = WhitePixel(display, screen);

    Window window = XCreateSimpleWindow(
           display, 
           root,
           100, 100,
           400, 200,
           1,
           black,
           white
    );

    XMapWindow(display, window);

    return 0;
}

Finally (not actually), we need to close the display connection right before the return statement to free the memory once we are done with the program using the XCloseDisplay() function. This function accepts a display that we are interested in closing.

#include <stdio.h>
#include <X11/Xlib.h>

int main(void) {
    Display* display = XOpenDisplay(NULL);

    if (!display) {
       fprintf(stderr, "Unable to open X display\n");
       return 1;
    }

    int screen = XDefaultScreen(display);

    Window root = RootWindow(display, screen);

    unsigned long black = BlackPixel(display, screen);
    unsigned long white = WhitePixel(display, screen);

    Window window = XCreateSimpleWindow(
           display, 
           root,
           100, 100,
           400, 200,
           1,
           black,
           white
    );

    XMapWindow(display, window);

    XCloseDisplay(display);

    return 0;
}

We are ready to run our program. But, we need to tweak that previous command, in order to include the X11 header in the compilation process.

gcc wm.c -lX11  -o wm && ./wm

The program compiles and executes but nothing happens! I mean we are supposed to see a window with white background. But, we didn't see it. Actually, we need to keep a program or window running in order to see the window and interact with it. How about having an infinite while loop?

#include <stdio.h>
#include <X11/Xlib.h>

int main(void) {
    Display* display = XOpenDisplay(NULL);

    if (!display) {
       fprintf(stderr, "Unable to open X display\n");
       return 1;
    }

    int screen = XDefaultScreen(display);

    Window root = RootWindow(display, screen);

    unsigned long black = BlackPixel(display, screen);
    unsigned long white = WhitePixel(display, screen);

    Window window = XCreateSimpleWindow(
           display, 
           root,
           100, 100,
           400, 200,
           1,
           black,
           white
    );

    XMapWindow(display, window);

    while (1) {

    }

    XCloseDisplay(display);

    return 0;
}

Perfect! Within this while loop, we can write custom code against the interaction. For example, close the window if the user presses any key from the keyboard. In order to do such interaction, we need to listen for input events such as KeyPress event. We need to call the XSelectInput() function to register the type of events we are interested in listening to. This function accepts three arguments - display, a window, and event (e.g. KeyPressMask in our scenario). Let's write this before we map the window as we need to register everything needed for the window before we display it on the screen.

#include <stdio.h>
#include <X11/Xlib.h>

int main(void) {
    Display* display = XOpenDisplay(NULL);

    if (!display) {
       fprintf(stderr, "Unable to open X display\n");
       return 1;
    }

    int screen = XDefaultScreen(display);

    Window root = RootWindow(display, screen);

    unsigned long black = BlackPixel(display, screen);
    unsigned long white = WhitePixel(display, screen);

    Window window = XCreateSimpleWindow(
           display, 
           root,
           100, 100,
           400, 200,
           1,
           black,
           white
    );

    XSelectInput(display, window, KeyPressMask);

    XMapWindow(display, window);

    while (1) {

    }

    XCloseDisplay(display);

    return 0;
}

Now, we can check for the event in a loop. XNextEvent() will loop for the event. It accepts two arguments - display and address of the event as a type of XEvent. It can then have a type that we can compare against e.g. KeyPress.

#include <stdio.h>
#include <X11/Xlib.h>

int main(void) {
    Display* display = XOpenDisplay(NULL);

    if (!display) {
       fprintf(stderr, "Unable to open X display\n");
       return 1;
    }

    int screen = XDefaultScreen(display);

    Window root = RootWindow(display, screen);

    unsigned long black = BlackPixel(display, screen);
    unsigned long white = WhitePixel(display, screen);

    Window window = XCreateSimpleWindow(
           display, 
           root,
           100, 100,
           400, 200,
           1,
           black,
           white
    );

    XSelectInput(display, window, KeyPressMask);

    XMapWindow(display, window);

    XEvent event;
    while (1) {
        XNextEvent(display, &event);

        if (event.type == KeyPress) {
            break;
        }
    }

    XCloseDisplay(display);

    return 0;
}

I wrote the XNextEvent() function within a loop as we continuously look for the event or interaction while our program is running. Currently, we are just listening for key presses but in future, we'll listen for many other types of interaction such as, mouse interaction, window map or unmap interaction, etc.

We are now ready to re-run the program again. This time you should see a window and will close on any key-press.

The article is long enough. I want you to take a break before we continue adding more code in this program. See you in the next article!