write your own terminal
What’s next after you write your own text editor and mail client? How about a terminal? In fact, as a practice exercise or to learn some new skills, I’d say a terminal emulator makes for a much better target. It’s composed of many parts, but at an approachable level, making it easy to make tangible progress. In this way, I think it makes for a good introductory project. At the same time, there’s a very long tail of features that can be added to keep things interesting.
Compared to some other make your own projects, I think it’s possible to get a terminal up to self hosting status very quickly, after which one can whittle away at the rest. But the missing features don’t hamper progress, and one can get the satisfaction of using it for real. When I think about the experience of using my own text editor for anything but demo purposes, it does not spark joy.
There are many ways to choose your own adventure, but here’s one way to break it down.
The most obvious thing to notice about a terminal is it consists of a window of text on the screen. So getting some basic text, like the word hello, on the screen would be one first objective.
This could be as simple as a text area in Qt, which would let you get moving pretty quickly.
A more challenging option might be to use OpenGL and render everything manually. This certainly opens the door for fooling around with some hilariously useless graphics effects later on. A terminal is not usually one’s first thought for an example 3D application, but that possibly makes it even better as an introduction to shaders, etc. The OpenGL pipeline is easier to understand without also trying to absorb all the matrix math at the same time. This is admittedly a big step to undertake for the first time. Some more notes below.
The FreeType tutorial is easy to follow and mostly sufficient for a terminal. We don’t need to worry about kerning and ligatures, etc.
It’s also possible to write a terminal in a terminal, like tmux, but I’d save this for my second attempt. It’s very helpful to have a place to dump logging info that’s not also the screen we’re writing to.
Next we need an input handler for the keyboard. Press any key, see any key appear in the text area.
Once we can draw and update some text, we can try reading output from a simple command, like ls. We read from a file, and put the contents on the screen. As long as the text is legible, it’s good enough. Always time to adjust the font size later.
But what’s a terminal without a psuedo terminal? The forkpty function will probably be of interest.
At this point, we should be pretty close to running a shell. Change ls to sh. Does it work? At this point, if not done yet, we may need to figure out scrolling when we run ls -l /usr/bin. This is already starting to become useful for basic shell tasks.
Time to handle some subset of vt100/ansi/xterm escape sequences. This is certainly easier to handle if you’ve written a terminal app, or even just a basic
printf("^[32m") to get green text. This is a huge rabbit hole, but it’s easy to take one step at a time. Colors are a simple starting point.
A slightly more complicated application is top. It uses some of the basic cursor movement and screen clearing functions. Just implementing the H, J, K commands is enough to see visible progress.
The most complete documentation for escape sequences is the xterm reference although it’s not an easy document to read top to bottom. The wezterm documentation focuses a lot on colors, with an empty section for cursor movement, but also contains some useful links such as to the ANSI escape code on wikipedia. There’s many ways of explaining the same archaic concepts, so it helps to skim a few resources.
With a few more sequences implemented, (r to set scroll region being a notable one), more programs like vi should start to work.
And that’s a terminal emulator.
There’s still so much more to do, compared to any terminal widely used, but ticking off more boxes is pretty easy in isolation. There’s a hundred more escape sequences, but usually only a few minutes work each. Many of them end up being duplicates or variations of other codes, the result of a long twisted history.
Similarly one can add scroll back. Copy and paste just the way you like it.
I didn’t think selecting and copying text would be difficult, but I was quite surprised at how easy it came together. And the same for some other features I’d put off while I poked around trying to find more escape sequences to implement. In the end, I decided let’s just use this thing, add my required features, and it all came together.
Special keys like arrows require specific handling and weird retro encodings. Another example of a feature which one can get by without, but which also only takes a minute to implement when you finally need it.
Endless work remaining, but it’s already very useful. I alternate my time between fixing up xterm conformance, improving the performance of the scrollback, and fine tuning the font size selection algorithm.
And just now there’s monaspace which promises to challenge the simplicity of font rendering.
Overall, what I really liked about this project was that I could defer work, but without compromising the project with hacks. For a long time, I didn’t even have my character glyphs properly aligned to the baseline. I simply wrote that code a bit later, but it was the same code I would have written at any earlier point. And in the mean time, I got to see what’s over the next hill.
Selecting the OpenGL option opens up a pretty long side quest. It’s overkill for a simple terminal emulator, but also way more fun once you get going. I think it’s worthwhile, we’re here to learn after all, but not at all necessary.
A good place to start would be something really basic like the glfw triangle demo, and then manipulate it to get textures. Alas, raw OpenGL code is very heavy on boiler plate and repetition.
It really helps if you can start with an abandoned game engine that provides helpful abstractions. It’s pretty long, but this video on renderer abstraction and some others from the cherno’s game engine series help. There’s plenty of other resources; I like this series from a few years ago because of the focus on practical realities of programming, not just graphical demos.
It’s possible to skip writing the abstraction, or use something else entirely (like godot maybe?), but I found the experience very informative. And now I have some code that lets me get any other OpenGL project up and running much faster. (Although technically, I already had it and this is my other project.)
In contrast to traditional OpenGL starter projects, this one doesn’t plunge down into the rabbit hole of materials and shadow maps, etc. Unless you want it to. You can be very inefficient to start, but there’s still room to then later refine and use better techniques like instancing.
Most importantly, I don’t think learning to do 2D in OpenGL will teach you anything that won’t help you learn 3D later, if you haven’t done so.
Or go hard mode and do it in Vulkan.