readr

Minimal RSS reader (WIP)
Log | Files | Refs | README | LICENSE

termbox2.h (106208B)


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