Find file
Fetching contributors…
Cannot retrieve contributors at this time
219 lines (176 sloc) 8.05 KB
title: "Write your own terminal emulator"
description: |
There are so many terminal emulators. None of them may suit you.
It's super easy to write your own!
keywords: "terminal, libvte, emulator"
uuid: 186aa60a-e683-4495-b16c-899f820c74ce
"": "Source code"
I was an happy user of [rxvt-unicode][] until I got a laptop with an
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
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>
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 };
NULL, /* working directory */
command, /* command */
NULL, /* environment */
0, /* spawn flags */
NULL, NULL, /* child setup */
NULL, /* child pid */
/* 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);
You can compile it with the following command:
gcc -O2 -Wall $(pkg-config --cflags --libs vte-2.91) term.c -o term
And run it with `./term`:
![Simple VTE-based terminal][1]
[1]: [[!!images/vte-term.jpg]] "Simple VTE-based terminal"
# 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 }
&(GdkRGBA){ .alpha = 0.85 },
(const GdkRGBA[]){
}, 16);
While you can't see it on the screenshot[^compositing], this also
enables background transparency.
![Color rendering][2]
[2]: [[!!images/vte-term-2.jpg]] "Color rendering"
[^compositing]: Transparency is handled by the *composite manager*
([Compton][], in my case).
## 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_rewrap_on_resize(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,
- rewrap content when terminal size change, 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;
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"[^scratch] suits my need, it may not be
worth it. <del>[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.</del>
**UPDATED:** [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.
[^scratch]: For some definition of "scratch" since the hard work is
handled by *VTE*.
*[HiDPI]: High Dots Per Inch
*[LoDPI]: Low Dots Per Inch
[VTE]: "VTE Terminal Widget Library"
[GNOME Terminal]: "GNOME Terminal"
[evilvte]: "VTE based, highly customizable terminal emulator"
[sakura]: "sakura terminal emulator"
[termit]: "Simple terminal emulator based on vte library, extensible via Lua"
[ROXTerm]: "A tabbed, vte- (GTK+) based terminal emulator"
[Vala]: "Vala - Compiler for the GObject type system"
[documentation]: "VteTerminal API reference"
[own copy]:
[dynamic abbrev expansion]:
[Compton]: "Compton: a compositor for X11"
[tmux]: "tmux is a terminal multiplexer"
[XTerm control sequences]: "XTerm Control Sequences"
[rxvt-unicode]: "xvt-unicode is a fork of the well known terminal emulator rxvt"
{# Local Variables: #}
{# mode: markdown #}
{# indent-tabs-mode: nil #}
{# End: #}