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.
- Let's Build the Terminal Pt. 1
- Let's Build the Terminal Pt. 2
- 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.
- GCC
- GTK4
- 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!



