Writing your own terminal emulator
Vincent Bernat
I was a happy user of rxvt-unicode until I got a laptop with a HiDPI display. Switching from a LoDPI to a HiDPI screen and back was a pain: I had to manually adjust the font size on all terminals or restart them.
VTE is a library to build a terminal emulator using the GTK+ toolkit, which handles DPI changes. It is used by many terminal emulators, like GNOME Terminal, evilvte, sakura, termit and ROXTerm. The library is quite straightforward and writing a terminal doesn’t take much time if you don’t need many features.
Let’s see how to write a simple one.
A simple terminal#
Let’s start small with a terminal with the default settings. We’ll write that in C. Another supported option is Vala.
#include <vte/vte.h> static void child_ready(VteTerminal *terminal, GPid pid, GError *error, gpointer user_data) { if (!terminal) return; if (pid == -1) gtk_main_quit(); } int main(int argc, char *argv[]) { GtkWidget *window, *terminal; /* Initialise GTK, the window and the terminal */ gtk_init(&argc, &argv); terminal = vte_terminal_new(); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "myterm"); /* Start a new shell */ gchar **envp = g_get_environ(); gchar **command = (gchar *[]){g_strdup(g_environ_getenv(envp, "SHELL")), NULL }; g_strfreev(envp); vte_terminal_spawn_async(VTE_TERMINAL(terminal), VTE_PTY_DEFAULT, NULL, /* working directory */ command, /* command */ NULL, /* environment */ 0, /* spawn flags */ NULL, NULL, /* child setup */ NULL, /* child pid */ -1, /* timeout */ NULL, /* cancellable */ child_ready, /* callback */ NULL); /* user_data */ /* Connect some signals */ g_signal_connect(window, "delete-event", gtk_main_quit, NULL); g_signal_connect(terminal, "child-exited", gtk_main_quit, NULL); /* Put widgets together and run the main loop */ gtk_container_add(GTK_CONTAINER(window), terminal); gtk_widget_show_all(window); gtk_main(); }
You can compile it with the following command:
gcc -O2 -Wall $(pkg-config --cflags vte-2.91) term.c -o term $(pkg-config --libs vte-2.91)
And run it with ./term
:
More features#
From here, you can have a look at the documentation to alter behavior or add more features. Here are three examples.
Colors#
You can define the 16 basic colors with the following code:
#define CLR_R(x) (((x) & 0xff0000) >> 16) #define CLR_G(x) (((x) & 0x00ff00) >> 8) #define CLR_B(x) (((x) & 0x0000ff) >> 0) #define CLR_16(x) ((double)(x) / 0xff) #define CLR_GDK(x) (const GdkRGBA){ .red = CLR_16(CLR_R(x)), \ .green = CLR_16(CLR_G(x)), \ .blue = CLR_16(CLR_B(x)), \ .alpha = 0 } vte_terminal_set_colors(VTE_TERMINAL(terminal), &CLR_GDK(0xffffff), &(GdkRGBA){ .alpha = 0.85 }, (const GdkRGBA[]){ CLR_GDK(0x111111), CLR_GDK(0xd36265), CLR_GDK(0xaece91), CLR_GDK(0xe7e18c), CLR_GDK(0x5297cf), CLR_GDK(0x963c59), CLR_GDK(0x5E7175), CLR_GDK(0xbebebe), CLR_GDK(0x666666), CLR_GDK(0xef8171), CLR_GDK(0xcfefb3), CLR_GDK(0xfff796), CLR_GDK(0x74b8ef), CLR_GDK(0xb85e7b), CLR_GDK(0xA3BABF), CLR_GDK(0xffffff) }, 16);
While you can’t see it on the screenshot,1 this also enables background transparency.
Miscellaneous settings#
VTE comes with many settings to change the behavior of the terminal. Consider the following code:
vte_terminal_set_scrollback_lines(VTE_TERMINAL(terminal), 0); vte_terminal_set_scroll_on_output(VTE_TERMINAL(terminal), FALSE); vte_terminal_set_scroll_on_keystroke(VTE_TERMINAL(terminal), TRUE); vte_terminal_set_mouse_autohide(VTE_TERMINAL(terminal), TRUE);
This will:
- disable the scrollback buffer;
- not scroll to the bottom on new output;
- scroll to the bottom on keystroke; and
- hide the mouse cursor when typing.
Update the window title#
An application can change the window title
using XTerm control sequences (for example, with printf
"\e]2;${title}\a"
). If you want the actual window title to reflect
this, you need to define this function:
static gboolean on_title_changed(GtkWidget *terminal, gpointer user_data) { GtkWindow *window = user_data; gtk_window_set_title(window, vte_terminal_get_window_title(VTE_TERMINAL(terminal))?:"Terminal"); return TRUE; }
Then, connect it to the appropriate signal, in main()
:
g_signal_connect(terminal, "window-title-changed", G_CALLBACK(on_title_changed), GTK_WINDOW(window));
Final words#
I don’t need much more as I am using tmux inside each terminal. In
my own copy, I have also added the ability to complete a word
using ones from the current window or other windows (also known
as dynamic abbrev expansion). This requires to implement a
terminal daemon to handle all terminal windows with one process,
similar to urxvtcd
.
While writing a terminal “from scratch”2 suits my need, it may not be
worth it. evilvte is quite customizable and can be
lightweight. Consider it as a first alternative. Honestly, I don’t
remember why I didn’t pick it.
Update (2017-02)
evilvte has not seen an update since 2014. Its GTK+3 support is buggy. It doesn’t support the latest versions of the VTE library. Therefore, it’s not a good idea to use it.
You should also note that the primary goal of VTE is to be a library to support GNOME Terminal. Notably, if a feature is not needed for GNOME Terminal, it won’t be added to VTE. If it already exists, it will likely to be deprecated and removed.