A terminal UI library in C11. No external dependencies. No dynamic memory allocation. Single write() per frame.
Runs on Linux, macOS, BSDs, and embedded Linux (Raspberry Pi and smaller).
make # builds liblightui.a
make test # runs all tests
make examples # builds examples/hello and examples/sysmonRequires a C11 compiler (gcc or clang) and POSIX.
Provide your own buffers. lightui allocates nothing.
#include "lightui.h"
static lightui_cell_t front[220 * 50], back[220 * 50];
static uint8_t wbuf[LIGHTUI_WRITE_BUF_MIN(220, 50)];
static lightui_event_t evq[64];
int main(void) {
lightui_config_t cfg = {
.cells_front = front,
.cells_back = back,
.width = 220,
.height = 50,
.write_buf = wbuf,
.write_buf_size = sizeof(wbuf),
.event_queue = evq,
.event_queue_size = 64,
.esc_timeout_ms = 50,
};
lightui_t ctx;
if (lightui_init(&ctx, &cfg) != LIGHTUI_OK) return 1;
lightui_event_t ev;
while (lightui_wait_event(&ctx, &ev, -1)) {
lightui_clear_back_buffer(&ctx);
lightui_canvas_t cv; lightui_canvas_fullscreen(&cv, &ctx);
lightui_draw_box(&cv, (lightui_rect_t){2, 1, 30, 5}, LIGHTUI_BOX_ROUNDED);
lightui_draw_text(&cv, 4, 2, "hello", LIGHTUI_STYLE_DEFAULT);
lightui_flush(&ctx);
if (ev.type == LIGHTUI_EVENT_KEY && ev.key.codepoint == 'q') break;
if (ev.type == LIGHTUI_EVENT_SIGNAL) break;
}
lightui_shutdown(&ctx);
}lightui_result_t lightui_init (lightui_t *ctx, const lightui_config_t *cfg);
void lightui_shutdown(lightui_t *ctx);
uint16_t lightui_cols (const lightui_t *ctx);
uint16_t lightui_rows (const lightui_t *ctx);void lightui_clear_back_buffer(lightui_t *ctx);
void lightui_flush (lightui_t *ctx);
bool lightui_poll_event (lightui_t *ctx, lightui_event_t *ev);
bool lightui_wait_event (lightui_t *ctx, lightui_event_t *ev, int timeout_ms);void lightui_canvas_fullscreen(lightui_canvas_t *c, lightui_t *ctx);
void lightui_canvas_clip (lightui_canvas_t *c, lightui_t *ctx, lightui_rect_t r);
void lightui_draw_text (lightui_canvas_t *c, int16_t x, int16_t y, const char *s, lightui_style_t style);
void lightui_draw_box (lightui_canvas_t *c, lightui_rect_t r, lightui_box_style_t style);
void lightui_fill (lightui_canvas_t *c, lightui_rect_t r, uint32_t cp, lightui_style_t style);
void lightui_draw_hline (lightui_canvas_t *c, int16_t x, int16_t y, int16_t len, lightui_style_t style);
void lightui_draw_vline (lightui_canvas_t *c, int16_t x, int16_t y, int16_t len, lightui_style_t style);lightui_result_t lightui_plugin_register(lightui_t *ctx, const lightui_plugin_t *plugin);typedef struct {
const char *name;
uint32_t version;
void (*init) (lightui_t *ctx, const lightui_allocator_t *alloc, void **state);
void (*destroy) (lightui_t *ctx, void *state);
void (*render) (lightui_t *ctx, lightui_canvas_t *canvas, void *state);
bool (*on_event) (lightui_t *ctx, const lightui_event_t *ev, void *state);
void (*on_resize)(lightui_t *ctx, uint16_t cols, uint16_t rows, void *state);
} lightui_plugin_t;Plugins registered first get highest event priority and render at the bottom layer. The app renders on top.
// ev.type is one of:
LIGHTUI_EVENT_KEY // ev.key.codepoint, ev.key.mods
LIGHTUI_EVENT_MOUSE // ev.mouse.x, .y, .btn, .mods, .kind
LIGHTUI_EVENT_RESIZE // ev.resize.cols, ev.resize.rows
LIGHTUI_EVENT_SIGNAL // ev.signal.signumMouse reporting uses xterm SGR protocol (ESC[<...M). Coordinates are 0-based.
- One active
lightui_tper process (signal pipe is process-global). - Buffer dimensions are fixed at init time.
lightui_cols()/lightui_rows()return init-time values; live terminal size comes viaLIGHTUI_EVENT_RESIZE. - Maximum 256 rows (
LIGHTUI_MAX_ROWS), 512 columns (LIGHTUI_MAX_COLS).