ANSI escape codes in C
C has no string-escape for ESC — write `\x1b` (hex) or `\033` (octal). `printf` and `fputs` are byte-clean on every platform; on Windows 10+ enable virtual terminal processing via `SetConsoleMode(..., ENABLE_VIRTUAL_TERMINAL_PROCESSING)` first. For anything resembling a TUI (menus, windows, mouse) link `ncurses` rather than hand-rolling.
Recommended libraries
- ncurses
The standard C TUI library — windows, panels, menus, forms, colour pairs. Handles terminfo lookup and raw input. Abstracts you away from emitting ANSI directly.
- termios
POSIX terminal-attributes API in `<termios.h>` — switch to raw mode, disable echo, turn off ICANON to read single keystrokes. Required when reading mouse / focus / bracketed-paste escapes back from the terminal.
- termcap / terminfo (term.h)
`setupterm()` + `tigetstr()` look up capabilities like `setaf`, `cup`, `civis` and return the matching escape string for the current `$TERM`.
- notcurses
Modern ncurses successor — TrueColor, sixel/Kitty graphics, Unicode-aware. Use for new code that needs more than ncurses can give.
Idiomatic patterns
#include <stdio.h>
int main(void) {
printf("\x1b[1;31merror:\x1b[0m permission denied\n");
return 0;
}#include <stdio.h>
#include <term.h>
#include <stdlib.h>
int main(void) {
setupterm(NULL, 1, NULL);
char *red = tigetstr("setaf"); // takes one parameter
char *reset = tigetstr("sgr0");
putp(tparm(red, 1));
fputs("error: permission denied", stdout);
putp(reset);
putchar('\n');
return 0;
}
/* link with: cc prog.c -lncurses */#include <stdio.h>
#include <signal.h>
#include <unistd.h>
static void restore(int sig) { (void)sig; printf("\x1b[?25h"); _exit(0); }
int main(void) {
signal(SIGINT, restore);
signal(SIGTERM, restore);
printf("\x1b[?25l"); // hide cursor
for (int i = 0; i <= 100; i++) {
printf("\r\x1b[K%d%%", i);
fflush(stdout);
usleep(20000);
}
printf("\n");
printf("\x1b[?25h"); // show cursor
return 0;
}#include <termios.h>
#include <unistd.h>
#include <stdio.h>
int main(void) {
struct termios orig, raw;
tcgetattr(STDIN_FILENO, &orig);
raw = orig;
raw.c_lflag &= ~(ICANON | ECHO); // no line-buffering, no echo
tcsetattr(STDIN_FILENO, TCSANOW, &raw);
/* now reads of stdin deliver single bytes including ESC sequences */
int c;
while ((c = getchar()) != 'q') { printf("0x%02x ", c); fflush(stdout); }
tcsetattr(STDIN_FILENO, TCSANOW, &orig); // always restore
return 0;
}