A look at terminal emulators, part 1
This article is the first in a two-part series about terminal emulators.
- part one: features
- part two: performance
Terminals have a special place in computing history, surviving along with the command line in the face of the rising ubiquity of graphical interfaces. Terminal emulators have replaced hardware terminals, which themselves were upgrades from punched cards and toggle-switch inputs. Modern distributions now ship with a surprising variety of terminal emulators. While some people may be happy with the default terminal provided by their desktop environment, others take great pride at using exotic software for running their favorite shell or text editor. But as we'll see in this two-part series, not all terminals are created equal: they vary wildly in terms of functionality, size, and performance.
Some terminals have surprising security vulnerabilities and most have wildly different feature sets, from support for a tabbed interface to scripting. While we have covered terminal emulators in the distant past, this article provides a refresh to help readers determine which terminal they should be running in 2018. This first article compares features, while the second part evaluates performance.
Here are the terminals examined in the series:
Terminal | Debian | Fedora | Upstream | Notes |
---|---|---|---|---|
Alacritty | N/A | N/A | 6debc4f | no releases, Git head |
GNOME Terminal | 3.22.2 | 3.26.2 | 3.28.0 | uses GTK3, VTE |
Konsole | 16.12.0 | 17.12.2 | 17.12.3 | uses KDE libraries |
mlterm | 3.5.0 | 3.7.0 | 3.8.5 | |
pterm | 0.67 | 0.70 | 0.70 | PuTTY without ssh, uses GTK2 |
st | 0.6 | 0.7 | 0.8.1 | "simple terminal" |
Terminator | 1.90+bzr-1705 | 1.91 | 1.91 | uses GTK3, VTE |
urxvt | 9.22 | 9.22 | 9.22 | main rxvt fork, also known as rxvt-unicode |
Xfce Terminal | 0.8.3 | 0.8.7 | 0.8.7.2 | uses GTK3, VTE |
xterm | 327 | 330 | 331 | the original X terminal |
Those versions may be behind the latest upstream releases, as I restricted myself to stable software that managed to make it into Debian 9 (stretch) or Fedora 27. One exception to this rule is the Alacritty project, which is a poster child for GPU-accelerated terminals written in a fancy new language (Rust, in this case). I excluded web-based terminals (including those using Electron) because preliminary tests showed rather poor performance.
Unicode support
The first feature I considered is Unicode support. The first test was to display a string that was based on a string from the Wikipedia Unicode page: "é, Δ, Й, ק ,م, ๗,あ,叶, 葉, and 말". This tests whether a terminal can correctly display scripts from all over the world reliably. xterm fails to display the Arabic Mem character in its default configuration:
By default, xterm uses the classic "fixed" font which, according to Wikipedia has "substantial Unicode coverage since 1997". Something is happening here that makes the character display as a box: only by bumping the font size to "Huge" (20 points) is the character finally displayed correctly, and then other characters fail to display correctly:
Those screenshots were generated on Fedora 27 as it gave better results than Debian 9, where some older versions of the terminals (mlterm, namely) would fail to properly fallback across fonts. Thankfully, this seems to have been fixed in later versions.
Now notice the order of the string displayed by xterm: it turns out that Mem and the following character, the Semitic Qoph, are both part of right-to-left (RTL) scripts, so technically, they should be rendered right to left when displayed. Web browsers like Firefox 57 handle this correctly in the above string. A simpler test is the word "Sarah" in Hebrew (שרה). The Wikipedia page about bi-directional text explains that:
Many computer programs fail to display bi-directional text correctly. For example, the Hebrew name Sarah (שרה) is spelled: sin (ש) (which appears rightmost), then resh (ר), and finally heh (ה) (which should appear leftmost).
Many terminals fail this test: Alacritty, VTE-derivatives (GNOME Terminal, Terminator, and XFCE Terminal), urxvt, st, and xterm all show Sarah's name backwards—as if we would display it as "Haras" in English.
The other challenge with bi-directional text is how to align it, especially mixed RTL and left-to-right (LTR) text. RTL scripts should start from the right side of the terminal, but what should happen in a terminal where the prompt is in English, on the left? Most terminals do not make special provisions and align all of the text on the left, including Konsole, which otherwise displays Sarah's name in the right order. Here, pterm and mlterm seem to be sticking to the standard a little more closely and align the test string on the right.
Paste protection
The next critical feature I have identified is paste protection. While it is widely known that incantations like:
$ curl http://example.com/ | sh
are arbitrary code execution vectors, a less well-known vulnerability is that hidden commands can sneak into copy-pasted text from a web browser, even after careful review. Jann Horn's test site brilliantly shows how the apparently innocuous command: git clone git://git.kernel.org/pub/scm/utils/kup/kup.git
gets turned into this nasty mess (reformatted a bit for easier reading) when pasted from Horn's site into a terminal:
git clone /dev/null;
clear;
echo -n "Hello ";
whoami|tr -d '\n';
echo -e '!\nThat was a bad idea. Don'"'"'t copy code from websites you don'"'"'t trust! \
Here'"'"'s the first line of your /etc/passwd: ';
head -n1 /etc/passwd
git clone git://git.kernel.org/pub/scm/utils/kup/kup.git
This works by hiding the evil code in a <span>
block that's moved out
of the viewport using CSS.
Bracketed paste mode is
explicitly designed to neutralize this attack. In this mode, terminals
wrap pasted text in a pair of special escape sequences to inform the
shell of that text's origin. The shell can then ignore special editing
characters found in the pasted text. Terminals going all the way back to
the venerable xterm have supported this feature, but bracketed paste
also needs support from the shell or application running on the
terminal. For example, software using GNU
Readline (e.g.
Bash) needs the following in the ~/.inputrc
file:
set enable-bracketed-paste on
Unfortunately, Horn's test page also shows how to bypass this
protection, by including the end-of-pasted-text sequence in the pasted
text itself, thus ending the bracketed mode prematurely. This works
because some terminals do not properly filter escape sequences before
adding their own. For example, in my tests, Konsole fails to properly
escape the second test, even with .inputrc
properly configured. That
means it is easy to end up with a broken configuration, either due to an
unsupported application or misconfigured shell. This is particularly
likely when logged on to remote servers where carefully crafted
configuration files may be less common, especially if you operate many
different machines.
A good solution to this problem is the confirm-paste
plugin of the
urxvt terminal, which simply prompts before allowing any paste with a
newline character. I haven't found another terminal with such definitive
protection against the attack described by Horn.
Update: confirm-paste
also has issues, because not only newline
characters can cause a commandline to execute. For example,
control-o also causes execution in bash. This was fixed in
newer versions (9.25) however, available in bookworm.
Tabs and profiles
A popular feature is support for a tabbed interface, which we'll define broadly as a single terminal window holding multiple terminals. This feature varies across terminals: while traditional terminals like xterm do not support tabs at all, more modern implementations like Xfce Terminal, GNOME Terminal, and Konsole all have tab support. Urxvt also features tab support through a plugin. But in terms of tab support, Terminator takes the prize: not only does it support tabs, but it can also tile terminals in arbitrary patterns (as seen at the right).
Another feature of Terminator is the capability to "group" those tabs together and to send the same keystrokes to a set of terminals all at once, which provides a crude way to do mass operations on multiple servers simultaneously. A similar feature is also implemented in Konsole. Third-party software like Cluster SSH, xlax, or tmux must be used to have this functionality in other terminals.
Tabs work especially well with the notion of "profiles": for example, you may have one tab for your email, another for chat, and so on. This is well supported by Konsole and GNOME Terminal; both allow each tab to automatically start a profile. Terminator, on the other hand, supports profiles, but I could not find a way to have specific tabs automatically start a given program. Other terminals do not have the concept of "profiles" at all.
Eye candy
The last feature I considered is the terminal's look and feel. For
example, GNOME, Xfce, and urxvt support transparency, background colors,
and background images. Terminator also supports transparency, but
recently dropped support for background images, which made some people
switch away to another tiling terminal,
Tilix. I am personally happy with
only a Xresources
file setting a basic color set
(Solarized) for urxvt. Such
non-standard color themes can create problems however. Solarized, for
example, breaks
with color-using applications such as htop and
IPTraf.
While the original VT100 terminal did not support colors, newer terminals usually did, but were often limited to a 256-color palette. For power users styling their terminals, shell prompts, or status bars in more elaborate ways, this can be a frustrating limitation. A Gist keeps track of which terminals have "true color" support. My tests also confirm that st, Alacritty, and the VTE-derived terminals I tested have excellent true color support. Other terminals, however, do not fare so well and actually fail to display even 256 colors. You can see below the difference between true color support in GNOME Terminal, st, and xterm, which still does a decent job at approximating the colors using its 256-color palette. Urxvt not only fails the test but even shows blinking characters instead of colors.
Some terminals also parse the text for URL patterns to make them
clickable. This is the case for all VTE-derived terminals, while urxvt
requires the matcher
plugin to visit URLs through a mouse click or
keyboard shortcut. Other terminals reviewed do not display URLs in any
special way.
Finally, a new trend treats scrollback buffers as an optional feature.
For example, st
has no scrollback buffer at all, pointing people
toward terminal multiplexers like tmux and GNU
Screen in its
FAQ. Alacritty also lacks
scrollback buffers but will add support
soon because there was
"so much pushback on the scrollback support". Apart from those outliers,
every terminal I could find supports scrollback buffers.
Preliminary conclusions
In the next article, we'll compare performance characteristics like memory usage, speed, and latency of the terminals. But we can already see that some terminals have serious drawbacks. For example, users dealing with RTL scripts on a regular basis may be interested in mlterm and pterm, as they seem to have better support for those scripts. Konsole gets away with a good score here as well. Users who do not normally work with RTL scripts will also be happy with the other terminal choices.
In terms of paste protection, urxvt stands alone above the rest with its special feature, which I find particularly convenient. Those looking for all the bells and whistles will probably head toward terminals like Konsole. Finally, it should be noted that the VTE library provides an excellent basis for terminals to provide true color support, URL detection, and so on. So at first glance, the default terminal provided by your favorite desktop environment might just fit the bill, but we'll reserve judgment until our look at performance in the next article.
This article first appeared in the Linux Weekly News.
A lengthy discussion about this article has taken place on the LWN.net article, which might be interesting to readers here.
Also of interest of course is git repository. To quote my first comment on the article:
If you have followed other articles I wrote, you know I usually do this, pretty extensively. For example, I filed almost a dozen bug upstream while writing 2018-03-19-sigal (list here).
But this series was different: I was not reviewing a single program, but multiple. And there are a bunch that I reviewed that are not mentioned here. Filing bugs and following up on everything would have been too prohibitive. I have spent already way too much time writing those articles and couldn't afford following up on all those issues.
Besides, many of those are controversial, as you can see in the comments on the LWN site. RTL, for example, is especially tricky to agree on. Others question the very idea that paste problems are even a security in the first place. I do not really want to get into that with all those upstreams...
But of course, the door is wide open for others to file those issues now, and I would very much welcome help in that regard.
Cheers!
Before Unicode, font rendering was simple: a font could only contain 256 characters at most, so pretty much every font had every character you cared about, so you could just load it up and start drawing it to the screen.
With the advent of Unicode, nobody has the patience to draw a complete Unicode font, never mind multiple sizes. Instead, modern font-rendering systems (like browsers and GUI toolkits) will scan all the installed fonts at startup to figure out what Unicode characters they support, and then when they need to draw a particular character, they will search through all the fonts looking for the most appropriate version.
Unfortunately, xterm was designed before Unicode, so by default it just uses characters from a single font. It's not that xterm lacks Unicode support, it's just that it expects you to choose a font that covers all the characters you use on a regular basis.
xterm defaults to non-scalable ("bitmap") fonts, which means that changing the size doesn't just make the letters bigger or smaller, it switches to an entirely separate font that can have different characters available. You can configure xterm to use a scalable font, of course.
As far as I know, there's no convention about how terminal emulators should handle RTL text; I don't believe any of the original DEC terminals (the ones that terminal emulators are emulating) handled it at all.
For example, If I send a terminal the control sequence to go to location (3, 7) and print "abc", then go back to the same location and print "d", I can be sure I'll see "dbc". If I tell the terminal to go to location (3, 7) and print "שרה" then go back to the same location, and print "ר", will I see "שרר" (overwriting the character visibly at that location) or "ררה" (overwriting the character printed at that location)?
The sanest advice I've heard is that terminals should always display characters left-to-right as they're received, and applications should reverse RTL strings before sending them to the terminal, because the application is the only one that knows what the strings mean and where they should go.