1/*
   2MIT License
   3
   4Copyright (c) 2010-2020 nsf <no.smile.face@gmail.com>
   5              2015-2024 Adam Saponara <as@php.net>
   6
   7Permission is hereby granted, free of charge, to any person obtaining a copy
   8of this software and associated documentation files (the "Software"), to deal
   9in the Software without restriction, including without limitation the rights
  10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11copies of the Software, and to permit persons to whom the Software is
  12furnished to do so, subject to the following conditions:
  13
  14The above copyright notice and this permission notice shall be included in all
  15copies or substantial portions of the Software.
  16
  17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23SOFTWARE.
  24*/
  25
  26#ifndef TERMBOX_H_INCL
  27#define TERMBOX_H_INCL
  28
  29#ifndef _XOPEN_SOURCE
  30#define _XOPEN_SOURCE
  31#endif
  32
  33#ifndef _DEFAULT_SOURCE
  34#define _DEFAULT_SOURCE
  35#endif
  36
  37#include <errno.h>
  38#include <fcntl.h>
  39#include <limits.h>
  40#include <signal.h>
  41#include <stdarg.h>
  42#include <stdint.h>
  43#include <stdio.h>
  44#include <stdlib.h>
  45#include <string.h>
  46#include <sys/ioctl.h>
  47#include <sys/select.h>
  48#include <sys/stat.h>
  49#include <sys/time.h>
  50#include <sys/types.h>
  51#include <termios.h>
  52#include <unistd.h>
  53#include <wchar.h>
  54#include <wctype.h>
  55
  56#ifdef PATH_MAX
  57#define TB_PATH_MAX PATH_MAX
  58#else
  59#define TB_PATH_MAX 4096
  60#endif
  61
  62#ifdef __cplusplus
  63extern "C" {
  64#endif
  65
  66// __ffi_start
  67
  68#define TB_VERSION_STR "2.5.0-dev"
  69
  70/* The following compile-time options are supported:
  71 *
  72 *     TB_OPT_ATTR_W: Integer width of fg and bg attributes. Valid values
  73 *                    (assuming system support) are 16, 32, and 64. (See
  74 *                    uintattr_t). 32 or 64 enables output mode
  75 *                    TB_OUTPUT_TRUECOLOR. 64 enables additional style
  76 *                    attributes. (See tb_set_output_mode.) Larger values
  77 *                    consume more memory in exchange for more features.
  78 *                    Defaults to 16.
  79 *
  80 *        TB_OPT_EGC: If set, enable extended grapheme cluster support
  81 *                    (tb_extend_cell, tb_set_cell_ex). Consumes more memory.
  82 *                    Defaults off.
  83 *
  84 * TB_OPT_PRINTF_BUF: Write buffer size for printf operations. Represents the
  85 *                    largest string that can be sent in one call to tb_print*
  86 *                    and tb_send* functions. Defaults to 4096.
  87 *
  88 *   TB_OPT_READ_BUF: Read buffer size for tty reads. Defaults to 64.
  89 *
  90 *  TB_OPT_TRUECOLOR: Deprecated. Sets TB_OPT_ATTR_W to 32 if not already set.
  91 */
  92
  93#if defined(TB_LIB_OPTS) || 0 // __tb_lib_opts
  94/* Ensure consistent compile-time options when using as a shared library */
  95#undef TB_OPT_ATTR_W
  96#undef TB_OPT_EGC
  97#undef TB_OPT_PRINTF_BUF
  98#undef TB_OPT_READ_BUF
  99#define TB_OPT_ATTR_W 64
 100#define TB_OPT_EGC
 101#endif
 102
 103/* Ensure sane `TB_OPT_ATTR_W` (16, 32, or 64) */
 104#if defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 16
 105#elif defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 32
 106#elif defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 64
 107#else
 108#undef TB_OPT_ATTR_W
 109#if defined TB_OPT_TRUECOLOR // Deprecated. Back-compat for old flag.
 110#define TB_OPT_ATTR_W 32
 111#else
 112#define TB_OPT_ATTR_W 16
 113#endif
 114#endif
 115
 116/* ASCII key constants (`tb_event.key`) */
 117#define TB_KEY_CTRL_TILDE       0x00
 118#define TB_KEY_CTRL_2           0x00 // clash with `CTRL_TILDE`
 119#define TB_KEY_CTRL_A           0x01
 120#define TB_KEY_CTRL_B           0x02
 121#define TB_KEY_CTRL_C           0x03
 122#define TB_KEY_CTRL_D           0x04
 123#define TB_KEY_CTRL_E           0x05
 124#define TB_KEY_CTRL_F           0x06
 125#define TB_KEY_CTRL_G           0x07
 126#define TB_KEY_BACKSPACE        0x08
 127#define TB_KEY_CTRL_H           0x08 // clash with `CTRL_BACKSPACE`
 128#define TB_KEY_TAB              0x09
 129#define TB_KEY_CTRL_I           0x09 // clash with `TAB`
 130#define TB_KEY_CTRL_J           0x0a
 131#define TB_KEY_CTRL_K           0x0b
 132#define TB_KEY_CTRL_L           0x0c
 133#define TB_KEY_ENTER            0x0d
 134#define TB_KEY_CTRL_M           0x0d // clash with `ENTER`
 135#define TB_KEY_CTRL_N           0x0e
 136#define TB_KEY_CTRL_O           0x0f
 137#define TB_KEY_CTRL_P           0x10
 138#define TB_KEY_CTRL_Q           0x11
 139#define TB_KEY_CTRL_R           0x12
 140#define TB_KEY_CTRL_S           0x13
 141#define TB_KEY_CTRL_T           0x14
 142#define TB_KEY_CTRL_U           0x15
 143#define TB_KEY_CTRL_V           0x16
 144#define TB_KEY_CTRL_W           0x17
 145#define TB_KEY_CTRL_X           0x18
 146#define TB_KEY_CTRL_Y           0x19
 147#define TB_KEY_CTRL_Z           0x1a
 148#define TB_KEY_ESC              0x1b
 149#define TB_KEY_CTRL_LSQ_BRACKET 0x1b // clash with 'ESC'
 150#define TB_KEY_CTRL_3           0x1b // clash with 'ESC'
 151#define TB_KEY_CTRL_4           0x1c
 152#define TB_KEY_CTRL_BACKSLASH   0x1c // clash with 'CTRL_4'
 153#define TB_KEY_CTRL_5           0x1d
 154#define TB_KEY_CTRL_RSQ_BRACKET 0x1d // clash with 'CTRL_5'
 155#define TB_KEY_CTRL_6           0x1e
 156#define TB_KEY_CTRL_7           0x1f
 157#define TB_KEY_CTRL_SLASH       0x1f // clash with 'CTRL_7'
 158#define TB_KEY_CTRL_UNDERSCORE  0x1f // clash with 'CTRL_7'
 159#define TB_KEY_SPACE            0x20
 160#define TB_KEY_BACKSPACE2       0x7f
 161#define TB_KEY_CTRL_8           0x7f // clash with 'BACKSPACE2'
 162
 163#define tb_key_i(i)             0xffff - (i)
 164/* Terminal-dependent key constants (`tb_event.key`) and terminfo caps */
 165/* BEGIN codegen h */
 166/* Produced by ./codegen.sh on Tue, 03 Sep 2024 04:17:47 +0000 */
 167#define TB_KEY_F1               (0xffff - 0)
 168#define TB_KEY_F2               (0xffff - 1)
 169#define TB_KEY_F3               (0xffff - 2)
 170#define TB_KEY_F4               (0xffff - 3)
 171#define TB_KEY_F5               (0xffff - 4)
 172#define TB_KEY_F6               (0xffff - 5)
 173#define TB_KEY_F7               (0xffff - 6)
 174#define TB_KEY_F8               (0xffff - 7)
 175#define TB_KEY_F9               (0xffff - 8)
 176#define TB_KEY_F10              (0xffff - 9)
 177#define TB_KEY_F11              (0xffff - 10)
 178#define TB_KEY_F12              (0xffff - 11)
 179#define TB_KEY_INSERT           (0xffff - 12)
 180#define TB_KEY_DELETE           (0xffff - 13)
 181#define TB_KEY_HOME             (0xffff - 14)
 182#define TB_KEY_END              (0xffff - 15)
 183#define TB_KEY_PGUP             (0xffff - 16)
 184#define TB_KEY_PGDN             (0xffff - 17)
 185#define TB_KEY_ARROW_UP         (0xffff - 18)
 186#define TB_KEY_ARROW_DOWN       (0xffff - 19)
 187#define TB_KEY_ARROW_LEFT       (0xffff - 20)
 188#define TB_KEY_ARROW_RIGHT      (0xffff - 21)
 189#define TB_KEY_BACK_TAB         (0xffff - 22)
 190#define TB_KEY_MOUSE_LEFT       (0xffff - 23)
 191#define TB_KEY_MOUSE_RIGHT      (0xffff - 24)
 192#define TB_KEY_MOUSE_MIDDLE     (0xffff - 25)
 193#define TB_KEY_MOUSE_RELEASE    (0xffff - 26)
 194#define TB_KEY_MOUSE_WHEEL_UP   (0xffff - 27)
 195#define TB_KEY_MOUSE_WHEEL_DOWN (0xffff - 28)
 196
 197#define TB_CAP_F1               0
 198#define TB_CAP_F2               1
 199#define TB_CAP_F3               2
 200#define TB_CAP_F4               3
 201#define TB_CAP_F5               4
 202#define TB_CAP_F6               5
 203#define TB_CAP_F7               6
 204#define TB_CAP_F8               7
 205#define TB_CAP_F9               8
 206#define TB_CAP_F10              9
 207#define TB_CAP_F11              10
 208#define TB_CAP_F12              11
 209#define TB_CAP_INSERT           12
 210#define TB_CAP_DELETE           13
 211#define TB_CAP_HOME             14
 212#define TB_CAP_END              15
 213#define TB_CAP_PGUP             16
 214#define TB_CAP_PGDN             17
 215#define TB_CAP_ARROW_UP         18
 216#define TB_CAP_ARROW_DOWN       19
 217#define TB_CAP_ARROW_LEFT       20
 218#define TB_CAP_ARROW_RIGHT      21
 219#define TB_CAP_BACK_TAB         22
 220#define TB_CAP__COUNT_KEYS      23
 221#define TB_CAP_ENTER_CA         23
 222#define TB_CAP_EXIT_CA          24
 223#define TB_CAP_SHOW_CURSOR      25
 224#define TB_CAP_HIDE_CURSOR      26
 225#define TB_CAP_CLEAR_SCREEN     27
 226#define TB_CAP_SGR0             28
 227#define TB_CAP_UNDERLINE        29
 228#define TB_CAP_BOLD             30
 229#define TB_CAP_BLINK            31
 230#define TB_CAP_ITALIC           32
 231#define TB_CAP_REVERSE          33
 232#define TB_CAP_ENTER_KEYPAD     34
 233#define TB_CAP_EXIT_KEYPAD      35
 234#define TB_CAP_DIM              36
 235#define TB_CAP_INVISIBLE        37
 236#define TB_CAP__COUNT           38
 237/* END codegen h */
 238
 239/* Some hard-coded caps */
 240#define TB_HARDCAP_ENTER_MOUSE  "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h"
 241#define TB_HARDCAP_EXIT_MOUSE   "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l"
 242#define TB_HARDCAP_STRIKEOUT    "\x1b[9m"
 243#define TB_HARDCAP_UNDERLINE_2  "\x1b[21m"
 244#define TB_HARDCAP_OVERLINE     "\x1b[53m"
 245
 246/* Colors (numeric) and attributes (bitwise) (`tb_cell.fg`, `tb_cell.bg`) */
 247#define TB_DEFAULT              0x0000
 248#define TB_BLACK                0x0001
 249#define TB_RED                  0x0002
 250#define TB_GREEN                0x0003
 251#define TB_YELLOW               0x0004
 252#define TB_BLUE                 0x0005
 253#define TB_MAGENTA              0x0006
 254#define TB_CYAN                 0x0007
 255#define TB_WHITE                0x0008
 256
 257#if TB_OPT_ATTR_W == 16
 258#define TB_BOLD      0x0100
 259#define TB_UNDERLINE 0x0200
 260#define TB_REVERSE   0x0400
 261#define TB_ITALIC    0x0800
 262#define TB_BLINK     0x1000
 263#define TB_HI_BLACK  0x2000
 264#define TB_BRIGHT    0x4000
 265#define TB_DIM       0x8000
 266#define TB_256_BLACK TB_HI_BLACK // `TB_256_BLACK` is deprecated
 267#else
 268// `TB_OPT_ATTR_W` is 32 or 64
 269#define TB_BOLD                0x01000000
 270#define TB_UNDERLINE           0x02000000
 271#define TB_REVERSE             0x04000000
 272#define TB_ITALIC              0x08000000
 273#define TB_BLINK               0x10000000
 274#define TB_HI_BLACK            0x20000000
 275#define TB_BRIGHT              0x40000000
 276#define TB_DIM                 0x80000000
 277#define TB_TRUECOLOR_BOLD      TB_BOLD // `TB_TRUECOLOR_*` is deprecated
 278#define TB_TRUECOLOR_UNDERLINE TB_UNDERLINE
 279#define TB_TRUECOLOR_REVERSE   TB_REVERSE
 280#define TB_TRUECOLOR_ITALIC    TB_ITALIC
 281#define TB_TRUECOLOR_BLINK     TB_BLINK
 282#define TB_TRUECOLOR_BLACK     TB_HI_BLACK
 283#endif
 284
 285#if TB_OPT_ATTR_W == 64
 286#define TB_STRIKEOUT   0x0000000100000000
 287#define TB_UNDERLINE_2 0x0000000200000000
 288#define TB_OVERLINE    0x0000000400000000
 289#define TB_INVISIBLE   0x0000000800000000
 290#endif
 291
 292/* Event types (`tb_event.type`) */
 293#define TB_EVENT_KEY        1
 294#define TB_EVENT_RESIZE     2
 295#define TB_EVENT_MOUSE      3
 296
 297/* Key modifiers (bitwise) (`tb_event.mod`) */
 298#define TB_MOD_ALT          1
 299#define TB_MOD_CTRL         2
 300#define TB_MOD_SHIFT        4
 301#define TB_MOD_MOTION       8
 302
 303/* Input modes (bitwise) (`tb_set_input_mode`) */
 304#define TB_INPUT_CURRENT    0
 305#define TB_INPUT_ESC        1
 306#define TB_INPUT_ALT        2
 307#define TB_INPUT_MOUSE      4
 308
 309/* Output modes (`tb_set_output_mode`) */
 310#define TB_OUTPUT_CURRENT   0
 311#define TB_OUTPUT_NORMAL    1
 312#define TB_OUTPUT_256       2
 313#define TB_OUTPUT_216       3
 314#define TB_OUTPUT_GRAYSCALE 4
 315#if TB_OPT_ATTR_W >= 32
 316#define TB_OUTPUT_TRUECOLOR 5
 317#endif
 318
 319/* Common function return values unless otherwise noted.
 320 *
 321 * Library behavior is undefined after receiving `TB_ERR_MEM`. Callers may
 322 * attempt reinitializing by freeing memory, invoking `tb_shutdown`, then
 323 * `tb_init`.
 324 */
 325#define TB_OK                   0
 326#define TB_ERR                  -1
 327#define TB_ERR_NEED_MORE        -2
 328#define TB_ERR_INIT_ALREADY     -3
 329#define TB_ERR_INIT_OPEN        -4
 330#define TB_ERR_MEM              -5
 331#define TB_ERR_NO_EVENT         -6
 332#define TB_ERR_NO_TERM          -7
 333#define TB_ERR_NOT_INIT         -8
 334#define TB_ERR_OUT_OF_BOUNDS    -9
 335#define TB_ERR_READ             -10
 336#define TB_ERR_RESIZE_IOCTL     -11
 337#define TB_ERR_RESIZE_PIPE      -12
 338#define TB_ERR_RESIZE_SIGACTION -13
 339#define TB_ERR_POLL             -14
 340#define TB_ERR_TCGETATTR        -15
 341#define TB_ERR_TCSETATTR        -16
 342#define TB_ERR_UNSUPPORTED_TERM -17
 343#define TB_ERR_RESIZE_WRITE     -18
 344#define TB_ERR_RESIZE_POLL      -19
 345#define TB_ERR_RESIZE_READ      -20
 346#define TB_ERR_RESIZE_SSCANF    -21
 347#define TB_ERR_CAP_COLLISION    -22
 348
 349#define TB_ERR_SELECT           TB_ERR_POLL
 350#define TB_ERR_RESIZE_SELECT    TB_ERR_RESIZE_POLL
 351
 352/* Deprecated. Function types to be used with `tb_set_func`. */
 353#define TB_FUNC_EXTRACT_PRE     0
 354#define TB_FUNC_EXTRACT_POST    1
 355
 356/* Define this to set the size of the buffer used in `tb_printf`
 357 * and `tb_sendf`
 358 */
 359#ifndef TB_OPT_PRINTF_BUF
 360#define TB_OPT_PRINTF_BUF 4096
 361#endif
 362
 363/* Define this to set the size of the read buffer used when reading
 364 * from the tty
 365 */
 366#ifndef TB_OPT_READ_BUF
 367#define TB_OPT_READ_BUF 64
 368#endif
 369
 370/* Define this for limited back compat with termbox v1 */
 371#ifdef TB_OPT_V1_COMPAT
 372#define tb_change_cell          tb_set_cell
 373#define tb_put_cell(x, y, c)    tb_set_cell((x), (y), (c)->ch, (c)->fg, (c)->bg)
 374#define tb_set_clear_attributes tb_set_clear_attrs
 375#define tb_select_input_mode    tb_set_input_mode
 376#define tb_select_output_mode   tb_set_output_mode
 377#endif
 378
 379/* Define these to swap in a different allocator */
 380#ifndef tb_malloc
 381#define tb_malloc  malloc
 382#define tb_realloc realloc
 383#define tb_free    free
 384#endif
 385
 386#if TB_OPT_ATTR_W == 64
 387typedef uint64_t uintattr_t;
 388#elif TB_OPT_ATTR_W == 32
 389typedef uint32_t uintattr_t;
 390#else // 16
 391typedef uint16_t uintattr_t;
 392#endif
 393
 394/* A cell in a 2d grid representing the terminal screen.
 395 *
 396 * The terminal screen is represented as 2d array of cells. The structure is
 397 * optimized for dealing with single-width (`wcwidth==1`) Unicode codepoints,
 398 * however some support for grapheme clusters (e.g., combining diacritical
 399 * marks) and wide codepoints (e.g., Hiragana) is provided through `ech`,
 400 * `nech`, and `cech` via `tb_set_cell_ex`. `ech` is only valid when `nech>0`,
 401 * otherwise `ch` is used.
 402 *
 403 * For non-single-width codepoints, given `N=wcwidth(ch)/wcswidth(ech)`:
 404 *
 405 * when `N==0`: termbox forces a single-width cell. Callers should avoid this
 406 *              if aiming to render text accurately. Callers may use
 407 *              `tb_set_cell_ex` or `tb_print*` to render `N==0` combining
 408 *              characters.
 409 *
 410 *  when `N>1`: termbox zeroes out the following `N-1` cells and skips sending
 411 *              them to the tty. So, e.g., if the caller sets `x=0,y=0` to an
 412 *              `N==2` codepoint, the caller's next set should be at `x=2,y=0`.
 413 *              Anything set at `x=1,y=0` will be ignored. If there are not
 414 *              enough columns remaining on the line to render `N` width, spaces
 415 *              are sent instead.
 416 *
 417 * See `tb_present` for implementation.
 418 */
 419struct tb_cell {
 420    uint32_t ch;   // a Unicode codepoint
 421    uintattr_t fg; // bitwise foreground attributes
 422    uintattr_t bg; // bitwise background attributes
 423#ifdef TB_OPT_EGC
 424    uint32_t *ech; // a grapheme cluster of Unicode codepoints, 0-terminated
 425    size_t nech;   // num elements in ech, 0 means use ch instead of ech
 426    size_t cech;   // num elements allocated for ech
 427#endif
 428};
 429
 430/* An incoming event from the tty.
 431 *
 432 * Given the event type, the following fields are relevant:
 433 *
 434 *    when `TB_EVENT_KEY`: `key` xor `ch` (one will be zero) and `mod`. Note
 435 *                         there is overlap between `TB_MOD_CTRL` and
 436 *                         `TB_KEY_CTRL_*`. `TB_MOD_CTRL` and `TB_MOD_SHIFT` are
 437 *                         only set as modifiers to `TB_KEY_ARROW_*`.
 438 *
 439 * when `TB_EVENT_RESIZE`: `w` and `h`
 440 *
 441 *  when `TB_EVENT_MOUSE`: `key` (`TB_KEY_MOUSE_*`), `x`, and `y`
 442 */
 443struct tb_event {
 444    uint8_t type; // one of `TB_EVENT_*` constants
 445    uint8_t mod;  // bitwise `TB_MOD_*` constants
 446    uint16_t key; // one of `TB_KEY_*` constants
 447    uint32_t ch;  // a Unicode codepoint
 448    int32_t w;    // resize width
 449    int32_t h;    // resize height
 450    int32_t x;    // mouse x
 451    int32_t y;    // mouse y
 452};
 453
 454/* Initialize the termbox library. This function should be called before any
 455 * other functions. `tb_init` is equivalent to `tb_init_file("/dev/tty")`. After
 456 * successful initialization, the library must be finalized using `tb_shutdown`.
 457 */
 458int tb_init(void);
 459int tb_init_file(const char *path);
 460int tb_init_fd(int ttyfd);
 461int tb_init_rwfd(int rfd, int wfd);
 462int tb_shutdown(void);
 463
 464/* Return the size of the internal back buffer (which is the same as terminal's
 465 * window size in rows and columns). The internal buffer can be resized after
 466 * `tb_clear` or `tb_present` calls. Both dimensions have an unspecified
 467 * negative value when called before `tb_init` or after `tb_shutdown`.
 468 */
 469int tb_width(void);
 470int tb_height(void);
 471
 472/* Clear the internal back buffer using `TB_DEFAULT` or the attributes set by
 473 * `tb_set_clear_attrs`.
 474 */
 475int tb_clear(void);
 476int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg);
 477
 478/* Synchronize the internal back buffer with the terminal by writing to tty. */
 479int tb_present(void);
 480
 481/* Clear the internal front buffer effectively forcing a complete re-render of
 482 * the back buffer to the tty. It is not necessary to call this under normal
 483 * circumstances. */
 484int tb_invalidate(void);
 485
 486/* Set the position of the cursor. Upper-left cell is (0, 0). */
 487int tb_set_cursor(int cx, int cy);
 488int tb_hide_cursor(void);
 489
 490/* Set cell contents in the internal back buffer at the specified position.
 491 *
 492 * Use `tb_set_cell_ex` for rendering grapheme clusters (e.g., combining
 493 * diacritical marks).
 494 *
 495 * Calling `tb_set_cell(x, y, ch, fg, bg)` is equivalent to
 496 * `tb_set_cell_ex(x, y, &ch, 1, fg, bg)`.
 497 *
 498 * `tb_extend_cell` is a shortcut for appending 1 codepoint to `tb_cell.ech`.
 499 *
 500 * Non-printable (`iswprint(3)`) codepoints are replaced with `U+FFFD` at render
 501 * time.
 502 */
 503int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg);
 504int tb_set_cell_ex(int x, int y, uint32_t *ch, size_t nch, uintattr_t fg,
 505    uintattr_t bg);
 506int tb_extend_cell(int x, int y, uint32_t ch);
 507
 508/* Set the input mode. Termbox has two input modes:
 509 *
 510 * 1. `TB_INPUT_ESC`
 511 *    When escape (`\x1b`) is in the buffer and there's no match for an escape
 512 *    sequence, a key event for `TB_KEY_ESC` is returned.
 513 *
 514 * 2. `TB_INPUT_ALT`
 515 *    When escape (`\x1b`) is in the buffer and there's no match for an escape
 516 *    sequence, the next keyboard event is returned with a `TB_MOD_ALT`
 517 *    modifier.
 518 *
 519 * You can also apply `TB_INPUT_MOUSE` via bitwise OR operation to either of the
 520 * modes (e.g., `TB_INPUT_ESC | TB_INPUT_MOUSE`) to receive `TB_EVENT_MOUSE`
 521 * events. If none of the main two modes were set, but the mouse mode was,
 522 * `TB_INPUT_ESC` is used. If for some reason you've decided to use
 523 * `TB_INPUT_ESC | TB_INPUT_ALT`, it will behave as if only `TB_INPUT_ESC` was
 524 * selected.
 525 *
 526 * If mode is `TB_INPUT_CURRENT`, return the current input mode.
 527 *
 528 * The default input mode is `TB_INPUT_ESC`.
 529 */
 530int tb_set_input_mode(int mode);
 531
 532/* Set the output mode. Termbox has multiple output modes:
 533 *
 534 * 1. `TB_OUTPUT_NORMAL`     => [0..8]
 535 *
 536 *    This mode provides 8 different colors:
 537 *      `TB_BLACK`, `TB_RED`, `TB_GREEN`, `TB_YELLOW`,
 538 *      `TB_BLUE`, `TB_MAGENTA`, `TB_CYAN`, `TB_WHITE`
 539 *
 540 *    Plus `TB_DEFAULT` which skips sending a color code (i.e., uses the
 541 *    terminal's default color).
 542 *
 543 *    Colors (including `TB_DEFAULT`) may be bitwise OR'd with attributes:
 544 *      `TB_BOLD`, `TB_UNDERLINE`, `TB_REVERSE`, `TB_ITALIC`, `TB_BLINK`,
 545 *      `TB_BRIGHT`, `TB_DIM`
 546 *
 547 *    The following style attributes are also available if compiled with
 548 *    `TB_OPT_ATTR_W` set to 64:
 549 *      `TB_STRIKEOUT`, `TB_UNDERLINE_2`, `TB_OVERLINE`, `TB_INVISIBLE`
 550 *
 551 *    As in all modes, the value 0 is interpreted as `TB_DEFAULT` for
 552 *    convenience.
 553 *
 554 *    Some notes: `TB_REVERSE` and `TB_BRIGHT` can be applied as either `fg` or
 555 *    `bg` attributes for the same effect. The rest of the attributes apply to
 556 *    `fg` only and are ignored as `bg` attributes.
 557 *
 558 *    Example usage: `tb_set_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED)`
 559 *
 560 * 2. `TB_OUTPUT_256`        => [0..255] + `TB_HI_BLACK`
 561 *
 562 *    In this mode you get 256 distinct colors (plus default):
 563 *                0x00   (1): `TB_DEFAULT`
 564 *       `TB_HI_BLACK`   (1): `TB_BLACK` in `TB_OUTPUT_NORMAL`
 565 *          0x01..0x07   (7): the next 7 colors as in `TB_OUTPUT_NORMAL`
 566 *          0x08..0x0f   (8): bright versions of the above
 567 *          0x10..0xe7 (216): 216 different colors
 568 *          0xe8..0xff  (24): 24 different shades of gray
 569 *
 570 *    All `TB_*` style attributes except `TB_BRIGHT` may be bitwise OR'd as in
 571 *    `TB_OUTPUT_NORMAL`.
 572 *
 573 *    Note `TB_HI_BLACK` must be used for black, as 0x00 represents default.
 574 *
 575 * 3. `TB_OUTPUT_216`        => [0..216]
 576 *
 577 *    This mode supports the 216-color range of `TB_OUTPUT_256` only, but you
 578 *    don't need to provide an offset:
 579 *                0x00   (1): `TB_DEFAULT`
 580 *          0x01..0xd8 (216): 216 different colors
 581 *
 582 * 4. `TB_OUTPUT_GRAYSCALE`  => [0..24]
 583 *
 584 *    This mode supports the 24-color range of `TB_OUTPUT_256` only, but you
 585 *    don't need to provide an offset:
 586 *                0x00   (1): `TB_DEFAULT`
 587 *          0x01..0x18  (24): 24 different shades of gray
 588 *
 589 * 5. `TB_OUTPUT_TRUECOLOR`  => [0x000000..0xffffff] + `TB_HI_BLACK`
 590 *
 591 *    This mode provides 24-bit color on supported terminals. The format is
 592 *    0xRRGGBB.
 593 *
 594 *    All `TB_*` style attributes except `TB_BRIGHT` may be bitwise OR'd as in
 595 *    `TB_OUTPUT_NORMAL`.
 596 *
 597 *    Note `TB_HI_BLACK` must be used for black, as 0x000000 represents default.
 598 *
 599 * To use the terminal default color (i.e., to not send an escape code), pass
 600 * `TB_DEFAULT`. For convenience, the value 0 is interpreted as `TB_DEFAULT` in
 601 * all modes.
 602 *
 603 * Note, cell attributes persist after switching output modes. Any translation
 604 * between, for example, `TB_OUTPUT_NORMAL`'s `TB_RED` and
 605 * `TB_OUTPUT_TRUECOLOR`'s 0xff0000 must be performed by the caller. Also note
 606 * that cells previously rendered in one mode may persist unchanged until the
 607 * front buffer is cleared (such as after a resize event) at which point it will
 608 * be re-interpreted and flushed according to the current mode. Callers may
 609 * invoke `tb_invalidate` if it is desirable to immediately re-interpret and
 610 * flush the entire screen according to the current mode.
 611 *
 612 * Note, not all terminals support all output modes, especially beyond
 613 * `TB_OUTPUT_NORMAL`. There is also no very reliable way to determine color
 614 * support dynamically. If portability is desired, callers are recommended to
 615 * use `TB_OUTPUT_NORMAL` or make output mode end-user configurable. The same
 616 * advice applies to style attributes.
 617 *
 618 * If mode is `TB_OUTPUT_CURRENT`, return the current output mode.
 619 *
 620 * The default output mode is `TB_OUTPUT_NORMAL`.
 621 */
 622int tb_set_output_mode(int mode);
 623
 624/* Wait for an event up to `timeout_ms` milliseconds and populate `event` with
 625 * it. If no event is available within the timeout period, `TB_ERR_NO_EVENT`
 626 * is returned. On a resize event, the underlying `select(2)` call may be
 627 * interrupted, yielding a return code of `TB_ERR_POLL`. In this case, you may
 628 * check `errno` via `tb_last_errno`. If it's `EINTR`, you may elect to ignore
 629 * that and call `tb_peek_event` again.
 630 */
 631int tb_peek_event(struct tb_event *event, int timeout_ms);
 632
 633/* Same as `tb_peek_event` except no timeout. */
 634int tb_poll_event(struct tb_event *event);
 635
 636/* Internal termbox fds that can be used with `poll(2)`, `select(2)`, etc.
 637 * externally. Callers must invoke `tb_poll_event` or `tb_peek_event` if
 638 * fds become readable. */
 639int tb_get_fds(int *ttyfd, int *resizefd);
 640
 641/* Print and printf functions. Specify param `out_w` to determine width of
 642 * printed string. Strings are interpreted as UTF-8.
 643 *
 644 * Non-printable characters (`iswprint(3)`) and truncated UTF-8 byte sequences
 645 * are replaced with U+FFFD.
 646 *
 647 * Newlines (`\n`) are supported with the caveat that `out_w` will return the
 648 * width of the string as if it were on a single line.
 649 *
 650 * If the starting coordinate is out of bounds, `TB_ERR_OUT_OF_BOUNDS` is
 651 * returned. If the starting coordinate is in bounds, but goes out of bounds,
 652 * then the out-of-bounds portions of the string are ignored.
 653 *
 654 * For finer control, use `tb_set_cell`.
 655 */
 656int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str);
 657int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt, ...);
 658int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
 659    const char *str);
 660int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
 661    const char *fmt, ...);
 662
 663/* Send raw bytes to terminal. */
 664int tb_send(const char *buf, size_t nbuf);
 665int tb_sendf(const char *fmt, ...);
 666
 667/* Deprecated. Set custom callbacks. `fn_type` is one of `TB_FUNC_*` constants,
 668 * `fn` is a compatible function pointer, or NULL to clear.
 669 *
 670 * `TB_FUNC_EXTRACT_PRE`:
 671 *   If specified, invoke this function BEFORE termbox tries to extract any
 672 *   escape sequences from the input buffer.
 673 *
 674 * `TB_FUNC_EXTRACT_POST`:
 675 *   If specified, invoke this function AFTER termbox tries (and fails) to
 676 *   extract any escape sequences from the input buffer.
 677 */
 678int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *));
 679
 680/* Return byte length of codepoint given first byte of UTF-8 sequence (1-6). */
 681int tb_utf8_char_length(char c);
 682
 683/* Convert UTF-8 null-terminated byte sequence to UTF-32 codepoint.
 684 *
 685 * If `c` is an empty C string, return 0. `out` is left unchanged.
 686 *
 687 * If a null byte is encountered in the middle of the codepoint, return a
 688 * negative number indicating how many bytes were processed. `out` is left
 689 * unchanged.
 690 *
 691 * Otherwise, return byte length of codepoint (1-6).
 692 */
 693int tb_utf8_char_to_unicode(uint32_t *out, const char *c);
 694
 695/* Convert UTF-32 codepoint to UTF-8 null-terminated byte sequence.
 696 *
 697 * `out` must be char[7] or greater. Return byte length of codepoint (1-6).
 698 */
 699int tb_utf8_unicode_to_char(char *out, uint32_t c);
 700
 701/* Library utility functions */
 702int tb_last_errno(void);
 703const char *tb_strerror(int err);
 704struct tb_cell *tb_cell_buffer(void); // Deprecated
 705int tb_has_truecolor(void);
 706int tb_has_egc(void);
 707int tb_attr_width(void);
 708const char *tb_version(void);
 709
 710/* Deprecation notice!
 711 *
 712 * The following will be removed in version 3.x (ABI version 3):
 713 *
 714 *   TB_256_BLACK           (use TB_HI_BLACK)
 715 *   TB_OPT_TRUECOLOR       (use TB_OPT_ATTR_W)
 716 *   TB_TRUECOLOR_BOLD      (use TB_BOLD)
 717 *   TB_TRUECOLOR_UNDERLINE (use TB_UNDERLINE)
 718 *   TB_TRUECOLOR_REVERSE   (use TB_REVERSE)
 719 *   TB_TRUECOLOR_ITALIC    (use TB_ITALICe)
 720 *   TB_TRUECOLOR_BLINK     (use TB_BLINK)
 721 *   TB_TRUECOLOR_BLACK     (use TB_HI_BLACK)
 722 *   tb_cell_buffer
 723 *   tb_set_func
 724 *   TB_FUNC_EXTRACT_PRE
 725 *   TB_FUNC_EXTRACT_POST
 726 */
 727
 728#ifdef __cplusplus
 729}
 730#endif
 731
 732#endif // TERMBOX_H_INCL
 733
 734#ifdef TB_IMPL
 735
 736#define if_err_return(rv, expr)                                                \
 737    if (((rv) = (expr)) != TB_OK) return (rv)
 738#define if_err_break(rv, expr)                                                 \
 739    if (((rv) = (expr)) != TB_OK) break
 740#define if_ok_return(rv, expr)                                                 \
 741    if (((rv) = (expr)) == TB_OK) return (rv)
 742#define if_ok_or_need_more_return(rv, expr)                                    \
 743    if (((rv) = (expr)) == TB_OK || (rv) == TB_ERR_NEED_MORE) return (rv)
 744
 745#define send_literal(rv, a)                                                    \
 746    if_err_return((rv), bytebuf_nputs(&global.out, (a), sizeof(a) - 1))
 747
 748#define send_num(rv, nbuf, n)                                                  \
 749    if_err_return((rv),                                                        \
 750        bytebuf_nputs(&global.out, (nbuf), convert_num((n), (nbuf))))
 751
 752#define snprintf_or_return(rv, str, sz, fmt, ...)                              \
 753    do {                                                                       \
 754        (rv) = snprintf((str), (sz), (fmt), __VA_ARGS__);                      \
 755        if ((rv) < 0 || (rv) >= (int)(sz)) return TB_ERR;                      \
 756    } while (0)
 757
 758#define if_not_init_return()                                                   \
 759    if (!global.initialized) return TB_ERR_NOT_INIT
 760
 761struct bytebuf_t {
 762    char *buf;
 763    size_t len;
 764    size_t cap;
 765};
 766
 767struct cellbuf_t {
 768    int width;
 769    int height;
 770    struct tb_cell *cells;
 771};
 772
 773struct cap_trie_t {
 774    char c;
 775    struct cap_trie_t *children;
 776    size_t nchildren;
 777    int is_leaf;
 778    uint16_t key;
 779    uint8_t mod;
 780};
 781
 782struct tb_global_t {
 783    int ttyfd;
 784    int rfd;
 785    int wfd;
 786    int ttyfd_open;
 787    int resize_pipefd[2];
 788    int width;
 789    int height;
 790    int cursor_x;
 791    int cursor_y;
 792    int last_x;
 793    int last_y;
 794    uintattr_t fg;
 795    uintattr_t bg;
 796    uintattr_t last_fg;
 797    uintattr_t last_bg;
 798    int input_mode;
 799    int output_mode;
 800    char *terminfo;
 801    size_t nterminfo;
 802    const char *caps[TB_CAP__COUNT];
 803    struct cap_trie_t cap_trie;
 804    struct bytebuf_t in;
 805    struct bytebuf_t out;
 806    struct cellbuf_t back;
 807    struct cellbuf_t front;
 808    struct termios orig_tios;
 809    int has_orig_tios;
 810    int last_errno;
 811    int initialized;
 812    int (*fn_extract_esc_pre)(struct tb_event *, size_t *);
 813    int (*fn_extract_esc_post)(struct tb_event *, size_t *);
 814    char errbuf[1024];
 815};
 816
 817static struct tb_global_t global = {0};
 818
 819/* BEGIN codegen c */
 820/* Produced by ./codegen.sh on Tue, 03 Sep 2024 04:17:48 +0000 */
 821
 822static const int16_t terminfo_cap_indexes[] = {
 823    66,  // kf1 (TB_CAP_F1)
 824    68,  // kf2 (TB_CAP_F2)
 825    69,  // kf3 (TB_CAP_F3)
 826    70,  // kf4 (TB_CAP_F4)
 827    71,  // kf5 (TB_CAP_F5)
 828    72,  // kf6 (TB_CAP_F6)
 829    73,  // kf7 (TB_CAP_F7)
 830    74,  // kf8 (TB_CAP_F8)
 831    75,  // kf9 (TB_CAP_F9)
 832    67,  // kf10 (TB_CAP_F10)
 833    216, // kf11 (TB_CAP_F11)
 834    217, // kf12 (TB_CAP_F12)
 835    77,  // kich1 (TB_CAP_INSERT)
 836    59,  // kdch1 (TB_CAP_DELETE)
 837    76,  // khome (TB_CAP_HOME)
 838    164, // kend (TB_CAP_END)
 839    82,  // kpp (TB_CAP_PGUP)
 840    81,  // knp (TB_CAP_PGDN)
 841    87,  // kcuu1 (TB_CAP_ARROW_UP)
 842    61,  // kcud1 (TB_CAP_ARROW_DOWN)
 843    79,  // kcub1 (TB_CAP_ARROW_LEFT)
 844    83,  // kcuf1 (TB_CAP_ARROW_RIGHT)
 845    148, // kcbt (TB_CAP_BACK_TAB)
 846    28,  // smcup (TB_CAP_ENTER_CA)
 847    40,  // rmcup (TB_CAP_EXIT_CA)
 848    16,  // cnorm (TB_CAP_SHOW_CURSOR)
 849    13,  // civis (TB_CAP_HIDE_CURSOR)
 850    5,   // clear (TB_CAP_CLEAR_SCREEN)
 851    39,  // sgr0 (TB_CAP_SGR0)
 852    36,  // smul (TB_CAP_UNDERLINE)
 853    27,  // bold (TB_CAP_BOLD)
 854    26,  // blink (TB_CAP_BLINK)
 855    311, // sitm (TB_CAP_ITALIC)
 856    34,  // rev (TB_CAP_REVERSE)
 857    89,  // smkx (TB_CAP_ENTER_KEYPAD)
 858    88,  // rmkx (TB_CAP_EXIT_KEYPAD)
 859    30,  // dim (TB_CAP_DIM)
 860    32,  // invis (TB_CAP_INVISIBLE)
 861};
 862
 863// xterm
 864static const char *xterm_caps[] = {
 865    "\033OP",                  // kf1 (TB_CAP_F1)
 866    "\033OQ",                  // kf2 (TB_CAP_F2)
 867    "\033OR",                  // kf3 (TB_CAP_F3)
 868    "\033OS",                  // kf4 (TB_CAP_F4)
 869    "\033[15~",                // kf5 (TB_CAP_F5)
 870    "\033[17~",                // kf6 (TB_CAP_F6)
 871    "\033[18~",                // kf7 (TB_CAP_F7)
 872    "\033[19~",                // kf8 (TB_CAP_F8)
 873    "\033[20~",                // kf9 (TB_CAP_F9)
 874    "\033[21~",                // kf10 (TB_CAP_F10)
 875    "\033[23~",                // kf11 (TB_CAP_F11)
 876    "\033[24~",                // kf12 (TB_CAP_F12)
 877    "\033[2~",                 // kich1 (TB_CAP_INSERT)
 878    "\033[3~",                 // kdch1 (TB_CAP_DELETE)
 879    "\033OH",                  // khome (TB_CAP_HOME)
 880    "\033OF",                  // kend (TB_CAP_END)
 881    "\033[5~",                 // kpp (TB_CAP_PGUP)
 882    "\033[6~",                 // knp (TB_CAP_PGDN)
 883    "\033OA",                  // kcuu1 (TB_CAP_ARROW_UP)
 884    "\033OB",                  // kcud1 (TB_CAP_ARROW_DOWN)
 885    "\033OD",                  // kcub1 (TB_CAP_ARROW_LEFT)
 886    "\033OC",                  // kcuf1 (TB_CAP_ARROW_RIGHT)
 887    "\033[Z",                  // kcbt (TB_CAP_BACK_TAB)
 888    "\033[?1049h\033[22;0;0t", // smcup (TB_CAP_ENTER_CA)
 889    "\033[?1049l\033[23;0;0t", // rmcup (TB_CAP_EXIT_CA)
 890    "\033[?12l\033[?25h",      // cnorm (TB_CAP_SHOW_CURSOR)
 891    "\033[?25l",               // civis (TB_CAP_HIDE_CURSOR)
 892    "\033[H\033[2J",           // clear (TB_CAP_CLEAR_SCREEN)
 893    "\033(B\033[m",            // sgr0 (TB_CAP_SGR0)
 894    "\033[4m",                 // smul (TB_CAP_UNDERLINE)
 895    "\033[1m",                 // bold (TB_CAP_BOLD)
 896    "\033[5m",                 // blink (TB_CAP_BLINK)
 897    "\033[3m",                 // sitm (TB_CAP_ITALIC)
 898    "\033[7m",                 // rev (TB_CAP_REVERSE)
 899    "\033[?1h\033=",           // smkx (TB_CAP_ENTER_KEYPAD)
 900    "\033[?1l\033>",           // rmkx (TB_CAP_EXIT_KEYPAD)
 901    "\033[2m",                 // dim (TB_CAP_DIM)
 902    "\033[8m",                 // invis (TB_CAP_INVISIBLE)
 903};
 904
 905// linux
 906static const char *linux_caps[] = {
 907    "\033[[A",           // kf1 (TB_CAP_F1)
 908    "\033[[B",           // kf2 (TB_CAP_F2)
 909    "\033[[C",           // kf3 (TB_CAP_F3)
 910    "\033[[D",           // kf4 (TB_CAP_F4)
 911    "\033[[E",           // kf5 (TB_CAP_F5)
 912    "\033[17~",          // kf6 (TB_CAP_F6)
 913    "\033[18~",          // kf7 (TB_CAP_F7)
 914    "\033[19~",          // kf8 (TB_CAP_F8)
 915    "\033[20~",          // kf9 (TB_CAP_F9)
 916    "\033[21~",          // kf10 (TB_CAP_F10)
 917    "\033[23~",          // kf11 (TB_CAP_F11)
 918    "\033[24~",          // kf12 (TB_CAP_F12)
 919    "\033[2~",           // kich1 (TB_CAP_INSERT)
 920    "\033[3~",           // kdch1 (TB_CAP_DELETE)
 921    "\033[1~",           // khome (TB_CAP_HOME)
 922    "\033[4~",           // kend (TB_CAP_END)
 923    "\033[5~",           // kpp (TB_CAP_PGUP)
 924    "\033[6~",           // knp (TB_CAP_PGDN)
 925    "\033[A",            // kcuu1 (TB_CAP_ARROW_UP)
 926    "\033[B",            // kcud1 (TB_CAP_ARROW_DOWN)
 927    "\033[D",            // kcub1 (TB_CAP_ARROW_LEFT)
 928    "\033[C",            // kcuf1 (TB_CAP_ARROW_RIGHT)
 929    "\033\011",          // kcbt (TB_CAP_BACK_TAB)
 930    "",                  // smcup (TB_CAP_ENTER_CA)
 931    "",                  // rmcup (TB_CAP_EXIT_CA)
 932    "\033[?25h\033[?0c", // cnorm (TB_CAP_SHOW_CURSOR)
 933    "\033[?25l\033[?1c", // civis (TB_CAP_HIDE_CURSOR)
 934    "\033[H\033[J",      // clear (TB_CAP_CLEAR_SCREEN)
 935    "\033[m\017",        // sgr0 (TB_CAP_SGR0)
 936    "\033[4m",           // smul (TB_CAP_UNDERLINE)
 937    "\033[1m",           // bold (TB_CAP_BOLD)
 938    "\033[5m",           // blink (TB_CAP_BLINK)
 939    "",                  // sitm (TB_CAP_ITALIC)
 940    "\033[7m",           // rev (TB_CAP_REVERSE)
 941    "",                  // smkx (TB_CAP_ENTER_KEYPAD)
 942    "",                  // rmkx (TB_CAP_EXIT_KEYPAD)
 943    "\033[2m",           // dim (TB_CAP_DIM)
 944    "",                  // invis (TB_CAP_INVISIBLE)
 945};
 946
 947// screen
 948static const char *screen_caps[] = {
 949    "\033OP",            // kf1 (TB_CAP_F1)
 950    "\033OQ",            // kf2 (TB_CAP_F2)
 951    "\033OR",            // kf3 (TB_CAP_F3)
 952    "\033OS",            // kf4 (TB_CAP_F4)
 953    "\033[15~",          // kf5 (TB_CAP_F5)
 954    "\033[17~",          // kf6 (TB_CAP_F6)
 955    "\033[18~",          // kf7 (TB_CAP_F7)
 956    "\033[19~",          // kf8 (TB_CAP_F8)
 957    "\033[20~",          // kf9 (TB_CAP_F9)
 958    "\033[21~",          // kf10 (TB_CAP_F10)
 959    "\033[23~",          // kf11 (TB_CAP_F11)
 960    "\033[24~",          // kf12 (TB_CAP_F12)
 961    "\033[2~",           // kich1 (TB_CAP_INSERT)
 962    "\033[3~",           // kdch1 (TB_CAP_DELETE)
 963    "\033[1~",           // khome (TB_CAP_HOME)
 964    "\033[4~",           // kend (TB_CAP_END)
 965    "\033[5~",           // kpp (TB_CAP_PGUP)
 966    "\033[6~",           // knp (TB_CAP_PGDN)
 967    "\033OA",            // kcuu1 (TB_CAP_ARROW_UP)
 968    "\033OB",            // kcud1 (TB_CAP_ARROW_DOWN)
 969    "\033OD",            // kcub1 (TB_CAP_ARROW_LEFT)
 970    "\033OC",            // kcuf1 (TB_CAP_ARROW_RIGHT)
 971    "\033[Z",            // kcbt (TB_CAP_BACK_TAB)
 972    "\033[?1049h",       // smcup (TB_CAP_ENTER_CA)
 973    "\033[?1049l",       // rmcup (TB_CAP_EXIT_CA)
 974    "\033[34h\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR)
 975    "\033[?25l",         // civis (TB_CAP_HIDE_CURSOR)
 976    "\033[H\033[J",      // clear (TB_CAP_CLEAR_SCREEN)
 977    "\033[m\017",        // sgr0 (TB_CAP_SGR0)
 978    "\033[4m",           // smul (TB_CAP_UNDERLINE)
 979    "\033[1m",           // bold (TB_CAP_BOLD)
 980    "\033[5m",           // blink (TB_CAP_BLINK)
 981    "",                  // sitm (TB_CAP_ITALIC)
 982    "\033[7m",           // rev (TB_CAP_REVERSE)
 983    "\033[?1h\033=",     // smkx (TB_CAP_ENTER_KEYPAD)
 984    "\033[?1l\033>",     // rmkx (TB_CAP_EXIT_KEYPAD)
 985    "\033[2m",           // dim (TB_CAP_DIM)
 986    "",                  // invis (TB_CAP_INVISIBLE)
 987};
 988
 989// rxvt-256color
 990static const char *rxvt_256color_caps[] = {
 991    "\033[11~",              // kf1 (TB_CAP_F1)
 992    "\033[12~",              // kf2 (TB_CAP_F2)
 993    "\033[13~",              // kf3 (TB_CAP_F3)
 994    "\033[14~",              // kf4 (TB_CAP_F4)
 995    "\033[15~",              // kf5 (TB_CAP_F5)
 996    "\033[17~",              // kf6 (TB_CAP_F6)
 997    "\033[18~",              // kf7 (TB_CAP_F7)
 998    "\033[19~",              // kf8 (TB_CAP_F8)
 999    "\033[20~",              // kf9 (TB_CAP_F9)
1000    "\033[21~",              // kf10 (TB_CAP_F10)
1001    "\033[23~",              // kf11 (TB_CAP_F11)
1002    "\033[24~",              // kf12 (TB_CAP_F12)
1003    "\033[2~",               // kich1 (TB_CAP_INSERT)
1004    "\033[3~",               // kdch1 (TB_CAP_DELETE)
1005    "\033[7~",               // khome (TB_CAP_HOME)
1006    "\033[8~",               // kend (TB_CAP_END)
1007    "\033[5~",               // kpp (TB_CAP_PGUP)
1008    "\033[6~",               // knp (TB_CAP_PGDN)
1009    "\033[A",                // kcuu1 (TB_CAP_ARROW_UP)
1010    "\033[B",                // kcud1 (TB_CAP_ARROW_DOWN)
1011    "\033[D",                // kcub1 (TB_CAP_ARROW_LEFT)
1012    "\033[C",                // kcuf1 (TB_CAP_ARROW_RIGHT)
1013    "\033[Z",                // kcbt (TB_CAP_BACK_TAB)
1014    "\0337\033[?47h",        // smcup (TB_CAP_ENTER_CA)
1015    "\033[2J\033[?47l\0338", // rmcup (TB_CAP_EXIT_CA)
1016    "\033[?25h",             // cnorm (TB_CAP_SHOW_CURSOR)
1017    "\033[?25l",             // civis (TB_CAP_HIDE_CURSOR)
1018    "\033[H\033[2J",         // clear (TB_CAP_CLEAR_SCREEN)
1019    "\033[m\017",            // sgr0 (TB_CAP_SGR0)
1020    "\033[4m",               // smul (TB_CAP_UNDERLINE)
1021    "\033[1m",               // bold (TB_CAP_BOLD)
1022    "\033[5m",               // blink (TB_CAP_BLINK)
1023    "",                      // sitm (TB_CAP_ITALIC)
1024    "\033[7m",               // rev (TB_CAP_REVERSE)
1025    "\033=",                 // smkx (TB_CAP_ENTER_KEYPAD)
1026    "\033>",                 // rmkx (TB_CAP_EXIT_KEYPAD)
1027    "",                      // dim (TB_CAP_DIM)
1028    "",                      // invis (TB_CAP_INVISIBLE)
1029};
1030
1031// rxvt-unicode
1032static const char *rxvt_unicode_caps[] = {
1033    "\033[11~",           // kf1 (TB_CAP_F1)
1034    "\033[12~",           // kf2 (TB_CAP_F2)
1035    "\033[13~",           // kf3 (TB_CAP_F3)
1036    "\033[14~",           // kf4 (TB_CAP_F4)
1037    "\033[15~",           // kf5 (TB_CAP_F5)
1038    "\033[17~",           // kf6 (TB_CAP_F6)
1039    "\033[18~",           // kf7 (TB_CAP_F7)
1040    "\033[19~",           // kf8 (TB_CAP_F8)
1041    "\033[20~",           // kf9 (TB_CAP_F9)
1042    "\033[21~",           // kf10 (TB_CAP_F10)
1043    "\033[23~",           // kf11 (TB_CAP_F11)
1044    "\033[24~",           // kf12 (TB_CAP_F12)
1045    "\033[2~",            // kich1 (TB_CAP_INSERT)
1046    "\033[3~",            // kdch1 (TB_CAP_DELETE)
1047    "\033[7~",            // khome (TB_CAP_HOME)
1048    "\033[8~",            // kend (TB_CAP_END)
1049    "\033[5~",            // kpp (TB_CAP_PGUP)
1050    "\033[6~",            // knp (TB_CAP_PGDN)
1051    "\033[A",             // kcuu1 (TB_CAP_ARROW_UP)
1052    "\033[B",             // kcud1 (TB_CAP_ARROW_DOWN)
1053    "\033[D",             // kcub1 (TB_CAP_ARROW_LEFT)
1054    "\033[C",             // kcuf1 (TB_CAP_ARROW_RIGHT)
1055    "\033[Z",             // kcbt (TB_CAP_BACK_TAB)
1056    "\033[?1049h",        // smcup (TB_CAP_ENTER_CA)
1057    "\033[r\033[?1049l",  // rmcup (TB_CAP_EXIT_CA)
1058    "\033[?12l\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR)
1059    "\033[?25l",          // civis (TB_CAP_HIDE_CURSOR)
1060    "\033[H\033[2J",      // clear (TB_CAP_CLEAR_SCREEN)
1061    "\033[m\033(B",       // sgr0 (TB_CAP_SGR0)
1062    "\033[4m",            // smul (TB_CAP_UNDERLINE)
1063    "\033[1m",            // bold (TB_CAP_BOLD)
1064    "\033[5m",            // blink (TB_CAP_BLINK)
1065    "\033[3m",            // sitm (TB_CAP_ITALIC)
1066    "\033[7m",            // rev (TB_CAP_REVERSE)
1067    "\033=",              // smkx (TB_CAP_ENTER_KEYPAD)
1068    "\033>",              // rmkx (TB_CAP_EXIT_KEYPAD)
1069    "",                   // dim (TB_CAP_DIM)
1070    "",                   // invis (TB_CAP_INVISIBLE)
1071};
1072
1073// Eterm
1074static const char *eterm_caps[] = {
1075    "\033[11~",              // kf1 (TB_CAP_F1)
1076    "\033[12~",              // kf2 (TB_CAP_F2)
1077    "\033[13~",              // kf3 (TB_CAP_F3)
1078    "\033[14~",              // kf4 (TB_CAP_F4)
1079    "\033[15~",              // kf5 (TB_CAP_F5)
1080    "\033[17~",              // kf6 (TB_CAP_F6)
1081    "\033[18~",              // kf7 (TB_CAP_F7)
1082    "\033[19~",              // kf8 (TB_CAP_F8)
1083    "\033[20~",              // kf9 (TB_CAP_F9)
1084    "\033[21~",              // kf10 (TB_CAP_F10)
1085    "\033[23~",              // kf11 (TB_CAP_F11)
1086    "\033[24~",              // kf12 (TB_CAP_F12)
1087    "\033[2~",               // kich1 (TB_CAP_INSERT)
1088    "\033[3~",               // kdch1 (TB_CAP_DELETE)
1089    "\033[7~",               // khome (TB_CAP_HOME)
1090    "\033[8~",               // kend (TB_CAP_END)
1091    "\033[5~",               // kpp (TB_CAP_PGUP)
1092    "\033[6~",               // knp (TB_CAP_PGDN)
1093    "\033[A",                // kcuu1 (TB_CAP_ARROW_UP)
1094    "\033[B",                // kcud1 (TB_CAP_ARROW_DOWN)
1095    "\033[D",                // kcub1 (TB_CAP_ARROW_LEFT)
1096    "\033[C",                // kcuf1 (TB_CAP_ARROW_RIGHT)
1097    "",                      // kcbt (TB_CAP_BACK_TAB)
1098    "\0337\033[?47h",        // smcup (TB_CAP_ENTER_CA)
1099    "\033[2J\033[?47l\0338", // rmcup (TB_CAP_EXIT_CA)
1100    "\033[?25h",             // cnorm (TB_CAP_SHOW_CURSOR)
1101    "\033[?25l",             // civis (TB_CAP_HIDE_CURSOR)
1102    "\033[H\033[2J",         // clear (TB_CAP_CLEAR_SCREEN)
1103    "\033[m\017",            // sgr0 (TB_CAP_SGR0)
1104    "\033[4m",               // smul (TB_CAP_UNDERLINE)
1105    "\033[1m",               // bold (TB_CAP_BOLD)
1106    "\033[5m",               // blink (TB_CAP_BLINK)
1107    "",                      // sitm (TB_CAP_ITALIC)
1108    "\033[7m",               // rev (TB_CAP_REVERSE)
1109    "",                      // smkx (TB_CAP_ENTER_KEYPAD)
1110    "",                      // rmkx (TB_CAP_EXIT_KEYPAD)
1111    "",                      // dim (TB_CAP_DIM)
1112    "",                      // invis (TB_CAP_INVISIBLE)
1113};
1114
1115static struct {
1116    const char *name;
1117    const char **caps;
1118    const char *alias;
1119} builtin_terms[] = {
1120    {"xterm",         xterm_caps,         ""    },
1121    {"linux",         linux_caps,         ""    },
1122    {"screen",        screen_caps,        "tmux"},
1123    {"rxvt-256color", rxvt_256color_caps, ""    },
1124    {"rxvt-unicode",  rxvt_unicode_caps,  "rxvt"},
1125    {"Eterm",         eterm_caps,         ""    },
1126    {NULL,            NULL,               NULL  },
1127};
1128
1129/* END codegen c */
1130
1131static struct {
1132    const char *cap;
1133    const uint16_t key;
1134    const uint8_t mod;
1135} builtin_mod_caps[] = {
1136  // xterm arrows
1137    {"\x1b[1;2A",    TB_KEY_ARROW_UP,    TB_MOD_SHIFT                           },
1138    {"\x1b[1;3A",    TB_KEY_ARROW_UP,    TB_MOD_ALT                             },
1139    {"\x1b[1;4A",    TB_KEY_ARROW_UP,    TB_MOD_ALT | TB_MOD_SHIFT              },
1140    {"\x1b[1;5A",    TB_KEY_ARROW_UP,    TB_MOD_CTRL                            },
1141    {"\x1b[1;6A",    TB_KEY_ARROW_UP,    TB_MOD_CTRL | TB_MOD_SHIFT             },
1142    {"\x1b[1;7A",    TB_KEY_ARROW_UP,    TB_MOD_CTRL | TB_MOD_ALT               },
1143    {"\x1b[1;8A",    TB_KEY_ARROW_UP,    TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1144
1145    {"\x1b[1;2B",    TB_KEY_ARROW_DOWN,  TB_MOD_SHIFT                           },
1146    {"\x1b[1;3B",    TB_KEY_ARROW_DOWN,  TB_MOD_ALT                             },
1147    {"\x1b[1;4B",    TB_KEY_ARROW_DOWN,  TB_MOD_ALT | TB_MOD_SHIFT              },
1148    {"\x1b[1;5B",    TB_KEY_ARROW_DOWN,  TB_MOD_CTRL                            },
1149    {"\x1b[1;6B",    TB_KEY_ARROW_DOWN,  TB_MOD_CTRL | TB_MOD_SHIFT             },
1150    {"\x1b[1;7B",    TB_KEY_ARROW_DOWN,  TB_MOD_CTRL | TB_MOD_ALT               },
1151    {"\x1b[1;8B",    TB_KEY_ARROW_DOWN,  TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1152
1153    {"\x1b[1;2C",    TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT                           },
1154    {"\x1b[1;3C",    TB_KEY_ARROW_RIGHT, TB_MOD_ALT                             },
1155    {"\x1b[1;4C",    TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT              },
1156    {"\x1b[1;5C",    TB_KEY_ARROW_RIGHT, TB_MOD_CTRL                            },
1157    {"\x1b[1;6C",    TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_SHIFT             },
1158    {"\x1b[1;7C",    TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT               },
1159    {"\x1b[1;8C",    TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1160
1161    {"\x1b[1;2D",    TB_KEY_ARROW_LEFT,  TB_MOD_SHIFT                           },
1162    {"\x1b[1;3D",    TB_KEY_ARROW_LEFT,  TB_MOD_ALT                             },
1163    {"\x1b[1;4D",    TB_KEY_ARROW_LEFT,  TB_MOD_ALT | TB_MOD_SHIFT              },
1164    {"\x1b[1;5D",    TB_KEY_ARROW_LEFT,  TB_MOD_CTRL                            },
1165    {"\x1b[1;6D",    TB_KEY_ARROW_LEFT,  TB_MOD_CTRL | TB_MOD_SHIFT             },
1166    {"\x1b[1;7D",    TB_KEY_ARROW_LEFT,  TB_MOD_CTRL | TB_MOD_ALT               },
1167    {"\x1b[1;8D",    TB_KEY_ARROW_LEFT,  TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1168
1169 // xterm keys
1170    {"\x1b[1;2H",    TB_KEY_HOME,        TB_MOD_SHIFT                           },
1171    {"\x1b[1;3H",    TB_KEY_HOME,        TB_MOD_ALT                             },
1172    {"\x1b[1;4H",    TB_KEY_HOME,        TB_MOD_ALT | TB_MOD_SHIFT              },
1173    {"\x1b[1;5H",    TB_KEY_HOME,        TB_MOD_CTRL                            },
1174    {"\x1b[1;6H",    TB_KEY_HOME,        TB_MOD_CTRL | TB_MOD_SHIFT             },
1175    {"\x1b[1;7H",    TB_KEY_HOME,        TB_MOD_CTRL | TB_MOD_ALT               },
1176    {"\x1b[1;8H",    TB_KEY_HOME,        TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1177
1178    {"\x1b[1;2F",    TB_KEY_END,         TB_MOD_SHIFT                           },
1179    {"\x1b[1;3F",    TB_KEY_END,         TB_MOD_ALT                             },
1180    {"\x1b[1;4F",    TB_KEY_END,         TB_MOD_ALT | TB_MOD_SHIFT              },
1181    {"\x1b[1;5F",    TB_KEY_END,         TB_MOD_CTRL                            },
1182    {"\x1b[1;6F",    TB_KEY_END,         TB_MOD_CTRL | TB_MOD_SHIFT             },
1183    {"\x1b[1;7F",    TB_KEY_END,         TB_MOD_CTRL | TB_MOD_ALT               },
1184    {"\x1b[1;8F",    TB_KEY_END,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1185
1186    {"\x1b[2;2~",    TB_KEY_INSERT,      TB_MOD_SHIFT                           },
1187    {"\x1b[2;3~",    TB_KEY_INSERT,      TB_MOD_ALT                             },
1188    {"\x1b[2;4~",    TB_KEY_INSERT,      TB_MOD_ALT | TB_MOD_SHIFT              },
1189    {"\x1b[2;5~",    TB_KEY_INSERT,      TB_MOD_CTRL                            },
1190    {"\x1b[2;6~",    TB_KEY_INSERT,      TB_MOD_CTRL | TB_MOD_SHIFT             },
1191    {"\x1b[2;7~",    TB_KEY_INSERT,      TB_MOD_CTRL | TB_MOD_ALT               },
1192    {"\x1b[2;8~",    TB_KEY_INSERT,      TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1193
1194    {"\x1b[3;2~",    TB_KEY_DELETE,      TB_MOD_SHIFT                           },
1195    {"\x1b[3;3~",    TB_KEY_DELETE,      TB_MOD_ALT                             },
1196    {"\x1b[3;4~",    TB_KEY_DELETE,      TB_MOD_ALT | TB_MOD_SHIFT              },
1197    {"\x1b[3;5~",    TB_KEY_DELETE,      TB_MOD_CTRL                            },
1198    {"\x1b[3;6~",    TB_KEY_DELETE,      TB_MOD_CTRL | TB_MOD_SHIFT             },
1199    {"\x1b[3;7~",    TB_KEY_DELETE,      TB_MOD_CTRL | TB_MOD_ALT               },
1200    {"\x1b[3;8~",    TB_KEY_DELETE,      TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1201
1202    {"\x1b[5;2~",    TB_KEY_PGUP,        TB_MOD_SHIFT                           },
1203    {"\x1b[5;3~",    TB_KEY_PGUP,        TB_MOD_ALT                             },
1204    {"\x1b[5;4~",    TB_KEY_PGUP,        TB_MOD_ALT | TB_MOD_SHIFT              },
1205    {"\x1b[5;5~",    TB_KEY_PGUP,        TB_MOD_CTRL                            },
1206    {"\x1b[5;6~",    TB_KEY_PGUP,        TB_MOD_CTRL | TB_MOD_SHIFT             },
1207    {"\x1b[5;7~",    TB_KEY_PGUP,        TB_MOD_CTRL | TB_MOD_ALT               },
1208    {"\x1b[5;8~",    TB_KEY_PGUP,        TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1209
1210    {"\x1b[6;2~",    TB_KEY_PGDN,        TB_MOD_SHIFT                           },
1211    {"\x1b[6;3~",    TB_KEY_PGDN,        TB_MOD_ALT                             },
1212    {"\x1b[6;4~",    TB_KEY_PGDN,        TB_MOD_ALT | TB_MOD_SHIFT              },
1213    {"\x1b[6;5~",    TB_KEY_PGDN,        TB_MOD_CTRL                            },
1214    {"\x1b[6;6~",    TB_KEY_PGDN,        TB_MOD_CTRL | TB_MOD_SHIFT             },
1215    {"\x1b[6;7~",    TB_KEY_PGDN,        TB_MOD_CTRL | TB_MOD_ALT               },
1216    {"\x1b[6;8~",    TB_KEY_PGDN,        TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1217
1218    {"\x1b[1;2P",    TB_KEY_F1,          TB_MOD_SHIFT                           },
1219    {"\x1b[1;3P",    TB_KEY_F1,          TB_MOD_ALT                             },
1220    {"\x1b[1;4P",    TB_KEY_F1,          TB_MOD_ALT | TB_MOD_SHIFT              },
1221    {"\x1b[1;5P",    TB_KEY_F1,          TB_MOD_CTRL                            },
1222    {"\x1b[1;6P",    TB_KEY_F1,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1223    {"\x1b[1;7P",    TB_KEY_F1,          TB_MOD_CTRL | TB_MOD_ALT               },
1224    {"\x1b[1;8P",    TB_KEY_F1,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1225
1226    {"\x1b[1;2Q",    TB_KEY_F2,          TB_MOD_SHIFT                           },
1227    {"\x1b[1;3Q",    TB_KEY_F2,          TB_MOD_ALT                             },
1228    {"\x1b[1;4Q",    TB_KEY_F2,          TB_MOD_ALT | TB_MOD_SHIFT              },
1229    {"\x1b[1;5Q",    TB_KEY_F2,          TB_MOD_CTRL                            },
1230    {"\x1b[1;6Q",    TB_KEY_F2,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1231    {"\x1b[1;7Q",    TB_KEY_F2,          TB_MOD_CTRL | TB_MOD_ALT               },
1232    {"\x1b[1;8Q",    TB_KEY_F2,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1233
1234    {"\x1b[1;2R",    TB_KEY_F3,          TB_MOD_SHIFT                           },
1235    {"\x1b[1;3R",    TB_KEY_F3,          TB_MOD_ALT                             },
1236    {"\x1b[1;4R",    TB_KEY_F3,          TB_MOD_ALT | TB_MOD_SHIFT              },
1237    {"\x1b[1;5R",    TB_KEY_F3,          TB_MOD_CTRL                            },
1238    {"\x1b[1;6R",    TB_KEY_F3,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1239    {"\x1b[1;7R",    TB_KEY_F3,          TB_MOD_CTRL | TB_MOD_ALT               },
1240    {"\x1b[1;8R",    TB_KEY_F3,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1241
1242    {"\x1b[1;2S",    TB_KEY_F4,          TB_MOD_SHIFT                           },
1243    {"\x1b[1;3S",    TB_KEY_F4,          TB_MOD_ALT                             },
1244    {"\x1b[1;4S",    TB_KEY_F4,          TB_MOD_ALT | TB_MOD_SHIFT              },
1245    {"\x1b[1;5S",    TB_KEY_F4,          TB_MOD_CTRL                            },
1246    {"\x1b[1;6S",    TB_KEY_F4,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1247    {"\x1b[1;7S",    TB_KEY_F4,          TB_MOD_CTRL | TB_MOD_ALT               },
1248    {"\x1b[1;8S",    TB_KEY_F4,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1249
1250    {"\x1b[15;2~",   TB_KEY_F5,          TB_MOD_SHIFT                           },
1251    {"\x1b[15;3~",   TB_KEY_F5,          TB_MOD_ALT                             },
1252    {"\x1b[15;4~",   TB_KEY_F5,          TB_MOD_ALT | TB_MOD_SHIFT              },
1253    {"\x1b[15;5~",   TB_KEY_F5,          TB_MOD_CTRL                            },
1254    {"\x1b[15;6~",   TB_KEY_F5,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1255    {"\x1b[15;7~",   TB_KEY_F5,          TB_MOD_CTRL | TB_MOD_ALT               },
1256    {"\x1b[15;8~",   TB_KEY_F5,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1257
1258    {"\x1b[17;2~",   TB_KEY_F6,          TB_MOD_SHIFT                           },
1259    {"\x1b[17;3~",   TB_KEY_F6,          TB_MOD_ALT                             },
1260    {"\x1b[17;4~",   TB_KEY_F6,          TB_MOD_ALT | TB_MOD_SHIFT              },
1261    {"\x1b[17;5~",   TB_KEY_F6,          TB_MOD_CTRL                            },
1262    {"\x1b[17;6~",   TB_KEY_F6,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1263    {"\x1b[17;7~",   TB_KEY_F6,          TB_MOD_CTRL | TB_MOD_ALT               },
1264    {"\x1b[17;8~",   TB_KEY_F6,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1265
1266    {"\x1b[18;2~",   TB_KEY_F7,          TB_MOD_SHIFT                           },
1267    {"\x1b[18;3~",   TB_KEY_F7,          TB_MOD_ALT                             },
1268    {"\x1b[18;4~",   TB_KEY_F7,          TB_MOD_ALT | TB_MOD_SHIFT              },
1269    {"\x1b[18;5~",   TB_KEY_F7,          TB_MOD_CTRL                            },
1270    {"\x1b[18;6~",   TB_KEY_F7,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1271    {"\x1b[18;7~",   TB_KEY_F7,          TB_MOD_CTRL | TB_MOD_ALT               },
1272    {"\x1b[18;8~",   TB_KEY_F7,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1273
1274    {"\x1b[19;2~",   TB_KEY_F8,          TB_MOD_SHIFT                           },
1275    {"\x1b[19;3~",   TB_KEY_F8,          TB_MOD_ALT                             },
1276    {"\x1b[19;4~",   TB_KEY_F8,          TB_MOD_ALT | TB_MOD_SHIFT              },
1277    {"\x1b[19;5~",   TB_KEY_F8,          TB_MOD_CTRL                            },
1278    {"\x1b[19;6~",   TB_KEY_F8,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1279    {"\x1b[19;7~",   TB_KEY_F8,          TB_MOD_CTRL | TB_MOD_ALT               },
1280    {"\x1b[19;8~",   TB_KEY_F8,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1281
1282    {"\x1b[20;2~",   TB_KEY_F9,          TB_MOD_SHIFT                           },
1283    {"\x1b[20;3~",   TB_KEY_F9,          TB_MOD_ALT                             },
1284    {"\x1b[20;4~",   TB_KEY_F9,          TB_MOD_ALT | TB_MOD_SHIFT              },
1285    {"\x1b[20;5~",   TB_KEY_F9,          TB_MOD_CTRL                            },
1286    {"\x1b[20;6~",   TB_KEY_F9,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1287    {"\x1b[20;7~",   TB_KEY_F9,          TB_MOD_CTRL | TB_MOD_ALT               },
1288    {"\x1b[20;8~",   TB_KEY_F9,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1289
1290    {"\x1b[21;2~",   TB_KEY_F10,         TB_MOD_SHIFT                           },
1291    {"\x1b[21;3~",   TB_KEY_F10,         TB_MOD_ALT                             },
1292    {"\x1b[21;4~",   TB_KEY_F10,         TB_MOD_ALT | TB_MOD_SHIFT              },
1293    {"\x1b[21;5~",   TB_KEY_F10,         TB_MOD_CTRL                            },
1294    {"\x1b[21;6~",   TB_KEY_F10,         TB_MOD_CTRL | TB_MOD_SHIFT             },
1295    {"\x1b[21;7~",   TB_KEY_F10,         TB_MOD_CTRL | TB_MOD_ALT               },
1296    {"\x1b[21;8~",   TB_KEY_F10,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1297
1298    {"\x1b[23;2~",   TB_KEY_F11,         TB_MOD_SHIFT                           },
1299    {"\x1b[23;3~",   TB_KEY_F11,         TB_MOD_ALT                             },
1300    {"\x1b[23;4~",   TB_KEY_F11,         TB_MOD_ALT | TB_MOD_SHIFT              },
1301    {"\x1b[23;5~",   TB_KEY_F11,         TB_MOD_CTRL                            },
1302    {"\x1b[23;6~",   TB_KEY_F11,         TB_MOD_CTRL | TB_MOD_SHIFT             },
1303    {"\x1b[23;7~",   TB_KEY_F11,         TB_MOD_CTRL | TB_MOD_ALT               },
1304    {"\x1b[23;8~",   TB_KEY_F11,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1305
1306    {"\x1b[24;2~",   TB_KEY_F12,         TB_MOD_SHIFT                           },
1307    {"\x1b[24;3~",   TB_KEY_F12,         TB_MOD_ALT                             },
1308    {"\x1b[24;4~",   TB_KEY_F12,         TB_MOD_ALT | TB_MOD_SHIFT              },
1309    {"\x1b[24;5~",   TB_KEY_F12,         TB_MOD_CTRL                            },
1310    {"\x1b[24;6~",   TB_KEY_F12,         TB_MOD_CTRL | TB_MOD_SHIFT             },
1311    {"\x1b[24;7~",   TB_KEY_F12,         TB_MOD_CTRL | TB_MOD_ALT               },
1312    {"\x1b[24;8~",   TB_KEY_F12,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1313
1314 // rxvt arrows
1315    {"\x1b[a",       TB_KEY_ARROW_UP,    TB_MOD_SHIFT                           },
1316    {"\x1b\x1b[A",   TB_KEY_ARROW_UP,    TB_MOD_ALT                             },
1317    {"\x1b\x1b[a",   TB_KEY_ARROW_UP,    TB_MOD_ALT | TB_MOD_SHIFT              },
1318    {"\x1bOa",       TB_KEY_ARROW_UP,    TB_MOD_CTRL                            },
1319    {"\x1b\x1bOa",   TB_KEY_ARROW_UP,    TB_MOD_CTRL | TB_MOD_ALT               },
1320
1321    {"\x1b[b",       TB_KEY_ARROW_DOWN,  TB_MOD_SHIFT                           },
1322    {"\x1b\x1b[B",   TB_KEY_ARROW_DOWN,  TB_MOD_ALT                             },
1323    {"\x1b\x1b[b",   TB_KEY_ARROW_DOWN,  TB_MOD_ALT | TB_MOD_SHIFT              },
1324    {"\x1bOb",       TB_KEY_ARROW_DOWN,  TB_MOD_CTRL                            },
1325    {"\x1b\x1bOb",   TB_KEY_ARROW_DOWN,  TB_MOD_CTRL | TB_MOD_ALT               },
1326
1327    {"\x1b[c",       TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT                           },
1328    {"\x1b\x1b[C",   TB_KEY_ARROW_RIGHT, TB_MOD_ALT                             },
1329    {"\x1b\x1b[c",   TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT              },
1330    {"\x1bOc",       TB_KEY_ARROW_RIGHT, TB_MOD_CTRL                            },
1331    {"\x1b\x1bOc",   TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT               },
1332
1333    {"\x1b[d",       TB_KEY_ARROW_LEFT,  TB_MOD_SHIFT                           },
1334    {"\x1b\x1b[D",   TB_KEY_ARROW_LEFT,  TB_MOD_ALT                             },
1335    {"\x1b\x1b[d",   TB_KEY_ARROW_LEFT,  TB_MOD_ALT | TB_MOD_SHIFT              },
1336    {"\x1bOd",       TB_KEY_ARROW_LEFT,  TB_MOD_CTRL                            },
1337    {"\x1b\x1bOd",   TB_KEY_ARROW_LEFT,  TB_MOD_CTRL | TB_MOD_ALT               },
1338
1339 // rxvt keys
1340    {"\x1b[7$",      TB_KEY_HOME,        TB_MOD_SHIFT                           },
1341    {"\x1b\x1b[7~",  TB_KEY_HOME,        TB_MOD_ALT                             },
1342    {"\x1b\x1b[7$",  TB_KEY_HOME,        TB_MOD_ALT | TB_MOD_SHIFT              },
1343    {"\x1b[7^",      TB_KEY_HOME,        TB_MOD_CTRL                            },
1344    {"\x1b[7@",      TB_KEY_HOME,        TB_MOD_CTRL | TB_MOD_SHIFT             },
1345    {"\x1b\x1b[7^",  TB_KEY_HOME,        TB_MOD_CTRL | TB_MOD_ALT               },
1346    {"\x1b\x1b[7@",  TB_KEY_HOME,        TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1347
1348    {"\x1b\x1b[8~",  TB_KEY_END,         TB_MOD_ALT                             },
1349    {"\x1b\x1b[8$",  TB_KEY_END,         TB_MOD_ALT | TB_MOD_SHIFT              },
1350    {"\x1b[8^",      TB_KEY_END,         TB_MOD_CTRL                            },
1351    {"\x1b\x1b[8^",  TB_KEY_END,         TB_MOD_CTRL | TB_MOD_ALT               },
1352    {"\x1b\x1b[8@",  TB_KEY_END,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1353    {"\x1b[8@",      TB_KEY_END,         TB_MOD_CTRL | TB_MOD_SHIFT             },
1354    {"\x1b[8$",      TB_KEY_END,         TB_MOD_SHIFT                           },
1355
1356    {"\x1b\x1b[2~",  TB_KEY_INSERT,      TB_MOD_ALT                             },
1357    {"\x1b\x1b[2$",  TB_KEY_INSERT,      TB_MOD_ALT | TB_MOD_SHIFT              },
1358    {"\x1b[2^",      TB_KEY_INSERT,      TB_MOD_CTRL                            },
1359    {"\x1b\x1b[2^",  TB_KEY_INSERT,      TB_MOD_CTRL | TB_MOD_ALT               },
1360    {"\x1b\x1b[2@",  TB_KEY_INSERT,      TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1361    {"\x1b[2@",      TB_KEY_INSERT,      TB_MOD_CTRL | TB_MOD_SHIFT             },
1362    {"\x1b[2$",      TB_KEY_INSERT,      TB_MOD_SHIFT                           },
1363
1364    {"\x1b\x1b[3~",  TB_KEY_DELETE,      TB_MOD_ALT                             },
1365    {"\x1b\x1b[3$",  TB_KEY_DELETE,      TB_MOD_ALT | TB_MOD_SHIFT              },
1366    {"\x1b[3^",      TB_KEY_DELETE,      TB_MOD_CTRL                            },
1367    {"\x1b\x1b[3^",  TB_KEY_DELETE,      TB_MOD_CTRL | TB_MOD_ALT               },
1368    {"\x1b\x1b[3@",  TB_KEY_DELETE,      TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1369    {"\x1b[3@",      TB_KEY_DELETE,      TB_MOD_CTRL | TB_MOD_SHIFT             },
1370    {"\x1b[3$",      TB_KEY_DELETE,      TB_MOD_SHIFT                           },
1371
1372    {"\x1b\x1b[5~",  TB_KEY_PGUP,        TB_MOD_ALT                             },
1373    {"\x1b\x1b[5$",  TB_KEY_PGUP,        TB_MOD_ALT | TB_MOD_SHIFT              },
1374    {"\x1b[5^",      TB_KEY_PGUP,        TB_MOD_CTRL                            },
1375    {"\x1b\x1b[5^",  TB_KEY_PGUP,        TB_MOD_CTRL | TB_MOD_ALT               },
1376    {"\x1b\x1b[5@",  TB_KEY_PGUP,        TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1377    {"\x1b[5@",      TB_KEY_PGUP,        TB_MOD_CTRL | TB_MOD_SHIFT             },
1378    {"\x1b[5$",      TB_KEY_PGUP,        TB_MOD_SHIFT                           },
1379
1380    {"\x1b\x1b[6~",  TB_KEY_PGDN,        TB_MOD_ALT                             },
1381    {"\x1b\x1b[6$",  TB_KEY_PGDN,        TB_MOD_ALT | TB_MOD_SHIFT              },
1382    {"\x1b[6^",      TB_KEY_PGDN,        TB_MOD_CTRL                            },
1383    {"\x1b\x1b[6^",  TB_KEY_PGDN,        TB_MOD_CTRL | TB_MOD_ALT               },
1384    {"\x1b\x1b[6@",  TB_KEY_PGDN,        TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1385    {"\x1b[6@",      TB_KEY_PGDN,        TB_MOD_CTRL | TB_MOD_SHIFT             },
1386    {"\x1b[6$",      TB_KEY_PGDN,        TB_MOD_SHIFT                           },
1387
1388    {"\x1b\x1b[11~", TB_KEY_F1,          TB_MOD_ALT                             },
1389    {"\x1b\x1b[23~", TB_KEY_F1,          TB_MOD_ALT | TB_MOD_SHIFT              },
1390    {"\x1b[11^",     TB_KEY_F1,          TB_MOD_CTRL                            },
1391    {"\x1b\x1b[11^", TB_KEY_F1,          TB_MOD_CTRL | TB_MOD_ALT               },
1392    {"\x1b\x1b[23^", TB_KEY_F1,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1393    {"\x1b[23^",     TB_KEY_F1,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1394    {"\x1b[23~",     TB_KEY_F1,          TB_MOD_SHIFT                           },
1395
1396    {"\x1b\x1b[12~", TB_KEY_F2,          TB_MOD_ALT                             },
1397    {"\x1b\x1b[24~", TB_KEY_F2,          TB_MOD_ALT | TB_MOD_SHIFT              },
1398    {"\x1b[12^",     TB_KEY_F2,          TB_MOD_CTRL                            },
1399    {"\x1b\x1b[12^", TB_KEY_F2,          TB_MOD_CTRL | TB_MOD_ALT               },
1400    {"\x1b\x1b[24^", TB_KEY_F2,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1401    {"\x1b[24^",     TB_KEY_F2,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1402    {"\x1b[24~",     TB_KEY_F2,          TB_MOD_SHIFT                           },
1403
1404    {"\x1b\x1b[13~", TB_KEY_F3,          TB_MOD_ALT                             },
1405    {"\x1b\x1b[25~", TB_KEY_F3,          TB_MOD_ALT | TB_MOD_SHIFT              },
1406    {"\x1b[13^",     TB_KEY_F3,          TB_MOD_CTRL                            },
1407    {"\x1b\x1b[13^", TB_KEY_F3,          TB_MOD_CTRL | TB_MOD_ALT               },
1408    {"\x1b\x1b[25^", TB_KEY_F3,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1409    {"\x1b[25^",     TB_KEY_F3,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1410    {"\x1b[25~",     TB_KEY_F3,          TB_MOD_SHIFT                           },
1411
1412    {"\x1b\x1b[14~", TB_KEY_F4,          TB_MOD_ALT                             },
1413    {"\x1b\x1b[26~", TB_KEY_F4,          TB_MOD_ALT | TB_MOD_SHIFT              },
1414    {"\x1b[14^",     TB_KEY_F4,          TB_MOD_CTRL                            },
1415    {"\x1b\x1b[14^", TB_KEY_F4,          TB_MOD_CTRL | TB_MOD_ALT               },
1416    {"\x1b\x1b[26^", TB_KEY_F4,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1417    {"\x1b[26^",     TB_KEY_F4,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1418    {"\x1b[26~",     TB_KEY_F4,          TB_MOD_SHIFT                           },
1419
1420    {"\x1b\x1b[15~", TB_KEY_F5,          TB_MOD_ALT                             },
1421    {"\x1b\x1b[28~", TB_KEY_F5,          TB_MOD_ALT | TB_MOD_SHIFT              },
1422    {"\x1b[15^",     TB_KEY_F5,          TB_MOD_CTRL                            },
1423    {"\x1b\x1b[15^", TB_KEY_F5,          TB_MOD_CTRL | TB_MOD_ALT               },
1424    {"\x1b\x1b[28^", TB_KEY_F5,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1425    {"\x1b[28^",     TB_KEY_F5,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1426    {"\x1b[28~",     TB_KEY_F5,          TB_MOD_SHIFT                           },
1427
1428    {"\x1b\x1b[17~", TB_KEY_F6,          TB_MOD_ALT                             },
1429    {"\x1b\x1b[29~", TB_KEY_F6,          TB_MOD_ALT | TB_MOD_SHIFT              },
1430    {"\x1b[17^",     TB_KEY_F6,          TB_MOD_CTRL                            },
1431    {"\x1b\x1b[17^", TB_KEY_F6,          TB_MOD_CTRL | TB_MOD_ALT               },
1432    {"\x1b\x1b[29^", TB_KEY_F6,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1433    {"\x1b[29^",     TB_KEY_F6,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1434    {"\x1b[29~",     TB_KEY_F6,          TB_MOD_SHIFT                           },
1435
1436    {"\x1b\x1b[18~", TB_KEY_F7,          TB_MOD_ALT                             },
1437    {"\x1b\x1b[31~", TB_KEY_F7,          TB_MOD_ALT | TB_MOD_SHIFT              },
1438    {"\x1b[18^",     TB_KEY_F7,          TB_MOD_CTRL                            },
1439    {"\x1b\x1b[18^", TB_KEY_F7,          TB_MOD_CTRL | TB_MOD_ALT               },
1440    {"\x1b\x1b[31^", TB_KEY_F7,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1441    {"\x1b[31^",     TB_KEY_F7,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1442    {"\x1b[31~",     TB_KEY_F7,          TB_MOD_SHIFT                           },
1443
1444    {"\x1b\x1b[19~", TB_KEY_F8,          TB_MOD_ALT                             },
1445    {"\x1b\x1b[32~", TB_KEY_F8,          TB_MOD_ALT | TB_MOD_SHIFT              },
1446    {"\x1b[19^",     TB_KEY_F8,          TB_MOD_CTRL                            },
1447    {"\x1b\x1b[19^", TB_KEY_F8,          TB_MOD_CTRL | TB_MOD_ALT               },
1448    {"\x1b\x1b[32^", TB_KEY_F8,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1449    {"\x1b[32^",     TB_KEY_F8,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1450    {"\x1b[32~",     TB_KEY_F8,          TB_MOD_SHIFT                           },
1451
1452    {"\x1b\x1b[20~", TB_KEY_F9,          TB_MOD_ALT                             },
1453    {"\x1b\x1b[33~", TB_KEY_F9,          TB_MOD_ALT | TB_MOD_SHIFT              },
1454    {"\x1b[20^",     TB_KEY_F9,          TB_MOD_CTRL                            },
1455    {"\x1b\x1b[20^", TB_KEY_F9,          TB_MOD_CTRL | TB_MOD_ALT               },
1456    {"\x1b\x1b[33^", TB_KEY_F9,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1457    {"\x1b[33^",     TB_KEY_F9,          TB_MOD_CTRL | TB_MOD_SHIFT             },
1458    {"\x1b[33~",     TB_KEY_F9,          TB_MOD_SHIFT                           },
1459
1460    {"\x1b\x1b[21~", TB_KEY_F10,         TB_MOD_ALT                             },
1461    {"\x1b\x1b[34~", TB_KEY_F10,         TB_MOD_ALT | TB_MOD_SHIFT              },
1462    {"\x1b[21^",     TB_KEY_F10,         TB_MOD_CTRL                            },
1463    {"\x1b\x1b[21^", TB_KEY_F10,         TB_MOD_CTRL | TB_MOD_ALT               },
1464    {"\x1b\x1b[34^", TB_KEY_F10,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1465    {"\x1b[34^",     TB_KEY_F10,         TB_MOD_CTRL | TB_MOD_SHIFT             },
1466    {"\x1b[34~",     TB_KEY_F10,         TB_MOD_SHIFT                           },
1467
1468    {"\x1b\x1b[23~", TB_KEY_F11,         TB_MOD_ALT                             },
1469    {"\x1b\x1b[23$", TB_KEY_F11,         TB_MOD_ALT | TB_MOD_SHIFT              },
1470    {"\x1b[23^",     TB_KEY_F11,         TB_MOD_CTRL                            },
1471    {"\x1b\x1b[23^", TB_KEY_F11,         TB_MOD_CTRL | TB_MOD_ALT               },
1472    {"\x1b\x1b[23@", TB_KEY_F11,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1473    {"\x1b[23@",     TB_KEY_F11,         TB_MOD_CTRL | TB_MOD_SHIFT             },
1474    {"\x1b[23$",     TB_KEY_F11,         TB_MOD_SHIFT                           },
1475
1476    {"\x1b\x1b[24~", TB_KEY_F12,         TB_MOD_ALT                             },
1477    {"\x1b\x1b[24$", TB_KEY_F12,         TB_MOD_ALT | TB_MOD_SHIFT              },
1478    {"\x1b[24^",     TB_KEY_F12,         TB_MOD_CTRL                            },
1479    {"\x1b\x1b[24^", TB_KEY_F12,         TB_MOD_CTRL | TB_MOD_ALT               },
1480    {"\x1b\x1b[24@", TB_KEY_F12,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
1481    {"\x1b[24@",     TB_KEY_F12,         TB_MOD_CTRL | TB_MOD_SHIFT             },
1482    {"\x1b[24$",     TB_KEY_F12,         TB_MOD_SHIFT                           },
1483
1484 // linux console/putty arrows
1485    {"\x1b[A",       TB_KEY_ARROW_UP,    TB_MOD_SHIFT                           },
1486    {"\x1b[B",       TB_KEY_ARROW_DOWN,  TB_MOD_SHIFT                           },
1487    {"\x1b[C",       TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT                           },
1488    {"\x1b[D",       TB_KEY_ARROW_LEFT,  TB_MOD_SHIFT                           },
1489
1490 // more putty arrows
1491    {"\x1bOA",       TB_KEY_ARROW_UP,    TB_MOD_CTRL                            },
1492    {"\x1b\x1bOA",   TB_KEY_ARROW_UP,    TB_MOD_CTRL | TB_MOD_ALT               },
1493    {"\x1bOB",       TB_KEY_ARROW_DOWN,  TB_MOD_CTRL                            },
1494    {"\x1b\x1bOB",   TB_KEY_ARROW_DOWN,  TB_MOD_CTRL | TB_MOD_ALT               },
1495    {"\x1bOC",       TB_KEY_ARROW_RIGHT, TB_MOD_CTRL                            },
1496    {"\x1b\x1bOC",   TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT               },
1497    {"\x1bOD",       TB_KEY_ARROW_LEFT,  TB_MOD_CTRL                            },
1498    {"\x1b\x1bOD",   TB_KEY_ARROW_LEFT,  TB_MOD_CTRL | TB_MOD_ALT               },
1499
1500    {NULL,           0,                  0                                      },
1501};
1502
1503static const unsigned char utf8_length[256] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1504    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1505    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1506    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1507    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1508    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1509    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1510    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1511    1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
1512    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
1513    3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1};
1514
1515static const unsigned char utf8_mask[6] = {0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01};
1516
1517static int tb_reset(void);
1518static int tb_printf_inner(int x, int y, uintattr_t fg, uintattr_t bg,
1519    size_t *out_w, const char *fmt, va_list vl);
1520static int init_term_attrs(void);
1521static int init_term_caps(void);
1522static int init_cap_trie(void);
1523static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod);
1524static int cap_trie_find(const char *buf, size_t nbuf, struct cap_trie_t **last,
1525    size_t *depth);
1526static int cap_trie_deinit(struct cap_trie_t *node);
1527static int init_resize_handler(void);
1528static int send_init_escape_codes(void);
1529static int send_clear(void);
1530static int update_term_size(void);
1531static int update_term_size_via_esc(void);
1532static int init_cellbuf(void);
1533static int tb_deinit(void);
1534static int load_terminfo(void);
1535static int load_terminfo_from_path(const char *path, const char *term);
1536static int read_terminfo_path(const char *path);
1537static int parse_terminfo_caps(void);
1538static int load_builtin_caps(void);
1539static const char *get_terminfo_string(int16_t str_offsets_pos,
1540    int16_t str_offsets_len, int16_t str_table_pos, int16_t str_table_len,
1541    int16_t str_index);
1542static int wait_event(struct tb_event *event, int timeout);
1543static int extract_event(struct tb_event *event);
1544static int extract_esc(struct tb_event *event);
1545static int extract_esc_user(struct tb_event *event, int is_post);
1546static int extract_esc_cap(struct tb_event *event);
1547static int extract_esc_mouse(struct tb_event *event);
1548static int resize_cellbufs(void);
1549static void handle_resize(int sig);
1550static int send_attr(uintattr_t fg, uintattr_t bg);
1551static int send_sgr(uint32_t fg, uint32_t bg, int fg_is_default,
1552    int bg_is_default);
1553static int send_cursor_if(int x, int y);
1554static int send_char(int x, int y, uint32_t ch);
1555static int send_cluster(int x, int y, uint32_t *ch, size_t nch);
1556static int convert_num(uint32_t num, char *buf);
1557static int cell_cmp(struct tb_cell *a, struct tb_cell *b);
1558static int cell_copy(struct tb_cell *dst, struct tb_cell *src);
1559static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch,
1560    uintattr_t fg, uintattr_t bg);
1561static int cell_reserve_ech(struct tb_cell *cell, size_t n);
1562static int cell_free(struct tb_cell *cell);
1563static int cellbuf_init(struct cellbuf_t *c, int w, int h);
1564static int cellbuf_free(struct cellbuf_t *c);
1565static int cellbuf_clear(struct cellbuf_t *c);
1566static int cellbuf_get(struct cellbuf_t *c, int x, int y, struct tb_cell **out);
1567static int cellbuf_in_bounds(struct cellbuf_t *c, int x, int y);
1568static int cellbuf_resize(struct cellbuf_t *c, int w, int h);
1569static int bytebuf_puts(struct bytebuf_t *b, const char *str);
1570static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr);
1571static int bytebuf_shift(struct bytebuf_t *b, size_t n);
1572static int bytebuf_flush(struct bytebuf_t *b, int fd);
1573static int bytebuf_reserve(struct bytebuf_t *b, size_t sz);
1574static int bytebuf_free(struct bytebuf_t *b);
1575
1576int tb_init(void) {
1577    return tb_init_file("/dev/tty");
1578}
1579
1580int tb_init_file(const char *path) {
1581    if (global.initialized) return TB_ERR_INIT_ALREADY;
1582    int ttyfd = open(path, O_RDWR);
1583    if (ttyfd < 0) {
1584        global.last_errno = errno;
1585        return TB_ERR_INIT_OPEN;
1586    }
1587    global.ttyfd_open = 1;
1588    return tb_init_fd(ttyfd);
1589}
1590
1591int tb_init_fd(int ttyfd) {
1592    return tb_init_rwfd(ttyfd, ttyfd);
1593}
1594
1595int tb_init_rwfd(int rfd, int wfd) {
1596    int rv;
1597
1598    tb_reset();
1599    global.ttyfd = rfd == wfd && isatty(rfd) ? rfd : -1;
1600    global.rfd = rfd;
1601    global.wfd = wfd;
1602
1603    do {
1604        if_err_break(rv, init_term_attrs());
1605        if_err_break(rv, init_term_caps());
1606        if_err_break(rv, init_cap_trie());
1607        if_err_break(rv, init_resize_handler());
1608        if_err_break(rv, send_init_escape_codes());
1609        if_err_break(rv, send_clear());
1610        if_err_break(rv, update_term_size());
1611        if_err_break(rv, init_cellbuf());
1612        global.initialized = 1;
1613    } while (0);
1614
1615    if (rv != TB_OK) {
1616        tb_deinit();
1617    }
1618
1619    return rv;
1620}
1621
1622int tb_shutdown(void) {
1623    if_not_init_return();
1624    tb_deinit();
1625    return TB_OK;
1626}
1627
1628int tb_width(void) {
1629    if_not_init_return();
1630    return global.width;
1631}
1632
1633int tb_height(void) {
1634    if_not_init_return();
1635    return global.height;
1636}
1637
1638int tb_clear(void) {
1639    if_not_init_return();
1640    return cellbuf_clear(&global.back);
1641}
1642
1643int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg) {
1644    if_not_init_return();
1645    global.fg = fg;
1646    global.bg = bg;
1647    return TB_OK;
1648}
1649
1650int tb_present(void) {
1651    if_not_init_return();
1652
1653    int rv;
1654
1655    // TODO: Assert global.back.(width,height) == global.front.(width,height)
1656
1657    global.last_x = -1;
1658    global.last_y = -1;
1659
1660    int x, y, i;
1661    for (y = 0; y < global.front.height; y++) {
1662        for (x = 0; x < global.front.width;) {
1663            struct tb_cell *back, *front;
1664            if_err_return(rv, cellbuf_get(&global.back, x, y, &back));
1665            if_err_return(rv, cellbuf_get(&global.front, x, y, &front));
1666
1667            int w;
1668            {
1669#ifdef TB_OPT_EGC
1670                if (back->nech > 0)
1671                    w = wcswidth((wchar_t *)back->ech, back->nech);
1672                else
1673#endif
1674                    // wcwidth simply returns -1 on overflow of wchar_t
1675                    w = wcwidth((wchar_t)back->ch);
1676            }
1677            if (w < 1) w = 1;
1678
1679            if (cell_cmp(back, front) != 0) {
1680                cell_copy(front, back);
1681
1682                send_attr(back->fg, back->bg);
1683                if (w > 1 && x >= global.front.width - (w - 1)) {
1684                    // Not enough room for wide char, send spaces
1685                    for (i = x; i < global.front.width; i++) {
1686                        send_char(i, y, ' ');
1687                    }
1688                } else {
1689                    {
1690#ifdef TB_OPT_EGC
1691                        if (back->nech > 0)
1692                            send_cluster(x, y, back->ech, back->nech);
1693                        else
1694#endif
1695                            send_char(x, y, back->ch);
1696                    }
1697
1698                    // When wcwidth>1, we need to advance the cursor by more
1699                    // than 1, thereby skipping some cells. Set these skipped
1700                    // cells to an invalid codepoint in the front buffer, so
1701                    // that if this cell is later replaced by a wcwidth==1 char,
1702                    // we'll get a cell_cmp diff for the skipped cells and
1703                    // properly re-render.
1704                    for (i = 1; i < w; i++) {
1705                        struct tb_cell *front_wide;
1706                        uint32_t invalid = -1;
1707                        if_err_return(rv,
1708                            cellbuf_get(&global.front, x + i, y, &front_wide));
1709                        if_err_return(rv,
1710                            cell_set(front_wide, &invalid, 1, -1, -1));
1711                    }
1712                }
1713            }
1714            x += w;
1715        }
1716    }
1717
1718    if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y));
1719    if_err_return(rv, bytebuf_flush(&global.out, global.wfd));
1720
1721    return TB_OK;
1722}
1723
1724int tb_invalidate(void) {
1725    int rv;
1726    if_not_init_return();
1727    if_err_return(rv, resize_cellbufs());
1728    return TB_OK;
1729}
1730
1731int tb_set_cursor(int cx, int cy) {
1732    if_not_init_return();
1733    int rv;
1734    if (cx < 0) cx = 0;
1735    if (cy < 0) cy = 0;
1736    if (global.cursor_x == -1) {
1737        if_err_return(rv,
1738            bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]));
1739    }
1740    if_err_return(rv, send_cursor_if(cx, cy));
1741    global.cursor_x = cx;
1742    global.cursor_y = cy;
1743    return TB_OK;
1744}
1745
1746int tb_hide_cursor(void) {
1747    if_not_init_return();
1748    int rv;
1749    if (global.cursor_x >= 0) {
1750        if_err_return(rv,
1751            bytebuf_puts(&global.out, global.caps[TB_CAP_HIDE_CURSOR]));
1752    }
1753    global.cursor_x = -1;
1754    global.cursor_y = -1;
1755    return TB_OK;
1756}
1757
1758int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg) {
1759    return tb_set_cell_ex(x, y, &ch, 1, fg, bg);
1760}
1761
1762int tb_set_cell_ex(int x, int y, uint32_t *ch, size_t nch, uintattr_t fg,
1763    uintattr_t bg) {
1764    if_not_init_return();
1765    int rv;
1766    struct tb_cell *cell;
1767    if_err_return(rv, cellbuf_get(&global.back, x, y, &cell));
1768    if_err_return(rv, cell_set(cell, ch, nch, fg, bg));
1769    return TB_OK;
1770}
1771
1772int tb_extend_cell(int x, int y, uint32_t ch) {
1773    if_not_init_return();
1774#ifdef TB_OPT_EGC
1775    int rv;
1776    struct tb_cell *cell;
1777    size_t nech;
1778    if_err_return(rv, cellbuf_get(&global.back, x, y, &cell));
1779    if (cell->nech > 0) { // append to ech
1780        nech = cell->nech + 1;
1781        if_err_return(rv, cell_reserve_ech(cell, nech));
1782        cell->ech[nech - 1] = ch;
1783    } else { // make new ech
1784        nech = 2;
1785        if_err_return(rv, cell_reserve_ech(cell, nech));
1786        cell->ech[0] = cell->ch;
1787        cell->ech[1] = ch;
1788    }
1789    cell->ech[nech] = '\0';
1790    cell->nech = nech;
1791    return TB_OK;
1792#else
1793    (void)x;
1794    (void)y;
1795    (void)ch;
1796    return TB_ERR;
1797#endif
1798}
1799
1800int tb_set_input_mode(int mode) {
1801    if_not_init_return();
1802    if (mode == TB_INPUT_CURRENT) {
1803        return global.input_mode;
1804    }
1805
1806    if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0) {
1807        mode |= TB_INPUT_ESC;
1808    }
1809
1810    if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT))
1811    {
1812        mode &= ~TB_INPUT_ALT;
1813    }
1814
1815    if (mode & TB_INPUT_MOUSE) {
1816        bytebuf_puts(&global.out, TB_HARDCAP_ENTER_MOUSE);
1817        bytebuf_flush(&global.out, global.wfd);
1818    } else {
1819        bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE);
1820        bytebuf_flush(&global.out, global.wfd);
1821    }
1822
1823    global.input_mode = mode;
1824    return TB_OK;
1825}
1826
1827int tb_set_output_mode(int mode) {
1828    if_not_init_return();
1829    switch (mode) {
1830        case TB_OUTPUT_CURRENT:
1831            return global.output_mode;
1832        case TB_OUTPUT_NORMAL:
1833        case TB_OUTPUT_256:
1834        case TB_OUTPUT_216:
1835        case TB_OUTPUT_GRAYSCALE:
1836#if TB_OPT_ATTR_W >= 32
1837        case TB_OUTPUT_TRUECOLOR:
1838#endif
1839            global.last_fg = ~global.fg;
1840            global.last_bg = ~global.bg;
1841            global.output_mode = mode;
1842            return TB_OK;
1843    }
1844    return TB_ERR;
1845}
1846
1847int tb_peek_event(struct tb_event *event, int timeout_ms) {
1848    if_not_init_return();
1849    return wait_event(event, timeout_ms);
1850}
1851
1852int tb_poll_event(struct tb_event *event) {
1853    if_not_init_return();
1854    return wait_event(event, -1);
1855}
1856
1857int tb_get_fds(int *ttyfd, int *resizefd) {
1858    if_not_init_return();
1859
1860    *ttyfd = global.rfd;
1861    *resizefd = global.resize_pipefd[0];
1862
1863    return TB_OK;
1864}
1865
1866int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str) {
1867    return tb_print_ex(x, y, fg, bg, NULL, str);
1868}
1869
1870int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
1871    const char *str) {
1872    int rv, w, ix;
1873    uint32_t uni;
1874
1875    if_not_init_return();
1876
1877    if (!cellbuf_in_bounds(&global.back, x, y)) {
1878        return TB_ERR_OUT_OF_BOUNDS;
1879    }
1880
1881    ix = x;
1882    if (out_w) *out_w = 0;
1883
1884    while (*str) {
1885        rv = tb_utf8_char_to_unicode(&uni, str);
1886
1887        if (rv < 0) {
1888            uni = 0xfffd; // replace invalid UTF-8 char with U+FFFD
1889            str += rv * -1;
1890        } else if (rv > 0) {
1891            str += rv;
1892        } else {
1893            break; // shouldn't get here
1894        }
1895
1896        if (uni == '\n') { // TODO: \r, \t, \v, \f, etc?
1897            x = ix;
1898            y += 1;
1899            continue;
1900        } else if (!iswprint((wint_t)uni)) {
1901            uni = 0xfffd; // replace non-printable with U+FFFD
1902        }
1903
1904        w = wcwidth((wchar_t)uni);
1905        if (w < 0) {
1906            return TB_ERR;   // shouldn't happen if iswprint
1907        } else if (w == 0) { // combining character
1908            if (cellbuf_in_bounds(&global.back, x - 1, y)) {
1909                if_err_return(rv, tb_extend_cell(x - 1, y, uni));
1910            }
1911        } else {
1912            if (cellbuf_in_bounds(&global.back, x, y)) {
1913                if_err_return(rv, tb_set_cell(x, y, uni, fg, bg));
1914            }
1915        }
1916
1917        x += w;
1918        if (out_w) *out_w += w;
1919    }
1920
1921    return TB_OK;
1922}
1923
1924int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt,
1925    ...) {
1926    int rv;
1927    va_list vl;
1928    va_start(vl, fmt);
1929    rv = tb_printf_inner(x, y, fg, bg, NULL, fmt, vl);
1930    va_end(vl);
1931    return rv;
1932}
1933
1934int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
1935    const char *fmt, ...) {
1936    int rv;
1937    va_list vl;
1938    va_start(vl, fmt);
1939    rv = tb_printf_inner(x, y, fg, bg, out_w, fmt, vl);
1940    va_end(vl);
1941    return rv;
1942}
1943
1944int tb_send(const char *buf, size_t nbuf) {
1945    return bytebuf_nputs(&global.out, buf, nbuf);
1946}
1947
1948int tb_sendf(const char *fmt, ...) {
1949    int rv;
1950    char buf[TB_OPT_PRINTF_BUF];
1951    va_list vl;
1952    va_start(vl, fmt);
1953    rv = vsnprintf(buf, sizeof(buf), fmt, vl);
1954    va_end(vl);
1955    if (rv < 0 || rv >= (int)sizeof(buf)) {
1956        return TB_ERR;
1957    }
1958    return tb_send(buf, (size_t)rv);
1959}
1960
1961int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *)) {
1962    switch (fn_type) {
1963        case TB_FUNC_EXTRACT_PRE:
1964            global.fn_extract_esc_pre = fn;
1965            return TB_OK;
1966        case TB_FUNC_EXTRACT_POST:
1967            global.fn_extract_esc_post = fn;
1968            return TB_OK;
1969    }
1970    return TB_ERR;
1971}
1972
1973struct tb_cell *tb_cell_buffer(void) {
1974    if (!global.initialized) return NULL;
1975    return global.back.cells;
1976}
1977
1978int tb_utf8_char_length(char c) {
1979    return utf8_length[(unsigned char)c];
1980}
1981
1982int tb_utf8_char_to_unicode(uint32_t *out, const char *c) {
1983    if (*c == '\0') return 0;
1984
1985    int i;
1986    unsigned char len = tb_utf8_char_length(*c);
1987    unsigned char mask = utf8_mask[len - 1];
1988    uint32_t result = c[0] & mask;
1989    for (i = 1; i < len && c[i] != '\0'; ++i) {
1990        result <<= 6;
1991        result |= c[i] & 0x3f;
1992    }
1993
1994    if (i != len) return i * -1;
1995
1996    *out = result;
1997    return (int)len;
1998}
1999
2000int tb_utf8_unicode_to_char(char *out, uint32_t c) {
2001    int len = 0;
2002    int first;
2003    int i;
2004
2005    if (c < 0x80) {
2006        first = 0;
2007        len = 1;
2008    } else if (c < 0x800) {
2009        first = 0xc0;
2010        len = 2;
2011    } else if (c < 0x10000) {
2012        first = 0xe0;
2013        len = 3;
2014    } else if (c < 0x200000) {
2015        first = 0xf0;
2016        len = 4;
2017    } else if (c < 0x4000000) {
2018        first = 0xf8;
2019        len = 5;
2020    } else {
2021        first = 0xfc;
2022        len = 6;
2023    }
2024
2025    for (i = len - 1; i > 0; --i) {
2026        out[i] = (c & 0x3f) | 0x80;
2027        c >>= 6;
2028    }
2029    out[0] = c | first;
2030    out[len] = '\0';
2031
2032    return len;
2033}
2034
2035int tb_last_errno(void) {
2036    return global.last_errno;
2037}
2038
2039const char *tb_strerror(int err) {
2040    switch (err) {
2041        case TB_OK:
2042            return "Success";
2043        case TB_ERR_NEED_MORE:
2044            return "Not enough input";
2045        case TB_ERR_INIT_ALREADY:
2046            return "Termbox initialized already";
2047        case TB_ERR_MEM:
2048            return "Out of memory";
2049        case TB_ERR_NO_EVENT:
2050            return "No event";
2051        case TB_ERR_NO_TERM:
2052            return "No TERM in environment";
2053        case TB_ERR_NOT_INIT:
2054            return "Termbox not initialized";
2055        case TB_ERR_OUT_OF_BOUNDS:
2056            return "Out of bounds";
2057        case TB_ERR_UNSUPPORTED_TERM:
2058            return "Unsupported terminal";
2059        case TB_ERR_CAP_COLLISION:
2060            return "Termcaps collision";
2061        case TB_ERR_RESIZE_SSCANF:
2062            return "Terminal width/height not received by sscanf() after "
2063                   "resize";
2064        case TB_ERR:
2065        case TB_ERR_INIT_OPEN:
2066        case TB_ERR_READ:
2067        case TB_ERR_RESIZE_IOCTL:
2068        case TB_ERR_RESIZE_PIPE:
2069        case TB_ERR_RESIZE_SIGACTION:
2070        case TB_ERR_POLL:
2071        case TB_ERR_TCGETATTR:
2072        case TB_ERR_TCSETATTR:
2073        case TB_ERR_RESIZE_WRITE:
2074        case TB_ERR_RESIZE_POLL:
2075        case TB_ERR_RESIZE_READ:
2076        default:
2077            strerror_r(global.last_errno, global.errbuf, sizeof(global.errbuf));
2078            return (const char *)global.errbuf;
2079    }
2080}
2081
2082int tb_has_truecolor(void) {
2083#if TB_OPT_ATTR_W >= 32
2084    return 1;
2085#else
2086    return 0;
2087#endif
2088}
2089
2090int tb_has_egc(void) {
2091#ifdef TB_OPT_EGC
2092    return 1;
2093#else
2094    return 0;
2095#endif
2096}
2097
2098int tb_attr_width(void) {
2099    return TB_OPT_ATTR_W;
2100}
2101
2102const char *tb_version(void) {
2103    return TB_VERSION_STR;
2104}
2105
2106static int tb_reset(void) {
2107    int ttyfd_open = global.ttyfd_open;
2108    memset(&global, 0, sizeof(global));
2109    global.ttyfd = -1;
2110    global.rfd = -1;
2111    global.wfd = -1;
2112    global.ttyfd_open = ttyfd_open;
2113    global.resize_pipefd[0] = -1;
2114    global.resize_pipefd[1] = -1;
2115    global.width = -1;
2116    global.height = -1;
2117    global.cursor_x = -1;
2118    global.cursor_y = -1;
2119    global.last_x = -1;
2120    global.last_y = -1;
2121    global.fg = TB_DEFAULT;
2122    global.bg = TB_DEFAULT;
2123    global.last_fg = ~global.fg;
2124    global.last_bg = ~global.bg;
2125    global.input_mode = TB_INPUT_ESC;
2126    global.output_mode = TB_OUTPUT_NORMAL;
2127    return TB_OK;
2128}
2129
2130static int init_term_attrs(void) {
2131    if (global.ttyfd < 0) {
2132        return TB_OK;
2133    }
2134
2135    if (tcgetattr(global.ttyfd, &global.orig_tios) != 0) {
2136        global.last_errno = errno;
2137        return TB_ERR_TCGETATTR;
2138    }
2139
2140    struct termios tios;
2141    memcpy(&tios, &global.orig_tios, sizeof(tios));
2142    global.has_orig_tios = 1;
2143
2144    cfmakeraw(&tios);
2145    tios.c_cc[VMIN] = 1;
2146    tios.c_cc[VTIME] = 0;
2147
2148    if (tcsetattr(global.ttyfd, TCSAFLUSH, &tios) != 0) {
2149        global.last_errno = errno;
2150        return TB_ERR_TCSETATTR;
2151    }
2152
2153    return TB_OK;
2154}
2155
2156int tb_printf_inner(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
2157    const char *fmt, va_list vl) {
2158    int rv;
2159    char buf[TB_OPT_PRINTF_BUF];
2160    rv = vsnprintf(buf, sizeof(buf), fmt, vl);
2161    if (rv < 0 || rv >= (int)sizeof(buf)) {
2162        return TB_ERR;
2163    }
2164    return tb_print_ex(x, y, fg, bg, out_w, buf);
2165}
2166
2167static int init_term_caps(void) {
2168    if (load_terminfo() == TB_OK) {
2169        return parse_terminfo_caps();
2170    }
2171    return load_builtin_caps();
2172}
2173
2174static int init_cap_trie(void) {
2175    int rv, i;
2176
2177    // Add caps from terminfo or built-in
2178    //
2179    // Collisions are expected as some terminfo entries have dupes. (For
2180    // example, att605-pc collides on TB_CAP_F4 and TB_CAP_DELETE.) First cap
2181    // in TB_CAP_* index order will win.
2182    //
2183    // TODO: Reorder TB_CAP_* so more critical caps come first.
2184    for (i = 0; i < TB_CAP__COUNT_KEYS; i++) {
2185        rv = cap_trie_add(global.caps[i], tb_key_i(i), 0);
2186        if (rv != TB_OK && rv != TB_ERR_CAP_COLLISION) return rv;
2187    }
2188
2189    // Add built-in mod caps
2190    //
2191    // Collisions are OK here as well. This can happen if global.caps collides
2192    // with builtin_mod_caps. It is desirable to give precedence to global.caps
2193    // here.
2194    for (i = 0; builtin_mod_caps[i].cap != NULL; i++) {
2195        rv = cap_trie_add(builtin_mod_caps[i].cap, builtin_mod_caps[i].key,
2196            builtin_mod_caps[i].mod);
2197        if (rv != TB_OK && rv != TB_ERR_CAP_COLLISION) return rv;
2198    }
2199
2200    return TB_OK;
2201}
2202
2203static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod) {
2204    struct cap_trie_t *next, *node = &global.cap_trie;
2205    size_t i, j;
2206
2207    if (!cap || strlen(cap) <= 0) return TB_OK; // Nothing to do for empty caps
2208
2209    for (i = 0; cap[i] != '\0'; i++) {
2210        char c = cap[i];
2211        next = NULL;
2212
2213        // Check if c is already a child of node
2214        for (j = 0; j < node->nchildren; j++) {
2215            if (node->children[j].c == c) {
2216                next = &node->children[j];
2217                break;
2218            }
2219        }
2220        if (!next) {
2221            // We need to add a new child to node
2222            node->nchildren += 1;
2223            node->children =
2224                tb_realloc(node->children, sizeof(*node) * node->nchildren);
2225            if (!node->children) {
2226                return TB_ERR_MEM;
2227            }
2228            next = &node->children[node->nchildren - 1];
2229            memset(next, 0, sizeof(*next));
2230            next->c = c;
2231        }
2232
2233        // Continue
2234        node = next;
2235    }
2236
2237    if (node->is_leaf) {
2238        // Already a leaf here
2239        return TB_ERR_CAP_COLLISION;
2240    }
2241
2242    node->is_leaf = 1;
2243    node->key = key;
2244    node->mod = mod;
2245    return TB_OK;
2246}
2247
2248static int cap_trie_find(const char *buf, size_t nbuf, struct cap_trie_t **last,
2249    size_t *depth) {
2250    struct cap_trie_t *next, *node = &global.cap_trie;
2251    size_t i, j;
2252    *last = node;
2253    *depth = 0;
2254    for (i = 0; i < nbuf; i++) {
2255        char c = buf[i];
2256        next = NULL;
2257
2258        // Find c in node.children
2259        for (j = 0; j < node->nchildren; j++) {
2260            if (node->children[j].c == c) {
2261                next = &node->children[j];
2262                break;
2263            }
2264        }
2265        if (!next) {
2266            // Not found
2267            return TB_OK;
2268        }
2269        node = next;
2270        *last = node;
2271        *depth += 1;
2272        if (node->is_leaf && node->nchildren < 1) {
2273            break;
2274        }
2275    }
2276    return TB_OK;
2277}
2278
2279static int cap_trie_deinit(struct cap_trie_t *node) {
2280    size_t j;
2281    for (j = 0; j < node->nchildren; j++) {
2282        cap_trie_deinit(&node->children[j]);
2283    }
2284    if (node->children) {
2285        tb_free(node->children);
2286    }
2287    memset(node, 0, sizeof(*node));
2288    return TB_OK;
2289}
2290
2291static int init_resize_handler(void) {
2292    if (pipe(global.resize_pipefd) != 0) {
2293        global.last_errno = errno;
2294        return TB_ERR_RESIZE_PIPE;
2295    }
2296
2297    struct sigaction sa;
2298    memset(&sa, 0, sizeof(sa));
2299    sa.sa_handler = handle_resize;
2300    if (sigaction(SIGWINCH, &sa, NULL) != 0) {
2301        global.last_errno = errno;
2302        return TB_ERR_RESIZE_SIGACTION;
2303    }
2304
2305    return TB_OK;
2306}
2307
2308static int send_init_escape_codes(void) {
2309    int rv;
2310    if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_ENTER_CA]));
2311    if_err_return(rv,
2312        bytebuf_puts(&global.out, global.caps[TB_CAP_ENTER_KEYPAD]));
2313    if_err_return(rv,
2314        bytebuf_puts(&global.out, global.caps[TB_CAP_HIDE_CURSOR]));
2315    return TB_OK;
2316}
2317
2318static int send_clear(void) {
2319    int rv;
2320
2321    if_err_return(rv, send_attr(global.fg, global.bg));
2322    if_err_return(rv,
2323        bytebuf_puts(&global.out, global.caps[TB_CAP_CLEAR_SCREEN]));
2324
2325    if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y));
2326    if_err_return(rv, bytebuf_flush(&global.out, global.wfd));
2327
2328    global.last_x = -1;
2329    global.last_y = -1;
2330
2331    return TB_OK;
2332}
2333
2334static int update_term_size(void) {
2335    int rv, ioctl_errno;
2336
2337    if (global.ttyfd < 0) {
2338        return TB_OK;
2339    }
2340
2341    struct winsize sz;
2342    memset(&sz, 0, sizeof(sz));
2343
2344    // Try ioctl TIOCGWINSZ
2345    if (ioctl(global.ttyfd, TIOCGWINSZ, &sz) == 0) {
2346        global.width = sz.ws_col;
2347        global.height = sz.ws_row;
2348        return TB_OK;
2349    }
2350    ioctl_errno = errno;
2351
2352    // Try >cursor(9999,9999), >u7, <u6
2353    if_ok_return(rv, update_term_size_via_esc());
2354
2355    global.last_errno = ioctl_errno;
2356    return TB_ERR_RESIZE_IOCTL;
2357}
2358
2359static int update_term_size_via_esc(void) {
2360#ifndef TB_RESIZE_FALLBACK_MS
2361#define TB_RESIZE_FALLBACK_MS 1000
2362#endif
2363
2364    char *move_and_report = "\x1b[9999;9999H\x1b[6n";
2365    ssize_t write_rv =
2366        write(global.wfd, move_and_report, strlen(move_and_report));
2367    if (write_rv != (ssize_t)strlen(move_and_report)) {
2368        return TB_ERR_RESIZE_WRITE;
2369    }
2370
2371    fd_set fds;
2372    FD_ZERO(&fds);
2373    FD_SET(global.rfd, &fds);
2374
2375    struct timeval timeout;
2376    timeout.tv_sec = 0;
2377    timeout.tv_usec = TB_RESIZE_FALLBACK_MS * 1000;
2378
2379    int select_rv = select(global.rfd + 1, &fds, NULL, NULL, &timeout);
2380
2381    if (select_rv != 1) {
2382        global.last_errno = errno;
2383        return TB_ERR_RESIZE_POLL;
2384    }
2385
2386    char buf[TB_OPT_READ_BUF];
2387    ssize_t read_rv = read(global.rfd, buf, sizeof(buf) - 1);
2388    if (read_rv < 1) {
2389        global.last_errno = errno;
2390        return TB_ERR_RESIZE_READ;
2391    }
2392    buf[read_rv] = '\0';
2393
2394    int rw, rh;
2395    if (sscanf(buf, "\x1b[%d;%dR", &rh, &rw) != 2) {
2396        return TB_ERR_RESIZE_SSCANF;
2397    }
2398
2399    global.width = rw;
2400    global.height = rh;
2401    return TB_OK;
2402}
2403
2404static int init_cellbuf(void) {
2405    int rv;
2406    if_err_return(rv, cellbuf_init(&global.back, global.width, global.height));
2407    if_err_return(rv, cellbuf_init(&global.front, global.width, global.height));
2408    if_err_return(rv, cellbuf_clear(&global.back));
2409    if_err_return(rv, cellbuf_clear(&global.front));
2410    return TB_OK;
2411}
2412
2413static int tb_deinit(void) {
2414    if (global.caps[0] != NULL && global.wfd >= 0) {
2415        bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]);
2416        bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0]);
2417        bytebuf_puts(&global.out, global.caps[TB_CAP_CLEAR_SCREEN]);
2418        bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_CA]);
2419        bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_KEYPAD]);
2420        bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE);
2421        bytebuf_flush(&global.out, global.wfd);
2422    }
2423    if (global.ttyfd >= 0) {
2424        if (global.has_orig_tios) {
2425            tcsetattr(global.ttyfd, TCSAFLUSH, &global.orig_tios);
2426        }
2427        if (global.ttyfd_open) {
2428            close(global.ttyfd);
2429            global.ttyfd_open = 0;
2430        }
2431    }
2432
2433    sigaction(SIGWINCH, &(struct sigaction){.sa_handler = SIG_DFL}, NULL);
2434    if (global.resize_pipefd[0] >= 0) close(global.resize_pipefd[0]);
2435    if (global.resize_pipefd[1] >= 0) close(global.resize_pipefd[1]);
2436
2437    cellbuf_free(&global.back);
2438    cellbuf_free(&global.front);
2439    bytebuf_free(&global.in);
2440    bytebuf_free(&global.out);
2441
2442    if (global.terminfo) tb_free(global.terminfo);
2443
2444    cap_trie_deinit(&global.cap_trie);
2445
2446    tb_reset();
2447    return TB_OK;
2448}
2449
2450static int load_terminfo(void) {
2451    int rv;
2452    char tmp[TB_PATH_MAX];
2453
2454    // See terminfo(5) "Fetching Compiled Descriptions" for a description of
2455    // this behavior. Some of these paths are compile-time ncurses options, so
2456    // best guesses are used here.
2457    const char *term = getenv("TERM");
2458    if (!term) {
2459        return TB_ERR;
2460    }
2461
2462    // If TERMINFO is set, try that directory and stop
2463    const char *terminfo = getenv("TERMINFO");
2464    if (terminfo) {
2465        return load_terminfo_from_path(terminfo, term);
2466    }
2467
2468    // Next try ~/.terminfo
2469    const char *home = getenv("HOME");
2470    if (home) {
2471        snprintf_or_return(rv, tmp, sizeof(tmp), "%s/.terminfo", home);
2472        if_ok_return(rv, load_terminfo_from_path(tmp, term));
2473    }
2474
2475    // Next try TERMINFO_DIRS
2476    //
2477    // Note, empty entries are supposed to be interpretted as the "compiled-in
2478    // default", which is of course system-dependent. Previously /etc/terminfo
2479    // was used here. Let's skip empty entries altogether rather than give
2480    // precedence to a guess, and check common paths after this loop.
2481    const char *dirs = getenv("TERMINFO_DIRS");
2482    if (dirs) {
2483        snprintf_or_return(rv, tmp, sizeof(tmp), "%s", dirs);
2484        char *dir = strtok(tmp, ":");
2485        while (dir) {
2486            const char *cdir = dir;
2487            if (*cdir != '\0') {
2488                if_ok_return(rv, load_terminfo_from_path(cdir, term));
2489            }
2490            dir = strtok(NULL, ":");
2491        }
2492    }
2493
2494#ifdef TB_TERMINFO_DIR
2495    if_ok_return(rv, load_terminfo_from_path(TB_TERMINFO_DIR, term));
2496#endif
2497    if_ok_return(rv, load_terminfo_from_path("/usr/local/etc/terminfo", term));
2498    if_ok_return(rv,
2499        load_terminfo_from_path("/usr/local/share/terminfo", term));
2500    if_ok_return(rv, load_terminfo_from_path("/usr/local/lib/terminfo", term));
2501    if_ok_return(rv, load_terminfo_from_path("/etc/terminfo", term));
2502    if_ok_return(rv, load_terminfo_from_path("/usr/share/terminfo", term));
2503    if_ok_return(rv, load_terminfo_from_path("/usr/lib/terminfo", term));
2504    if_ok_return(rv, load_terminfo_from_path("/usr/share/lib/terminfo", term));
2505    if_ok_return(rv, load_terminfo_from_path("/lib/terminfo", term));
2506
2507    return TB_ERR;
2508}
2509
2510static int load_terminfo_from_path(const char *path, const char *term) {
2511    int rv;
2512    char tmp[TB_PATH_MAX];
2513
2514    // Look for term at this terminfo location, e.g., <terminfo>/x/xterm
2515    snprintf_or_return(rv, tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term);
2516    if_ok_return(rv, read_terminfo_path(tmp));
2517
2518#ifdef __APPLE__
2519    // Try the Darwin equivalent path, e.g., <terminfo>/78/xterm
2520    snprintf_or_return(rv, tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term);
2521    return read_terminfo_path(tmp);
2522#endif
2523
2524    return TB_ERR;
2525}
2526
2527static int read_terminfo_path(const char *path) {
2528    FILE *fp = fopen(path, "rb");
2529    if (!fp) {
2530        return TB_ERR;
2531    }
2532
2533    struct stat st;
2534    if (fstat(fileno(fp), &st) != 0) {
2535        fclose(fp);
2536        return TB_ERR;
2537    }
2538
2539    size_t fsize = st.st_size;
2540    char *data = tb_malloc(fsize);
2541    if (!data) {
2542        fclose(fp);
2543        return TB_ERR;
2544    }
2545
2546    if (fread(data, 1, fsize, fp) != fsize) {
2547        fclose(fp);
2548        tb_free(data);
2549        return TB_ERR;
2550    }
2551
2552    global.terminfo = data;
2553    global.nterminfo = fsize;
2554
2555    fclose(fp);
2556    return TB_OK;
2557}
2558
2559static int parse_terminfo_caps(void) {
2560    // See term(5) "LEGACY STORAGE FORMAT" and "EXTENDED STORAGE FORMAT" for a
2561    // description of this behavior.
2562
2563    // Ensure there's at least a header's worth of data
2564    if (global.nterminfo < 6) {
2565        return TB_ERR;
2566    }
2567
2568    int16_t *header = (int16_t *)global.terminfo;
2569    // header[0] the magic number (octal 0432 or 01036)
2570    // header[1] the size, in bytes, of the names section
2571    // header[2] the number of bytes in the boolean section
2572    // header[3] the number of short integers in the numbers section
2573    // header[4] the number of offsets (short integers) in the strings section
2574    // header[5] the size, in bytes, of the string table
2575
2576    // Legacy ints are 16-bit, extended ints are 32-bit
2577    const int bytes_per_int = header[0] == 01036 ? 4  // 32-bit
2578                                                 : 2; // 16-bit
2579
2580    // > Between the boolean section and the number section, a null byte will be
2581    // > inserted, if necessary, to ensure that the number section begins on an
2582    // > even byte
2583    const int align_offset = (header[1] + header[2]) % 2 != 0 ? 1 : 0;
2584
2585    const int pos_str_offsets =
2586        (6 * sizeof(int16_t)) // header (12 bytes)
2587        + header[1]           // length of names section
2588        + header[2]           // length of boolean section
2589        + align_offset +
2590        (header[3] * bytes_per_int); // length of numbers section
2591
2592    const int pos_str_table =
2593        pos_str_offsets +
2594        (header[4] * sizeof(int16_t)); // length of string offsets table
2595
2596    // Load caps
2597    int i;
2598    for (i = 0; i < TB_CAP__COUNT; i++) {
2599        const char *cap = get_terminfo_string(pos_str_offsets, header[4],
2600            pos_str_table, header[5], terminfo_cap_indexes[i]);
2601        if (!cap) {
2602            // Something is not right
2603            return TB_ERR;
2604        }
2605        global.caps[i] = cap;
2606    }
2607
2608    return TB_OK;
2609}
2610
2611static int load_builtin_caps(void) {
2612    int i, j;
2613    const char *term = getenv("TERM");
2614
2615    if (!term) {
2616        return TB_ERR_NO_TERM;
2617    }
2618
2619    // Check for exact TERM match
2620    for (i = 0; builtin_terms[i].name != NULL; i++) {
2621        if (strcmp(term, builtin_terms[i].name) == 0) {
2622            for (j = 0; j < TB_CAP__COUNT; j++) {
2623                global.caps[j] = builtin_terms[i].caps[j];
2624            }
2625            return TB_OK;
2626        }
2627    }
2628
2629    // Check for partial TERM or alias match
2630    for (i = 0; builtin_terms[i].name != NULL; i++) {
2631        if (strstr(term, builtin_terms[i].name) != NULL ||
2632            (*(builtin_terms[i].alias) != '\0' &&
2633                strstr(term, builtin_terms[i].alias) != NULL))
2634        {
2635            for (j = 0; j < TB_CAP__COUNT; j++) {
2636                global.caps[j] = builtin_terms[i].caps[j];
2637            }
2638            return TB_OK;
2639        }
2640    }
2641
2642    return TB_ERR_UNSUPPORTED_TERM;
2643}
2644
2645static const char *get_terminfo_string(int16_t str_offsets_pos,
2646    int16_t str_offsets_len, int16_t str_table_pos, int16_t str_table_len,
2647    int16_t str_index) {
2648    const int str_byte_index = (int)str_index * (int)sizeof(int16_t);
2649    if (str_byte_index >= (int)str_offsets_len * (int)sizeof(int16_t)) {
2650        // An offset beyond the table indicates absent
2651        // See `convert_strings` in tinfo `read_entry.c`
2652        return "";
2653    }
2654    const int16_t *str_offset =
2655        (int16_t *)(global.terminfo + (int)str_offsets_pos + str_byte_index);
2656    if ((char *)str_offset >= global.terminfo + global.nterminfo) {
2657        // str_offset points beyond end of entry
2658        // Truncated/corrupt terminfo entry?
2659        return NULL;
2660    }
2661    if (*str_offset < 0 || *str_offset >= str_table_len) {
2662        // A negative offset indicates absent
2663        // An offset beyond the table indicates absent
2664        // See `convert_strings` in tinfo `read_entry.c`
2665        return "";
2666    }
2667    if (((size_t)((int)str_table_pos + (int)*str_offset)) >= global.nterminfo) {
2668        // string points beyond end of entry
2669        // Truncated/corrupt terminfo entry?
2670        return NULL;
2671    }
2672    return (
2673        const char *)(global.terminfo + (int)str_table_pos + (int)*str_offset);
2674}
2675
2676static int wait_event(struct tb_event *event, int timeout) {
2677    int rv;
2678    char buf[TB_OPT_READ_BUF];
2679
2680    memset(event, 0, sizeof(*event));
2681    if_ok_return(rv, extract_event(event));
2682
2683    fd_set fds;
2684    struct timeval tv;
2685    tv.tv_sec = timeout / 1000;
2686    tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
2687
2688    do {
2689        FD_ZERO(&fds);
2690        FD_SET(global.rfd, &fds);
2691        FD_SET(global.resize_pipefd[0], &fds);
2692
2693        int maxfd = global.resize_pipefd[0] > global.rfd
2694                        ? global.resize_pipefd[0]
2695                        : global.rfd;
2696
2697        int select_rv =
2698            select(maxfd + 1, &fds, NULL, NULL, (timeout < 0) ? NULL : &tv);
2699
2700        if (select_rv < 0) {
2701            // Let EINTR/EAGAIN bubble up
2702            global.last_errno = errno;
2703            return TB_ERR_POLL;
2704        } else if (select_rv == 0) {
2705            return TB_ERR_NO_EVENT;
2706        }
2707
2708        int tty_has_events = (FD_ISSET(global.rfd, &fds));
2709        int resize_has_events = (FD_ISSET(global.resize_pipefd[0], &fds));
2710
2711        if (tty_has_events) {
2712            ssize_t read_rv = read(global.rfd, buf, sizeof(buf));
2713            if (read_rv < 0) {
2714                global.last_errno = errno;
2715                return TB_ERR_READ;
2716            } else if (read_rv > 0) {
2717                bytebuf_nputs(&global.in, buf, read_rv);
2718            }
2719        }
2720
2721        if (resize_has_events) {
2722            int ignore = 0;
2723            read(global.resize_pipefd[0], &ignore, sizeof(ignore));
2724            // TODO: Harden against errors encountered mid-resize
2725            if_err_return(rv, update_term_size());
2726            if_err_return(rv, resize_cellbufs());
2727            event->type = TB_EVENT_RESIZE;
2728            event->w = global.width;
2729            event->h = global.height;
2730            return TB_OK;
2731        }
2732
2733        memset(event, 0, sizeof(*event));
2734        if_ok_return(rv, extract_event(event));
2735    } while (timeout == -1);
2736
2737    return rv;
2738}
2739
2740static int extract_event(struct tb_event *event) {
2741    int rv;
2742    struct bytebuf_t *in = &global.in;
2743
2744    if (in->len == 0) {
2745        return TB_ERR;
2746    }
2747
2748    if (in->buf[0] == '\x1b') {
2749        // Escape sequence?
2750        // In TB_INPUT_ESC, skip if the buffer is a single escape char
2751        if (!((global.input_mode & TB_INPUT_ESC) && in->len == 1)) {
2752            if_ok_or_need_more_return(rv, extract_esc(event));
2753        }
2754
2755        // Escape key?
2756        if (global.input_mode & TB_INPUT_ESC) {
2757            event->type = TB_EVENT_KEY;
2758            event->ch = 0;
2759            event->key = TB_KEY_ESC;
2760            event->mod = 0;
2761            bytebuf_shift(in, 1);
2762            return TB_OK;
2763        }
2764
2765        // Recurse for alt key
2766        event->mod |= TB_MOD_ALT;
2767        bytebuf_shift(in, 1);
2768        return extract_event(event);
2769    }
2770
2771    // ASCII control key?
2772    if ((uint16_t)in->buf[0] < TB_KEY_SPACE || in->buf[0] == TB_KEY_BACKSPACE2)
2773    {
2774        event->type = TB_EVENT_KEY;
2775        event->ch = 0;
2776        event->key = (uint16_t)in->buf[0];
2777        event->mod |= TB_MOD_CTRL;
2778        bytebuf_shift(in, 1);
2779        return TB_OK;
2780    }
2781
2782    // UTF-8?
2783    if (in->len >= (size_t)tb_utf8_char_length(in->buf[0])) {
2784        event->type = TB_EVENT_KEY;
2785        tb_utf8_char_to_unicode(&event->ch, in->buf);
2786        event->key = 0;
2787        bytebuf_shift(in, tb_utf8_char_length(in->buf[0]));
2788        return TB_OK;
2789    }
2790
2791    // Need more input
2792    return TB_ERR;
2793}
2794
2795static int extract_esc(struct tb_event *event) {
2796    int rv;
2797    if_ok_or_need_more_return(rv, extract_esc_user(event, 0));
2798    if_ok_or_need_more_return(rv, extract_esc_cap(event));
2799    if_ok_or_need_more_return(rv, extract_esc_mouse(event));
2800    if_ok_or_need_more_return(rv, extract_esc_user(event, 1));
2801    return TB_ERR;
2802}
2803
2804static int extract_esc_user(struct tb_event *event, int is_post) {
2805    int rv;
2806    size_t consumed = 0;
2807    struct bytebuf_t *in = &global.in;
2808    int (*fn)(struct tb_event *, size_t *);
2809
2810    fn = is_post ? global.fn_extract_esc_post : global.fn_extract_esc_pre;
2811
2812    if (!fn) {
2813        return TB_ERR;
2814    }
2815
2816    rv = fn(event, &consumed);
2817    if (rv == TB_OK) {
2818        bytebuf_shift(in, consumed);
2819    }
2820
2821    if_ok_or_need_more_return(rv, rv);
2822    return TB_ERR;
2823}
2824
2825static int extract_esc_cap(struct tb_event *event) {
2826    int rv;
2827    struct bytebuf_t *in = &global.in;
2828    struct cap_trie_t *node;
2829    size_t depth;
2830
2831    if_err_return(rv, cap_trie_find(in->buf, in->len, &node, &depth));
2832    if (node->is_leaf) {
2833        // Found a leaf node
2834        event->type = TB_EVENT_KEY;
2835        event->ch = 0;
2836        event->key = node->key;
2837        event->mod = node->mod;
2838        bytebuf_shift(in, depth);
2839        return TB_OK;
2840    } else if (node->nchildren > 0 && in->len <= depth) {
2841        // Found a branch node (not enough input)
2842        return TB_ERR_NEED_MORE;
2843    }
2844
2845    return TB_ERR;
2846}
2847
2848static int extract_esc_mouse(struct tb_event *event) {
2849    struct bytebuf_t *in = &global.in;
2850
2851    enum type { TYPE_VT200 = 0, TYPE_1006, TYPE_1015, TYPE_MAX };
2852
2853    const char *cmp[TYPE_MAX] = {//
2854        // X10 mouse encoding, the simplest one
2855        // \x1b [ M Cb Cx Cy
2856        [TYPE_VT200] = "\x1b[M",
2857        // xterm 1006 extended mode or urxvt 1015 extended mode
2858        // xterm: \x1b [ < Cb ; Cx ; Cy (M or m)
2859        [TYPE_1006] = "\x1b[<",
2860        // urxvt: \x1b [ Cb ; Cx ; Cy M
2861        [TYPE_1015] = "\x1b["};
2862
2863    enum type type = 0;
2864    int ret = TB_ERR;
2865
2866    // Unrolled at compile-time (probably)
2867    for (; type < TYPE_MAX; type++) {
2868        size_t size = strlen(cmp[type]);
2869
2870        if (in->len >= size && (strncmp(cmp[type], in->buf, size)) == 0) {
2871            break;
2872        }
2873    }
2874
2875    if (type == TYPE_MAX) {
2876        ret = TB_ERR; // No match
2877        return ret;
2878    }
2879
2880    size_t buf_shift = 0;
2881
2882    switch (type) {
2883        case TYPE_VT200:
2884            if (in->len >= 6) {
2885                int b = in->buf[3] - 0x20;
2886                int fail = 0;
2887
2888                switch (b & 3) {
2889                    case 0:
2890                        event->key = ((b & 64) != 0) ? TB_KEY_MOUSE_WHEEL_UP
2891                                                     : TB_KEY_MOUSE_LEFT;
2892                        break;
2893                    case 1:
2894                        event->key = ((b & 64) != 0) ? TB_KEY_MOUSE_WHEEL_DOWN
2895                                                     : TB_KEY_MOUSE_MIDDLE;
2896                        break;
2897                    case 2:
2898                        event->key = TB_KEY_MOUSE_RIGHT;
2899                        break;
2900                    case 3:
2901                        event->key = TB_KEY_MOUSE_RELEASE;
2902                        break;
2903                    default:
2904                        ret = TB_ERR;
2905                        fail = 1;
2906                        break;
2907                }
2908
2909                if (!fail) {
2910                    if ((b & 32) != 0) {
2911                        event->mod |= TB_MOD_MOTION;
2912                    }
2913
2914                    // the coord is 1,1 for upper left
2915                    event->x = ((uint8_t)in->buf[4]) - 0x21;
2916                    event->y = ((uint8_t)in->buf[5]) - 0x21;
2917
2918                    ret = TB_OK;
2919                }
2920
2921                buf_shift = 6;
2922            }
2923            break;
2924        case TYPE_1006:
2925            // fallthrough
2926        case TYPE_1015: {
2927            size_t index_fail = (size_t)-1;
2928
2929            enum {
2930                FIRST_M = 0,
2931                FIRST_SEMICOLON,
2932                LAST_SEMICOLON,
2933                FIRST_LAST_MAX
2934            };
2935
2936            size_t indices[FIRST_LAST_MAX] = {index_fail, index_fail,
2937                index_fail};
2938            int m_is_capital = 0;
2939
2940            for (size_t i = 0; i < in->len; i++) {
2941                if (in->buf[i] == ';') {
2942                    if (indices[FIRST_SEMICOLON] == index_fail) {
2943                        indices[FIRST_SEMICOLON] = i;
2944                    } else {
2945                        indices[LAST_SEMICOLON] = i;
2946                    }
2947                } else if (indices[FIRST_M] == index_fail) {
2948                    if (in->buf[i] == 'm' || in->buf[i] == 'M') {
2949                        m_is_capital = (in->buf[i] == 'M');
2950                        indices[FIRST_M] = i;
2951                    }
2952                }
2953            }
2954
2955            if (indices[FIRST_M] == index_fail ||
2956                indices[FIRST_SEMICOLON] == index_fail ||
2957                indices[LAST_SEMICOLON] == index_fail)
2958            {
2959                ret = TB_ERR;
2960            } else {
2961                int start = (type == TYPE_1015 ? 2 : 3);
2962
2963                unsigned n1 = strtoul(&in->buf[start], NULL, 10);
2964                unsigned n2 =
2965                    strtoul(&in->buf[indices[FIRST_SEMICOLON] + 1], NULL, 10);
2966                unsigned n3 =
2967                    strtoul(&in->buf[indices[LAST_SEMICOLON] + 1], NULL, 10);
2968
2969                if (type == TYPE_1015) {
2970                    n1 -= 0x20;
2971                }
2972
2973                int fail = 0;
2974
2975                switch (n1 & 3) {
2976                    case 0:
2977                        event->key = ((n1 & 64) != 0) ? TB_KEY_MOUSE_WHEEL_UP
2978                                                      : TB_KEY_MOUSE_LEFT;
2979                        break;
2980                    case 1:
2981                        event->key = ((n1 & 64) != 0) ? TB_KEY_MOUSE_WHEEL_DOWN
2982                                                      : TB_KEY_MOUSE_MIDDLE;
2983                        break;
2984                    case 2:
2985                        event->key = TB_KEY_MOUSE_RIGHT;
2986                        break;
2987                    case 3:
2988                        event->key = TB_KEY_MOUSE_RELEASE;
2989                        break;
2990                    default:
2991                        ret = TB_ERR;
2992                        fail = 1;
2993                        break;
2994                }
2995
2996                buf_shift = in->len;
2997
2998                if (!fail) {
2999                    if (!m_is_capital) {
3000                        // on xterm mouse release is signaled by lowercase m
3001                        event->key = TB_KEY_MOUSE_RELEASE;
3002                    }
3003
3004                    if ((n1 & 32) != 0) {
3005                        event->mod |= TB_MOD_MOTION;
3006                    }
3007
3008                    event->x = ((uint8_t)n2) - 1;
3009                    event->y = ((uint8_t)n3) - 1;
3010
3011                    ret = TB_OK;
3012                }
3013            }
3014        } break;
3015        case TYPE_MAX:
3016            ret = TB_ERR;
3017    }
3018
3019    if (buf_shift > 0) {
3020        bytebuf_shift(in, buf_shift);
3021    }
3022
3023    if (ret == TB_OK) {
3024        event->type = TB_EVENT_MOUSE;
3025    }
3026
3027    return ret;
3028}
3029
3030static int resize_cellbufs(void) {
3031    int rv;
3032    if_err_return(rv,
3033        cellbuf_resize(&global.back, global.width, global.height));
3034    if_err_return(rv,
3035        cellbuf_resize(&global.front, global.width, global.height));
3036    if_err_return(rv, cellbuf_clear(&global.front));
3037    if_err_return(rv, send_clear());
3038    return TB_OK;
3039}
3040
3041static void handle_resize(int sig) {
3042    int errno_copy = errno;
3043    write(global.resize_pipefd[1], &sig, sizeof(sig));
3044    errno = errno_copy;
3045}
3046
3047static int send_attr(uintattr_t fg, uintattr_t bg) {
3048    int rv;
3049
3050    if (fg == global.last_fg && bg == global.last_bg) {
3051        return TB_OK;
3052    }
3053
3054    if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0]));
3055
3056    uint32_t cfg, cbg;
3057    switch (global.output_mode) {
3058        default:
3059        case TB_OUTPUT_NORMAL:
3060            // The minus 1 below is because our colors are 1-indexed starting
3061            // from black. Black is represented by a 30, 40, 90, or 100 for fg,
3062            // bg, bright fg, or bright bg respectively. Red is 31, 41, 91,
3063            // 101, etc.
3064            cfg = (fg & TB_BRIGHT ? 90 : 30) + (fg & 0x0f) - 1;
3065            cbg = (bg & TB_BRIGHT ? 100 : 40) + (bg & 0x0f) - 1;
3066            break;
3067
3068        case TB_OUTPUT_256:
3069            cfg = fg & 0xff;
3070            cbg = bg & 0xff;
3071            if (fg & TB_HI_BLACK) cfg = 0;
3072            if (bg & TB_HI_BLACK) cbg = 0;
3073            break;
3074
3075        case TB_OUTPUT_216:
3076            cfg = fg & 0xff;
3077            cbg = bg & 0xff;
3078            if (cfg > 216) cfg = 216;
3079            if (cbg > 216) cbg = 216;
3080            cfg += 0x0f;
3081            cbg += 0x0f;
3082            break;
3083
3084        case TB_OUTPUT_GRAYSCALE:
3085            cfg = fg & 0xff;
3086            cbg = bg & 0xff;
3087            if (cfg > 24) cfg = 24;
3088            if (cbg > 24) cbg = 24;
3089            cfg += 0xe7;
3090            cbg += 0xe7;
3091            break;
3092
3093#if TB_OPT_ATTR_W >= 32
3094        case TB_OUTPUT_TRUECOLOR:
3095            cfg = fg & 0xffffff;
3096            cbg = bg & 0xffffff;
3097            if (fg & TB_HI_BLACK) cfg = 0;
3098            if (bg & TB_HI_BLACK) cbg = 0;
3099            break;
3100#endif
3101    }
3102
3103    if (fg & TB_BOLD)
3104        if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BOLD]));
3105
3106    if (fg & TB_BLINK)
3107        if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BLINK]));
3108
3109    if (fg & TB_UNDERLINE)
3110        if_err_return(rv,
3111            bytebuf_puts(&global.out, global.caps[TB_CAP_UNDERLINE]));
3112
3113    if (fg & TB_ITALIC)
3114        if_err_return(rv,
3115            bytebuf_puts(&global.out, global.caps[TB_CAP_ITALIC]));
3116
3117    if (fg & TB_DIM)
3118        if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_DIM]));
3119
3120#if TB_OPT_ATTR_W == 64
3121    if (fg & TB_STRIKEOUT)
3122        if_err_return(rv, bytebuf_puts(&global.out, TB_HARDCAP_STRIKEOUT));
3123
3124    if (fg & TB_UNDERLINE_2)
3125        if_err_return(rv, bytebuf_puts(&global.out, TB_HARDCAP_UNDERLINE_2));
3126
3127    if (fg & TB_OVERLINE)
3128        if_err_return(rv, bytebuf_puts(&global.out, TB_HARDCAP_OVERLINE));
3129
3130    if (fg & TB_INVISIBLE)
3131        if_err_return(rv,
3132            bytebuf_puts(&global.out, global.caps[TB_CAP_INVISIBLE]));
3133#endif
3134
3135    if ((fg & TB_REVERSE) || (bg & TB_REVERSE))
3136        if_err_return(rv,
3137            bytebuf_puts(&global.out, global.caps[TB_CAP_REVERSE]));
3138
3139    int fg_is_default = (fg & 0xff) == 0;
3140    int bg_is_default = (bg & 0xff) == 0;
3141    if (global.output_mode == TB_OUTPUT_256) {
3142        if (fg & TB_HI_BLACK) fg_is_default = 0;
3143        if (bg & TB_HI_BLACK) bg_is_default = 0;
3144    }
3145#if TB_OPT_ATTR_W >= 32
3146    if (global.output_mode == TB_OUTPUT_TRUECOLOR) {
3147        fg_is_default = ((fg & 0xffffff) == 0) && ((fg & TB_HI_BLACK) == 0);
3148        bg_is_default = ((bg & 0xffffff) == 0) && ((bg & TB_HI_BLACK) == 0);
3149    }
3150#endif
3151
3152    if_err_return(rv, send_sgr(cfg, cbg, fg_is_default, bg_is_default));
3153
3154    global.last_fg = fg;
3155    global.last_bg = bg;
3156
3157    return TB_OK;
3158}
3159
3160static int send_sgr(uint32_t cfg, uint32_t cbg, int fg_is_default,
3161    int bg_is_default) {
3162    int rv;
3163    char nbuf[32];
3164
3165    if (fg_is_default && bg_is_default) {
3166        return TB_OK;
3167    }
3168
3169    switch (global.output_mode) {
3170        default:
3171        case TB_OUTPUT_NORMAL:
3172            send_literal(rv, "\x1b[");
3173            if (!fg_is_default) {
3174                send_num(rv, nbuf, cfg);
3175                if (!bg_is_default) {
3176                    send_literal(rv, ";");
3177                }
3178            }
3179            if (!bg_is_default) {
3180                send_num(rv, nbuf, cbg);
3181            }
3182            send_literal(rv, "m");
3183            break;
3184
3185        case TB_OUTPUT_256:
3186        case TB_OUTPUT_216:
3187        case TB_OUTPUT_GRAYSCALE:
3188            send_literal(rv, "\x1b[");
3189            if (!fg_is_default) {
3190                send_literal(rv, "38;5;");
3191                send_num(rv, nbuf, cfg);
3192                if (!bg_is_default) {
3193                    send_literal(rv, ";");
3194                }
3195            }
3196            if (!bg_is_default) {
3197                send_literal(rv, "48;5;");
3198                send_num(rv, nbuf, cbg);
3199            }
3200            send_literal(rv, "m");
3201            break;
3202
3203#if TB_OPT_ATTR_W >= 32
3204        case TB_OUTPUT_TRUECOLOR:
3205            send_literal(rv, "\x1b[");
3206            if (!fg_is_default) {
3207                send_literal(rv, "38;2;");
3208                send_num(rv, nbuf, (cfg >> 16) & 0xff);
3209                send_literal(rv, ";");
3210                send_num(rv, nbuf, (cfg >> 8) & 0xff);
3211                send_literal(rv, ";");
3212                send_num(rv, nbuf, cfg & 0xff);
3213                if (!bg_is_default) {
3214                    send_literal(rv, ";");
3215                }
3216            }
3217            if (!bg_is_default) {
3218                send_literal(rv, "48;2;");
3219                send_num(rv, nbuf, (cbg >> 16) & 0xff);
3220                send_literal(rv, ";");
3221                send_num(rv, nbuf, (cbg >> 8) & 0xff);
3222                send_literal(rv, ";");
3223                send_num(rv, nbuf, cbg & 0xff);
3224            }
3225            send_literal(rv, "m");
3226            break;
3227#endif
3228    }
3229    return TB_OK;
3230}
3231
3232static int send_cursor_if(int x, int y) {
3233    int rv;
3234    char nbuf[32];
3235    if (x < 0 || y < 0) {
3236        return TB_OK;
3237    }
3238    send_literal(rv, "\x1b[");
3239    send_num(rv, nbuf, y + 1);
3240    send_literal(rv, ";");
3241    send_num(rv, nbuf, x + 1);
3242    send_literal(rv, "H");
3243    return TB_OK;
3244}
3245
3246static int send_char(int x, int y, uint32_t ch) {
3247    return send_cluster(x, y, &ch, 1);
3248}
3249
3250static int send_cluster(int x, int y, uint32_t *ch, size_t nch) {
3251    int rv;
3252    char chu8[8];
3253
3254    if (global.last_x != x - 1 || global.last_y != y) {
3255        if_err_return(rv, send_cursor_if(x, y));
3256    }
3257    global.last_x = x;
3258    global.last_y = y;
3259
3260    int i;
3261    for (i = 0; i < (int)nch; i++) {
3262        uint32_t ch32 = *(ch + i);
3263        if (!iswprint((wint_t)ch32)) {
3264            ch32 = 0xfffd; // replace non-printable codepoints with U+FFFD
3265        }
3266        int chu8_len = tb_utf8_unicode_to_char(chu8, ch32);
3267        if_err_return(rv, bytebuf_nputs(&global.out, chu8, (size_t)chu8_len));
3268    }
3269
3270    return TB_OK;
3271}
3272
3273static int convert_num(uint32_t num, char *buf) {
3274    int i, l = 0;
3275    char ch;
3276    do {
3277        buf[l++] = (char)('0' + (num % 10));
3278        num /= 10;
3279    } while (num);
3280    for (i = 0; i < l / 2; i++) {
3281        ch = buf[i];
3282        buf[i] = buf[l - 1 - i];
3283        buf[l - 1 - i] = ch;
3284    }
3285    return l;
3286}
3287
3288static int cell_cmp(struct tb_cell *a, struct tb_cell *b) {
3289    if (a->ch != b->ch || a->fg != b->fg || a->bg != b->bg) {
3290        return 1;
3291    }
3292#ifdef TB_OPT_EGC
3293    if (a->nech != b->nech) {
3294        return 1;
3295    } else if (a->nech > 0) { // a->nech == b->nech
3296        return memcmp(a->ech, b->ech, a->nech);
3297    }
3298#endif
3299    return 0;
3300}
3301
3302static int cell_copy(struct tb_cell *dst, struct tb_cell *src) {
3303#ifdef TB_OPT_EGC
3304    if (src->nech > 0) {
3305        return cell_set(dst, src->ech, src->nech, src->fg, src->bg);
3306    }
3307#endif
3308    return cell_set(dst, &src->ch, 1, src->fg, src->bg);
3309}
3310
3311static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch,
3312    uintattr_t fg, uintattr_t bg) {
3313    cell->ch = ch ? *ch : 0;
3314    cell->fg = fg;
3315    cell->bg = bg;
3316#ifdef TB_OPT_EGC
3317    if (nch <= 1) {
3318        cell->nech = 0;
3319    } else {
3320        int rv;
3321        if_err_return(rv, cell_reserve_ech(cell, nch + 1));
3322        memcpy(cell->ech, ch, sizeof(ch) * nch);
3323        cell->ech[nch] = '\0';
3324        cell->nech = nch;
3325    }
3326#else
3327    (void)nch;
3328    (void)cell_reserve_ech;
3329#endif
3330    return TB_OK;
3331}
3332
3333static int cell_reserve_ech(struct tb_cell *cell, size_t n) {
3334#ifdef TB_OPT_EGC
3335    if (cell->cech >= n) {
3336        return TB_OK;
3337    }
3338    if (!(cell->ech = tb_realloc(cell->ech, n * sizeof(cell->ch)))) {
3339        return TB_ERR_MEM;
3340    }
3341    cell->cech = n;
3342    return TB_OK;
3343#else
3344    (void)cell;
3345    (void)n;
3346    return TB_ERR;
3347#endif
3348}
3349
3350static int cell_free(struct tb_cell *cell) {
3351#ifdef TB_OPT_EGC
3352    if (cell->ech) {
3353        tb_free(cell->ech);
3354    }
3355#endif
3356    memset(cell, 0, sizeof(*cell));
3357    return TB_OK;
3358}
3359
3360static int cellbuf_init(struct cellbuf_t *c, int w, int h) {
3361    c->cells = tb_malloc(sizeof(struct tb_cell) * w * h);
3362    if (!c->cells) {
3363        return TB_ERR_MEM;
3364    }
3365    memset(c->cells, 0, sizeof(struct tb_cell) * w * h);
3366    c->width = w;
3367    c->height = h;
3368    return TB_OK;
3369}
3370
3371static int cellbuf_free(struct cellbuf_t *c) {
3372    if (c->cells) {
3373        int i;
3374        for (i = 0; i < c->width * c->height; i++) {
3375            cell_free(&c->cells[i]);
3376        }
3377        tb_free(c->cells);
3378    }
3379    memset(c, 0, sizeof(*c));
3380    return TB_OK;
3381}
3382
3383static int cellbuf_clear(struct cellbuf_t *c) {
3384    int rv, i;
3385    uint32_t space = (uint32_t)' ';
3386    for (i = 0; i < c->width * c->height; i++) {
3387        if_err_return(rv,
3388            cell_set(&c->cells[i], &space, 1, global.fg, global.bg));
3389    }
3390    return TB_OK;
3391}
3392
3393static int cellbuf_get(struct cellbuf_t *c, int x, int y,
3394    struct tb_cell **out) {
3395    if (!cellbuf_in_bounds(c, x, y)) {
3396        *out = NULL;
3397        return TB_ERR_OUT_OF_BOUNDS;
3398    }
3399    *out = &c->cells[(y * c->width) + x];
3400    return TB_OK;
3401}
3402
3403static int cellbuf_in_bounds(struct cellbuf_t *c, int x, int y) {
3404    if (x < 0 || x >= c->width || y < 0 || y >= c->height) {
3405        return 0;
3406    }
3407    return 1;
3408}
3409
3410static int cellbuf_resize(struct cellbuf_t *c, int w, int h) {
3411    int rv;
3412
3413    int ow = c->width;
3414    int oh = c->height;
3415
3416    if (ow == w && oh == h) {
3417        return TB_OK;
3418    }
3419
3420    w = w < 1 ? 1 : w;
3421    h = h < 1 ? 1 : h;
3422
3423    int minw = (w < ow) ? w : ow;
3424    int minh = (h < oh) ? h : oh;
3425
3426    struct tb_cell *prev = c->cells;
3427
3428    if_err_return(rv, cellbuf_init(c, w, h));
3429    if_err_return(rv, cellbuf_clear(c));
3430
3431    int x, y;
3432    for (x = 0; x < minw; x++) {
3433        for (y = 0; y < minh; y++) {
3434            struct tb_cell *src, *dst;
3435            src = &prev[(y * ow) + x];
3436            if_err_return(rv, cellbuf_get(c, x, y, &dst));
3437            if_err_return(rv, cell_copy(dst, src));
3438        }
3439    }
3440
3441    tb_free(prev);
3442
3443    return TB_OK;
3444}
3445
3446static int bytebuf_puts(struct bytebuf_t *b, const char *str) {
3447    if (!str || strlen(str) <= 0) return TB_OK; // Nothing to do for empty caps
3448    return bytebuf_nputs(b, str, (size_t)strlen(str));
3449}
3450
3451static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr) {
3452    int rv;
3453    if_err_return(rv, bytebuf_reserve(b, b->len + nstr + 1));
3454    memcpy(b->buf + b->len, str, nstr);
3455    b->len += nstr;
3456    b->buf[b->len] = '\0';
3457    return TB_OK;
3458}
3459
3460static int bytebuf_shift(struct bytebuf_t *b, size_t n) {
3461    if (n > b->len) {
3462        n = b->len;
3463    }
3464    size_t nmove = b->len - n;
3465    memmove(b->buf, b->buf + n, nmove);
3466    b->len -= n;
3467    return TB_OK;
3468}
3469
3470static int bytebuf_flush(struct bytebuf_t *b, int fd) {
3471    if (b->len <= 0) {
3472        return TB_OK;
3473    }
3474    ssize_t write_rv = write(fd, b->buf, b->len);
3475    if (write_rv < 0 || (size_t)write_rv != b->len) {
3476        // Note, errno will be 0 on partial write
3477        global.last_errno = errno;
3478        return TB_ERR;
3479    }
3480    b->len = 0;
3481    return TB_OK;
3482}
3483
3484static int bytebuf_reserve(struct bytebuf_t *b, size_t sz) {
3485    if (b->cap >= sz) {
3486        return TB_OK;
3487    }
3488    size_t newcap = b->cap > 0 ? b->cap : 1;
3489    while (newcap < sz) {
3490        newcap *= 2;
3491    }
3492    char *newbuf;
3493    if (b->buf) {
3494        newbuf = tb_realloc(b->buf, newcap);
3495    } else {
3496        newbuf = tb_malloc(newcap);
3497    }
3498    if (!newbuf) {
3499        return TB_ERR_MEM;
3500    }
3501    b->buf = newbuf;
3502    b->cap = newcap;
3503    return TB_OK;
3504}
3505
3506static int bytebuf_free(struct bytebuf_t *b) {
3507    if (b->buf) {
3508        tb_free(b->buf);
3509    }
3510    memset(b, 0, sizeof(*b));
3511    return TB_OK;
3512}
3513
3514#endif // TB_IMPL