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