-
-
Notifications
You must be signed in to change notification settings - Fork 0
Porting from termbox2
libterm is termbox2 with every symbol renamed (tb_ → lt_, TB_ → LT_). If you have existing termbox2 code, you have two ways to move it onto libterm:
-
The compat header — change one
#includeand keep yourtb_/TB_code. Fastest path; covered below. -
A full rename — switch to the
lt_/LT_names directly. Best long-term; the rest of this wiki documents that API.
This page covers the compat header and the one behavior difference you'll hit in practice: Ctrl+letter keys.
compat/termbox2.h (in the libterm repo) maps the supported termbox2 API onto libterm. It's a single self-contained header — put it on your include path and replace your include:
/* was: #include "termbox2.h" */
#include "termbox2.h" /* now resolved to libterm's compat/termbox2.h */cc myapp.c -I path/to/libterm/compat -lterm -o myappIt includes <libterm/libterm.h>, so it works against the normally-installed library. Your existing calls — tb_init, tb_present, tb_poll_event, tb_set_cell, TB_KEY_ENTER, struct tb_event, uintattr_t, … — compile and link unchanged.
- Every termbox2 function that has a libterm equivalent — lifecycle, rendering, the print/send helpers, input, output modes, the UTF-8 helpers, introspection.
-
All
TB_KEY_*and everyTB_*constant — keys, modifiers, events, output/input modes, colors, attributes, return codes. -
struct tb_cell,struct tb_event,uintattr_t— aliased to libterm's types. (tb_cellis libterm's fixed 16-byte cell; the per-cellech/nech/cechgrapheme pointers from termbox2 are not present — usetb_set_cell_ex/tb_extend_cellfor grapheme clusters.)
-
tb_get_cell— libterm copies a cell into a struct rather than handing back a live buffer pointer. The compat wrapper returns a pointer to an internal snapshot, valid until your nexttb_get_cellcall, and reads the back buffer only (back == 0, the front buffer, returnsLT_ERR).
These have no libterm equivalent; using one is a compile error whose message names the replacement:
| termbox2 | use instead |
|---|---|
tb_init_rwfd |
lt_init_fd / lt_init_file (libterm is single-fd by design) |
tb_has_truecolor |
lt_detect_color_depth |
tb_cell_buffer |
tb_get_cell (no raw back-buffer pointer) |
tb_set_func |
— (a custom parser hook; deprecated upstream) |
tb_key_i |
— (no terminfo capability table) |
This is the difference that trips up almost every port. Ctrl-S, Ctrl-Q, Ctrl-A … may appear to "do nothing" after you switch to the compat header.
Why. libterm defaults to a modern keyboard model. In it, Ctrl+letter is reported as the letter in ev.ch plus the LT_MOD_CTRL modifier, with ev.key == 0:
press Ctrl-S → ev.key = 0, ev.ch = 's', ev.mod = LT_MOD_CTRL
termbox2 (and most ported code) instead checks the legacy control byte in ev.key:
if (ev.key == TB_KEY_CTRL_S) save(); /* never true under the modern model */TB_KEY_CTRL_S is 0x13, but ev.key is 0 — so the check never fires, and the 's' falls through to your text-insert path. (This is not terminal flow control: libterm's raw mode disables IXON, so Ctrl-S/Ctrl-Q do reach your program.)
You can fix this two ways.
Ask libterm for termbox2's control-byte model right after init:
tb_init();
tb_set_input_mode(LT_INPUT_COMPAT); /* Ctrl+letter -> ev.key = the control byte */Now ev.key == TB_KEY_CTRL_S works exactly as it did under termbox2, and the rest of your tb_/TB_ code is untouched. This is the fastest, most faithful port — pick it if you just want your program running.
LT_INPUT_COMPAT also restores termbox2's other legacy behaviors: a lone ESC and an Alt-combo as two events (tunable with LT_INPUT_ESC / LT_INPUT_ALT), Shift+letter as a bare uppercase ch, and kitty negotiation off.
Leave libterm in its default (don't set LT_INPUT_COMPAT) and update your Ctrl+letter checks to read ch + mod instead of key:
struct tb_event ev;
tb_poll_event(&ev);
if (ev.type == TB_EVENT_KEY) {
if ((ev.mod & TB_MOD_CTRL) && ev.ch == 's') { save(); }
else if ((ev.mod & TB_MOD_CTRL) && ev.ch == 'q') { quit(); }
/* arrows, Enter, Backspace, Esc are UNCHANGED — still named keys in ev.key */
else if (ev.key == TB_KEY_ARROW_UP) { /* ... */ }
else if (ev.key == TB_KEY_ENTER) { /* ... */ }
else if (ev.ch != 0) { insert((char)ev.ch); }
}(TB_MOD_CTRL is the same value as LT_MOD_CTRL; Ctrl+letter always reports the lowercase letter in the modern model.)
Only Ctrl+letter and Shift+letter handling changes — named keys (arrows, F-keys, Enter, Backspace, Esc, nav) arrive in ev.key in both models, so those branches stay as they are.
Why prefer the modern model? On a kitty-capable terminal (negotiated automatically) or on Windows it is strictly more capable:
-
Ctrl-Istays distinct fromTab,Ctrl-MfromEnter,Ctrl-HfromBackspace— the legacy byte model collapses these. - Modifiers arrive on every key, including
Shift+letter; the typed character is layout-translated intoch. -
Bare modifier presses, key-release, and key-repeat become available (
ev.action; releases withLT_INPUT_RELEASE).
The trade-off: on a legacy POSIX terminal, Shift+letter / bare modifiers / releases simply aren't on the wire, so they're unavailable there regardless — a terminal limitation libterm can't paper over. See Input & Events for the full model.
Fix A — LT_INPUT_COMPAT
|
Fix B — modern model | |
|---|---|---|
| Change to your code | one line | rewrite Ctrl+letter checks |
Keeps ev.key == TB_KEY_CTRL_*
|
yes | no (use ev.ch + ev.mod) |
Ctrl-I vs Tab disambiguation |
no | yes (capable terminals) |
| Modifiers on all keys, release/repeat | no | yes (capable terminals) |
| Best for | a quick, faithful drop-in | code you'll keep growing |
examples/editor.c in the repo is a small text editor written entirely against the compat header (a termbox2-idiom program). It uses Fix A — a single tb_set_input_mode(LT_INPUT_COMPAT) after tb_init — so its Ctrl-S (save) and Ctrl-Q (quit) bindings work. It's the most complete reference for what a real drop-in looks like.
- Input & Events — the event struct, the modern vs. compat models in full, modifiers, Windows specifics.
-
API Reference — the
lt_API the compat names map onto. - Roadmap — per-symbol termbox2 parity status.