Termpaint is a low level terminal interface library for character-cell terminals in the tradition of VT1xx (like xterm, etc).

It’s designed to be portable and flexible to integrate. It covers event handling and rendering.


  • works with and without an central event loop

  • robust input handling, unknown key events are gracefully filtered out

  • truecolor, soft line breaks, explicit control of trailing whitespace

  • double and curly underlines, custom underline colors

  • flexible support for program supplied additional formatting like hyperlinks

  • block, underline and bar cursor shape

  • simple grid of character cell design, support for wide characters

  • does not depend on correctly set $TERM or terminfo database

  • mouse event handling

  • tagged paste

  • mostly utf-8 based, string width routines also handle utf-16 and utf-32

  • offscreen surfaces/layers

  • interface with opaque structures designed for ABI stability (but breaking changes are still happening)

  • possible to use where allocation failure needs to be handled gracefully

  • does not use global variables where possible, can handle multiple terminals in one process

  • input parsing subset usable standalone

  • permissively licensed: Boost Software License 1.0

Does not contain:

  • ready made user interface elements (form, menu or similar)

  • window or panel abstractions

  • support for non utf-8 capable terminals

Termpaint is meant as a basic building block to build more specific libraries upon. There are a lot of different higher layer styles, so it’s cleaner to have separate libraries for this.

Minimal example

A “hello world”, using the internal default operating system integration and opinionated default setup.

See Getting started for full source.

main code
    termpaint_integration *integration;
    termpaint_terminal *terminal;
    termpaint_surface *surface;

    bool quit = false;

    integration = termpaintx_full_integration_setup_terminal_fullscreen(
                "+kbdsig +kbdsigint",
                event_callback, &quit,
    surface = termpaint_terminal_get_surface(terminal);
                0, 0,
                "Hello World",

    termpaint_terminal_flush(terminal, false);

    while (!quit) {
        if (!termpaintx_full_integration_do_iteration(integration)) {
            // some kind of error

event callback
void event_callback(void *userdata, termpaint_event *event) {
    bool *quit = userdata;
    if (event->type == TERMPAINT_EV_CHAR) {
        if (event->c.length == 1 && event->c.string[0] == 'q') {
            *quit = true;
    if (event->type == TERMPAINT_EV_KEY) {
        if (event->key.atom == termpaint_input_escape()) {
            *quit = true;

Indices and tables