terminal UI frameworks are a luxury and a lie. For tonarchy, the Arch
installer-in-C, I wanted the fanciest-possible thing with no dependencies
and no ncurses. What you discover: a TUI is just escape codes and a raw tty.
Getting the terminal to shut up
Terminals echo what you type. A TUI needs raw mode via termios:
#include <termios.h>
static struct termios oldtio;
tcgetattr(STDIN_FILENO, &oldtio);
struct termios raw = oldtio;
raw.c_lflag &= ~(ICANON | ECHO | ISIG);
raw.c_cc[VMIN] = 1;
raw.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
Now one read() blocks per keystroke, with no newline buffet. Welcome to a
game engine.
Drawing is printf with numbers
Screen drawing = cursor control + colors:
#define RESET "\x1b[0m"
#define GREEN "\x1b[32m"
#define HOME() printf("\x1b[H")
\x1b[H— home cursor.\x1b[2K— clear line.- Menus = a list of rows, one
printfper row, redraw the whole thing.
The canvas discipline
No flicker hack, just: don’t draw before the screen clears. Render into a
buffer, write() it once, flush. Reads on stdin, eats arrow keys from a 3-byte
escape sequence.
What lessons came back
- Read the sequence, not the key: arrows arrive as
ESC [ A, notA. tcsetattrrestore in a signal handler if you don’t want a corpse terminal.- Portability is a lie — Linux vs TERM shapes every escape you can trust.
if (seq_len == 3 && seq[0]==27 && seq[1]=='[' && seq[2]=='A')
draw_arrow_up(); /* it's that simple and that fragile */
For the project this is pure joy. A C TUI with zero deps is the closest thing to a firmware you compile for your own terminal.