From 43da2edb0fcfe879311845d6460ed2f5be698d60 Mon Sep 17 00:00:00 2001 From: j4nk Date: Fri, 6 Jan 2023 19:12:24 -0500 Subject: [PATCH] First commit, old repository --- .gitignore | 6 + Makefile | 10 + ball.txt | 1 + card.txt | 4 + card_deck_demo.lua | 6 + demo2.lua | 27 + main.c | 62 + oct_termbox_sprite.h | 159 +++ oct_utils.lua | 8 + paddle.txt | 5 + pong.lua | 151 ++ termbox.h | 3214 ++++++++++++++++++++++++++++++++++++++++++ termbox_defs.lua | 93 ++ termbox_render.h | 26 + test | Bin 0 -> 98888 bytes test.c | 27 + 16 files changed, 3799 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 ball.txt create mode 100644 card.txt create mode 100644 card_deck_demo.lua create mode 100644 demo2.lua create mode 100644 main.c create mode 100644 oct_termbox_sprite.h create mode 100644 oct_utils.lua create mode 100644 paddle.txt create mode 100644 pong.lua create mode 100644 termbox.h create mode 100644 termbox_defs.lua create mode 100644 termbox_render.h create mode 100755 test create mode 100644 test.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ceda33 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +#* +*~ +main +*.o +vgcore* +*.core \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cc7d5ed --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +CC=cc +CLIB=-L/usr/local/lib -llua5.3 -lncurses -ltermbox +INC=-I/usr/local/include +BIN=main +DEBUG=-g + +all: + $(CC) $(INC) $(CLIB) main.c -o $(BIN) $(DEBUG) +test: + $(CC) $(INC) $(CLIB) -D_BSD_SOURCE test.c -o test $(DEBUG) diff --git a/ball.txt b/ball.txt new file mode 100644 index 0000000..2638c45 --- /dev/null +++ b/ball.txt @@ -0,0 +1 @@ +O diff --git a/card.txt b/card.txt new file mode 100644 index 0000000..5fa5139 --- /dev/null +++ b/card.txt @@ -0,0 +1,4 @@ +|--| +| | +| | +|--| diff --git a/card_deck_demo.lua b/card_deck_demo.lua new file mode 100644 index 0000000..2659c11 --- /dev/null +++ b/card_deck_demo.lua @@ -0,0 +1,6 @@ +require("oct_utils") + +function oct_init() + local sprite0 = load_termbox_sprite("card.txt"); + oct_set_termbox_sprite(0, 10, 10, 1, 8, sprite0); +end diff --git a/demo2.lua b/demo2.lua new file mode 100644 index 0000000..530d29a --- /dev/null +++ b/demo2.lua @@ -0,0 +1,27 @@ +require("oct_utils") +require("termbox_defs") + +card1 = oct_tb_sprite_new(); +card2 = oct_tb_sprite_new(); +dx = 1; + +function oct_init() + card1["shape"] = load_termbox_sprite("card.txt"); + card2["shape"] = load_termbox_sprite("card.txt"); + card1["x"] = 50; + card1["y"] = 0; + card2["x"] = 50; + card2["y"] = 50; +end + +function oct_loop(key) + if (key == TB_KEY_ARROW_RIGHT) + then + card1["x"] = card1["x"] + 1; + end + + if (key == TB_KEY_ARROW_LEFT) + then + card1["x"] = card1["x"] - 1; + end +end diff --git a/main.c b/main.c new file mode 100644 index 0000000..e36b1dd --- /dev/null +++ b/main.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include "termbox_render.h" +#include "oct_termbox_sprite.h" + +#define TB_IMPL + +lua_State *L; + +int initialize_everything(); +int deinitialize_everything(); +int main(int argc, char* argv[]) { + if (!initialize_everything()) { + fprintf(stderr, "Couldn't initialize Open Card Table"); + } + struct tb_event ev; + int finish = 0; + while (!finish) { + tb_clear(); + tb_peek_event(&ev, 10); + if (ev.key == TB_KEY_ESC) { + finish = 1; + } + lua_getglobal(L, "oct_loop"); + lua_pushinteger(L, ev.key); + lua_pushinteger(L, ev.ch); + lua_call(L, 2, 0); + + for (uint32_t i=0; i < oct_tb_sprite_list.new_index; i++) { + oct_render_termbox_sprite(oct_tb_sprite_list.sprite_list[i]); + } + tb_present(); + } + deinitialize_everything(); + return EXIT_SUCCESS; +} + +int initialize_everything() { + if (!oct_tb_sprite_list_initialize()) { + return 0; + } + L = luaL_newstate(); + if (L == NULL) { + fprintf(stderr, "Can't initialize Lua\n"); + return 0; + } + luaL_openlibs(L); + oct_tb_initialize_lua(L); + luaL_dofile(L, "pong.lua"); + lua_getglobal(L, "oct_init"); + lua_call(L, 0, 0); + tb_init(); + return 1; +} + +int deinitialize_everything() { + tb_shutdown(); + lua_close(L); + oct_tb_sprite_list_deinitialize(); + return 1; +} diff --git a/oct_termbox_sprite.h b/oct_termbox_sprite.h new file mode 100644 index 0000000..90ac329 --- /dev/null +++ b/oct_termbox_sprite.h @@ -0,0 +1,159 @@ +#ifndef OCT_TERMBOX_SPRITE_H +#define OCT_TERMBOX_SPRITE_H + +#include +#include +#include +#include +#include +#include "termbox.h" + +#define OCT_MAX_SPRITE_SIZE 101 +#define OCT_INITIAL_NUM_SPRITES 100 +#define OCT_SPRITE_LIST_REALLOC_LENGTH 100 +#define OCT_SPRITE_MEMBER_NAME_MAXLENGTH 10 +#define OCT_SPRITE_PRINT_BUFFER_SIZE 1024 +#define OCT_OUT_OF_SCREEN 1000 +struct oct_tb_sprite { + int x; + int y; + uintattr_t fg; + uintattr_t bg; + char shape[OCT_MAX_SPRITE_SIZE]; +}; + +struct { + // Array of pointers to oct_tb_sprites + struct oct_tb_sprite** sprite_list; + uint32_t new_index; + uint32_t size; + uint8_t initialized; +} oct_tb_sprite_list; + +int oct_tb_sprite_list_initialize() { + oct_tb_sprite_list.sprite_list = calloc(OCT_INITIAL_NUM_SPRITES, sizeof(struct oct_tb_sprite*)); + if (!oct_tb_sprite_list.sprite_list) { + perror("Could not allocate memory for sprite list"); + return 0; + } + oct_tb_sprite_list.new_index = 0; + oct_tb_sprite_list.size = OCT_INITIAL_NUM_SPRITES; + oct_tb_sprite_list.initialized = 1; + return 1; +} + +int oct_tb_sprite_list_deinitialize() { + free(oct_tb_sprite_list.sprite_list); + oct_tb_sprite_list.sprite_list = NULL; + oct_tb_sprite_list.new_index = 0; + oct_tb_sprite_list.size = 0; + oct_tb_sprite_list.initialized = 0; + return 1; +} + +// No functionality to remove sprites (just draw offscreen) + +int oct_tb_sprite__index(lua_State *L) { + char index[OCT_SPRITE_MEMBER_NAME_MAXLENGTH]; + struct oct_tb_sprite* self = (struct oct_tb_sprite*)lua_touserdata(L, -2); + strncpy(index, luaL_checkstring(L, -1), OCT_SPRITE_MEMBER_NAME_MAXLENGTH); + if (!strncmp(index, "x", OCT_SPRITE_MEMBER_NAME_MAXLENGTH)) { + lua_pushinteger(L, self->x); + } + else if (!strncmp(index, "y", OCT_SPRITE_MEMBER_NAME_MAXLENGTH)) { + lua_pushinteger(L, self->y); + } + else if (!strncmp(index, "fg", OCT_SPRITE_MEMBER_NAME_MAXLENGTH)) { + lua_pushinteger(L, self->fg); + } + else if (!strncmp(index, "bg", OCT_SPRITE_MEMBER_NAME_MAXLENGTH)) { + lua_pushinteger(L, self->bg); + } + else if (!strncmp(index, "shape", OCT_SPRITE_MEMBER_NAME_MAXLENGTH)) { + lua_pushstring(L, self->shape); + } + else { + lua_pushnil(L); + } + return 1; +} + +int oct_tb_sprite__newindex(lua_State *L) { + char index[OCT_SPRITE_MEMBER_NAME_MAXLENGTH]; + struct oct_tb_sprite* self = (struct oct_tb_sprite*)lua_touserdata(L, -3); + strncpy(index, luaL_checkstring(L, -2), OCT_SPRITE_MEMBER_NAME_MAXLENGTH); + + if (!strncmp(index, "x", OCT_SPRITE_MEMBER_NAME_MAXLENGTH)) { + self->x = luaL_checkinteger(L, -1); + } + else if (!strncmp(index, "y", OCT_SPRITE_MEMBER_NAME_MAXLENGTH)) { + self->y = luaL_checkinteger(L, -1); + } + else if (!strncmp(index, "fg", OCT_SPRITE_MEMBER_NAME_MAXLENGTH)) { + self->fg = (uintattr_t)luaL_checkinteger(L, -1); + } + else if (!strncmp(index, "bg", OCT_SPRITE_MEMBER_NAME_MAXLENGTH)) { + self->bg = (uintattr_t)luaL_checkinteger(L, -1); + } + else if (!strncmp(index, "shape", OCT_SPRITE_MEMBER_NAME_MAXLENGTH)) { + strncpy(self->shape, luaL_checkstring(L, -1), OCT_MAX_SPRITE_SIZE); + } + return 1; +} + +int oct_tb_sprite__tostring(lua_State *L) { + struct oct_tb_sprite* self = (struct oct_tb_sprite*)lua_touserdata(L, -1); + char buffer[OCT_SPRITE_PRINT_BUFFER_SIZE]; + snprintf(buffer, + OCT_SPRITE_PRINT_BUFFER_SIZE, + "{\n" + "\tx: %d\n" + "\ty: %d\n" + "\tfg: %d\n" + "\tbg: %d\n" + "\tshape: %s\n" + "}", + self->x, self->y, self->bg, self->fg, self->shape); + lua_pushstring(L, buffer); + return 1; +} + +struct luaL_Reg oct_tb_sprite_metamethods[] = + { + {"__index", oct_tb_sprite__index}, + {"__newindex", oct_tb_sprite__newindex}, + {"__tostring", oct_tb_sprite__tostring}, + {NULL, NULL} + }; + +int oct_tb_sprite_new(lua_State *L) { + if (oct_tb_sprite_list.size == oct_tb_sprite_list.new_index) { + if (!reallocarray(oct_tb_sprite_list.sprite_list, + sizeof(struct oct_tb_sprite*), + oct_tb_sprite_list.size+OCT_SPRITE_LIST_REALLOC_LENGTH)) { + perror("Could not reallocate memory for sprite list"); + } + } + struct oct_tb_sprite* new_sprite = (struct oct_tb_sprite*)lua_newuserdata(L, sizeof(struct oct_tb_sprite)); + new_sprite->x = OCT_OUT_OF_SCREEN; + new_sprite->y = OCT_OUT_OF_SCREEN; + new_sprite->fg = 0; + new_sprite->bg = 1; + strncpy(new_sprite->shape, "", OCT_MAX_SPRITE_SIZE); + oct_tb_sprite_list.sprite_list[oct_tb_sprite_list.new_index] = new_sprite; + lua_pushstring(L, "oct_tb_sprite_metatable"); + lua_gettable(L, LUA_REGISTRYINDEX); + lua_setmetatable(L, -2); + oct_tb_sprite_list.new_index++; + return 1; +} + +void oct_tb_initialize_lua(lua_State *L) { + lua_pushcfunction(L, oct_tb_sprite_new); + lua_setglobal(L, "oct_tb_sprite_new"); + if (luaL_newmetatable(L, "oct_tb_sprite_metatable")) { + luaL_setfuncs(L, oct_tb_sprite_metamethods, 0); + } +} + +#endif diff --git a/oct_utils.lua b/oct_utils.lua new file mode 100644 index 0000000..61921ee --- /dev/null +++ b/oct_utils.lua @@ -0,0 +1,8 @@ +-- Load a sprite stored in a .txt file +function load_termbox_sprite(file_name) + file = io.open(file_name, "r"); + io.input(file); + local contents = io.read("*a"); + io.close(file); + return contents; +end diff --git a/paddle.txt b/paddle.txt new file mode 100644 index 0000000..a2e0b05 --- /dev/null +++ b/paddle.txt @@ -0,0 +1,5 @@ +|| +|| +|| +|| +|| diff --git a/pong.lua b/pong.lua new file mode 100644 index 0000000..93af6e2 --- /dev/null +++ b/pong.lua @@ -0,0 +1,151 @@ +require("oct_utils") +require("termbox_defs") + +math.randomseed(os.time()) + +width = 74; +height = 25; + +paddle_left = oct_tb_sprite_new(); +paddle_right = oct_tb_sprite_new(); +ball = oct_tb_sprite_new(); +top_border = oct_tb_sprite_new(); +bottom_border = oct_tb_sprite_new(); +left_border = oct_tb_sprite_new(); +right_border = oct_tb_sprite_new(); +score_p1 = oct_tb_sprite_new(); +score_p2 = oct_tb_sprite_new(); +init_text = oct_tb_sprite_new(); + +border_topbottom = ""; + +dx = 0; +dy = 0; +delay = 10; +counter = 0; +for i=1,width,1 +do + border_topbottom = border_topbottom .. "-" +end +border_leftright = ""; +for i=1,height-1,1 +do + border_leftright = border_leftright .. "|\n"; +end +-- Get rid of the last \n +border_leftright = border_leftright:sub(1,-2); + +pause_game = 1; + +function pause() + pause_game = 1; + init_text["x"] = math.floor(width/2) - math.floor(string.len(init_text["shape"])/2); + init_text["y"] = math.floor(height/2)+2; + ball["x"] = math.floor(width/2); + ball["y"] = math.floor(height/2); +end + +function unpause() + pause_game = 0; -- unpause game + init_text["x"] = 1000; -- draw offscreen + init_text["y"] = 1000; -- draw offscreen +end + +function oct_init() + paddle_left["shape"] = load_termbox_sprite("paddle.txt"); + paddle_right["shape"] = load_termbox_sprite("paddle.txt"); + ball["shape"] = load_termbox_sprite("ball.txt"); + paddle_left["x"] = 1; + paddle_left["y"] = math.floor(height/2)-2; + paddle_left["fg"] = TB_RED; + paddle_right["x"] = width-3; + paddle_right["y"] = math.floor(height/2)-2; + paddle_right["fg"] = TB_GREEN; + top_border["shape"] = border_topbottom; + bottom_border["shape"] = border_topbottom; + top_border["x"] = 0; + bottom_border["x"] = 0; + top_border["y"] = 0; + bottom_border["y"] = height; + left_border["shape"] = border_leftright; + left_border["x"] = 0; + left_border["y"] = 1; + right_border["shape"] = border_leftright; + right_border["x"] = width-1; + right_border["y"] = 1; + score_p1["x"] = 0; + score_p2["x"] = 0; + score_p1["y"] = height+1; + score_p2["y"] = height+2; + score_p1["shape"] = "Player 1: 0"; + score_p2["shape"] = "Player 2: 0"; + init_text["shape"] = "Press SPACE to begin"; + pause(); +end + +function oct_loop(key, ch) + if (pause_game == 0) + then + -- handle keys + if (key == TB_KEY_ARROW_UP and paddle_right["y"] >= 2) + then + paddle_right["y"] = paddle_right["y"] - 1; + end + if (key == TB_KEY_ARROW_DOWN and paddle_right["y"] <= height-5-1) + then + paddle_right["y"] = paddle_right["y"] + 1; + end + if (ch == string.byte("w") and paddle_left["y"] >= 2) + then + paddle_left["y"] = paddle_left["y"] - 1; + end + if (ch == string.byte("s") and paddle_left["y"] <= height-5-1) + then + paddle_left["y"] = paddle_left["y"] + 1; + end + + counter = counter + 1; + if (counter == delay) + then + counter = 0; + -- update ball + ball["x"] = ball["x"] + dx; + ball["y"] = ball["y"] + dy; + + if (ball["y"] <= 1 or ball["y"] >= height-1) + then + dy = -dy; + end + if (ball["x"] <= 3) + then + if (ball["y"] >= paddle_left["y"] and ball["y"] <= paddle_left["y"] + 5) + then + dx = -dx; + else + pause(); + end + end + if (ball["x"] >= width-4) + then + if (ball["y"] >= paddle_right["y"] and ball["y"] <= paddle_right["y"] + 5) + then + dx = -dx; + else + pause(); + end + end + end + else + if (ch == string.byte(" ")) + then + unpause(); + -- ensure the ball is always started moving in the x direction or game will stall + dx = math.random(0, 1); + if (dx == 0) + then + dx = -1 + end + dy = math.random(-1, 1); + end + end +end diff --git a/termbox.h b/termbox.h new file mode 100644 index 0000000..9dd0cdc --- /dev/null +++ b/termbox.h @@ -0,0 +1,3214 @@ +/* +MIT License + +Copyright (c) 2010-2020 nsf + 2015-2021 Adam Saponara + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef __TERMBOX_H +#define __TERMBOX_H + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE +#endif + +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ASCII key constants (tb_event.key) */ +#define TB_KEY_CTRL_TILDE 0x00 +#define TB_KEY_CTRL_2 0x00 /* clash with 'CTRL_TILDE' */ +#define TB_KEY_CTRL_A 0x01 +#define TB_KEY_CTRL_B 0x02 +#define TB_KEY_CTRL_C 0x03 +#define TB_KEY_CTRL_D 0x04 +#define TB_KEY_CTRL_E 0x05 +#define TB_KEY_CTRL_F 0x06 +#define TB_KEY_CTRL_G 0x07 +#define TB_KEY_BACKSPACE 0x08 +#define TB_KEY_CTRL_H 0x08 /* clash with 'CTRL_BACKSPACE' */ +#define TB_KEY_TAB 0x09 +#define TB_KEY_CTRL_I 0x09 /* clash with 'TAB' */ +#define TB_KEY_CTRL_J 0x0a +#define TB_KEY_CTRL_K 0x0b +#define TB_KEY_CTRL_L 0x0c +#define TB_KEY_ENTER 0x0d +#define TB_KEY_CTRL_M 0x0d /* clash with 'ENTER' */ +#define TB_KEY_CTRL_N 0x0e +#define TB_KEY_CTRL_O 0x0f +#define TB_KEY_CTRL_P 0x10 +#define TB_KEY_CTRL_Q 0x11 +#define TB_KEY_CTRL_R 0x12 +#define TB_KEY_CTRL_S 0x13 +#define TB_KEY_CTRL_T 0x14 +#define TB_KEY_CTRL_U 0x15 +#define TB_KEY_CTRL_V 0x16 +#define TB_KEY_CTRL_W 0x17 +#define TB_KEY_CTRL_X 0x18 +#define TB_KEY_CTRL_Y 0x19 +#define TB_KEY_CTRL_Z 0x1a +#define TB_KEY_ESC 0x1b +#define TB_KEY_CTRL_LSQ_BRACKET 0x1b /* clash with 'ESC' */ +#define TB_KEY_CTRL_3 0x1b /* clash with 'ESC' */ +#define TB_KEY_CTRL_4 0x1c +#define TB_KEY_CTRL_BACKSLASH 0x1c /* clash with 'CTRL_4' */ +#define TB_KEY_CTRL_5 0x1d +#define TB_KEY_CTRL_RSQ_BRACKET 0x1d /* clash with 'CTRL_5' */ +#define TB_KEY_CTRL_6 0x1e +#define TB_KEY_CTRL_7 0x1f +#define TB_KEY_CTRL_SLASH 0x1f /* clash with 'CTRL_7' */ +#define TB_KEY_CTRL_UNDERSCORE 0x1f /* clash with 'CTRL_7' */ +#define TB_KEY_SPACE 0x20 +#define TB_KEY_BACKSPACE2 0x7f +#define TB_KEY_CTRL_8 0x7f /* clash with 'BACKSPACE2' */ + +#define tb_key_i(i) 0xffff - (i) +/* Terminal-dependent key constants (tb_event.key) and terminfo capabilities */ +/* BEGIN codegen h */ +/* Produced by ./codegen.sh on Sun, 19 Sep 2021 01:02:02 +0000 */ +#define TB_KEY_F1 (0xffff - 0) +#define TB_KEY_F2 (0xffff - 1) +#define TB_KEY_F3 (0xffff - 2) +#define TB_KEY_F4 (0xffff - 3) +#define TB_KEY_F5 (0xffff - 4) +#define TB_KEY_F6 (0xffff - 5) +#define TB_KEY_F7 (0xffff - 6) +#define TB_KEY_F8 (0xffff - 7) +#define TB_KEY_F9 (0xffff - 8) +#define TB_KEY_F10 (0xffff - 9) +#define TB_KEY_F11 (0xffff - 10) +#define TB_KEY_F12 (0xffff - 11) +#define TB_KEY_INSERT (0xffff - 12) +#define TB_KEY_DELETE (0xffff - 13) +#define TB_KEY_HOME (0xffff - 14) +#define TB_KEY_END (0xffff - 15) +#define TB_KEY_PGUP (0xffff - 16) +#define TB_KEY_PGDN (0xffff - 17) +#define TB_KEY_ARROW_UP (0xffff - 18) +#define TB_KEY_ARROW_DOWN (0xffff - 19) +#define TB_KEY_ARROW_LEFT (0xffff - 20) +#define TB_KEY_ARROW_RIGHT (0xffff - 21) +#define TB_KEY_BACK_TAB (0xffff - 22) +#define TB_KEY_MOUSE_LEFT (0xffff - 23) +#define TB_KEY_MOUSE_RIGHT (0xffff - 24) +#define TB_KEY_MOUSE_MIDDLE (0xffff - 25) +#define TB_KEY_MOUSE_RELEASE (0xffff - 26) +#define TB_KEY_MOUSE_WHEEL_UP (0xffff - 27) +#define TB_KEY_MOUSE_WHEEL_DOWN (0xffff - 28) + +#define TB_CAP_F1 0 +#define TB_CAP_F2 1 +#define TB_CAP_F3 2 +#define TB_CAP_F4 3 +#define TB_CAP_F5 4 +#define TB_CAP_F6 5 +#define TB_CAP_F7 6 +#define TB_CAP_F8 7 +#define TB_CAP_F9 8 +#define TB_CAP_F10 9 +#define TB_CAP_F11 10 +#define TB_CAP_F12 11 +#define TB_CAP_INSERT 12 +#define TB_CAP_DELETE 13 +#define TB_CAP_HOME 14 +#define TB_CAP_END 15 +#define TB_CAP_PGUP 16 +#define TB_CAP_PGDN 17 +#define TB_CAP_ARROW_UP 18 +#define TB_CAP_ARROW_DOWN 19 +#define TB_CAP_ARROW_LEFT 20 +#define TB_CAP_ARROW_RIGHT 21 +#define TB_CAP_BACK_TAB 22 +#define TB_CAP__COUNT_KEYS 23 +#define TB_CAP_ENTER_CA 23 +#define TB_CAP_EXIT_CA 24 +#define TB_CAP_SHOW_CURSOR 25 +#define TB_CAP_HIDE_CURSOR 26 +#define TB_CAP_CLEAR_SCREEN 27 +#define TB_CAP_SGR0 28 +#define TB_CAP_UNDERLINE 29 +#define TB_CAP_BOLD 30 +#define TB_CAP_BLINK 31 +#define TB_CAP_ITALIC 32 +#define TB_CAP_REVERSE 33 +#define TB_CAP_ENTER_KEYPAD 34 +#define TB_CAP_EXIT_KEYPAD 35 +#define TB_CAP__COUNT 36 +/* END codegen h */ + +/* Some hard-coded caps */ +#define TB_HARDCAP_ENTER_MOUSE "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" +#define TB_HARDCAP_EXIT_MOUSE "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l" + +/* Colors (numeric) and attributes (bitwise) (tb_cell.fg, tb_cell.bg) */ +#define TB_DEFAULT 0x0000 +#define TB_BLACK 0x0001 +#define TB_RED 0x0002 +#define TB_GREEN 0x0003 +#define TB_YELLOW 0x0004 +#define TB_BLUE 0x0005 +#define TB_MAGENTA 0x0006 +#define TB_CYAN 0x0007 +#define TB_WHITE 0x0008 +#define TB_BOLD 0x0100 +#define TB_UNDERLINE 0x0200 +#define TB_REVERSE 0x0400 +#define TB_ITALIC 0x0800 +#define TB_BLINK 0x1000 +#ifdef TB_OPT_TRUECOLOR +#define TB_TRUECOLOR_BOLD 0x01000000 +#define TB_TRUECOLOR_UNDERLINE 0x02000000 +#define TB_TRUECOLOR_REVERSE 0x04000000 +#define TB_TRUECOLOR_ITALIC 0x08000000 +#define TB_TRUECOLOR_BLINK 0x10000000 +#endif + +/* Event types (tb_event.type) */ +#define TB_EVENT_KEY 1 +#define TB_EVENT_RESIZE 2 +#define TB_EVENT_MOUSE 3 + +/* Key modifiers (bitwise) (tb_event.mod) */ +#define TB_MOD_ALT 1 +#define TB_MOD_CTRL 2 +#define TB_MOD_SHIFT 4 +#define TB_MOD_MOTION 8 + +/* Input modes (bitwise) (tb_set_input_mode) */ +#define TB_INPUT_CURRENT 0 +#define TB_INPUT_ESC 1 +#define TB_INPUT_ALT 2 +#define TB_INPUT_MOUSE 4 + +/* Output modes (tb_set_output_mode) */ +#define TB_OUTPUT_CURRENT 0 +#define TB_OUTPUT_NORMAL 1 +#define TB_OUTPUT_256 2 +#define TB_OUTPUT_216 3 +#define TB_OUTPUT_GRAYSCALE 4 +#ifdef TB_OPT_TRUECOLOR +#define TB_OUTPUT_TRUECOLOR 5 +#endif + +/* Common function return values unless otherwise noted. + * + * Library behavior is undefined after receiving TB_ERR_MEM. Callers may + * attempt reinitializing by freeing memory, invoking tb_shutdown, then + * tb_init. + */ +#define TB_OK 0 +#define TB_ERR -1 +#define TB_ERR_NEED_MORE -2 +#define TB_ERR_INIT_ALREADY -3 +#define TB_ERR_INIT_OPEN -4 +#define TB_ERR_MEM -5 +#define TB_ERR_NO_EVENT -6 +#define TB_ERR_NO_TERM -7 +#define TB_ERR_NOT_INIT -8 +#define TB_ERR_OUT_OF_BOUNDS -9 +#define TB_ERR_READ -10 +#define TB_ERR_RESIZE_IOCTL -11 +#define TB_ERR_RESIZE_PIPE -12 +#define TB_ERR_RESIZE_SIGACTION -13 +#define TB_ERR_POLL -14 +#define TB_ERR_TCGETATTR -15 +#define TB_ERR_TCSETATTR -16 +#define TB_ERR_UNSUPPORTED_TERM -17 +#define TB_ERR_RESIZE_WRITE -18 +#define TB_ERR_RESIZE_POLL -19 +#define TB_ERR_RESIZE_READ -20 +#define TB_ERR_RESIZE_SSCANF -21 +#define TB_ERR_CAP_COLLISION -22 + +#define TB_ERR_SELECT TB_ERR_POLL +#define TB_ERR_RESIZE_SELECT TB_ERR_RESIZE_POLL + +/* Function types to be used with tb_set_func() */ +#define TB_FUNC_EXTRACT_PRE 0 +#define TB_FUNC_EXTRACT_POST 1 + +/* Define this to set the size of the buffer used in tb_printf() + * and tb_sendf() + */ +#ifndef TB_OPT_PRINTF_BUF +#define TB_OPT_PRINTF_BUF 4096 +#endif + +/* Define this to set the size of the read buffer used when reading + * from the tty + */ +#ifndef TB_OPT_READ_BUF +#define TB_OPT_READ_BUF 64 +#endif + +/* Define this for limited back compat with termbox v1 */ +#ifdef TB_OPT_V1_COMPAT +#define tb_change_cell tb_set_cell +#define tb_put_cell(x, y, c) tb_set_cell((x), (y), (c)->ch, (c)->fg, (c)->bg) +#define tb_set_clear_attributes tb_set_clear_attrs +#define tb_select_input_mode tb_set_input_mode +#define tb_select_output_mode tb_set_output_mode +#endif + +/* Define these to swap in a different allocator */ +#ifndef tb_malloc +#define tb_malloc malloc +#define tb_realloc realloc +#define tb_free free +#endif + +// __ffi_start + +#ifdef TB_OPT_TRUECOLOR +typedef uint32_t uintattr_t; +#else +typedef uint16_t uintattr_t; +#endif + +/* The terminal screen is represented as 2d array of cells. The structure is + * optimized for dealing with single-width (wcwidth()==1) Unicode code points, + * however some support for grapheme clusters (e.g., combining diacritical + * marks) and wide code points (e.g., Hiragana) is provided through ech, nech, + * cech via tb_set_cell_ex(). ech is only valid when nech>0, otherwise ch is + * used. + * + * For non-single-width code points, given N=wcwidth(ch)/wcswidth(ech): + * + * when N==0: termbox forces a single-width cell. Callers should avoid this + * if aiming to render text accurately. + * + * when N>1: termbox zeroes out the following N-1 cells and skips sending + * them to the tty. So, e.g., if the caller sets x=0,y=0 to an N==2 + * code point, the caller's next set should be at x=2,y=0. Anything + * set at x=1,y=0 will be ignored. If there are not enough columns + * remaining on the line to render N width, spaces are sent + * instead. + * + * See tb_present() for implementation. + */ +struct tb_cell { + uint32_t ch; /* a Unicode character */ + uintattr_t fg; /* bit-wise foreground attributes */ + uintattr_t bg; /* bit-wise background attributes */ +#ifdef TB_OPT_EGC + uint32_t *ech; /* a grapheme cluster of Unicode code points */ + size_t nech; /* length in bytes of ech, 0 means use ch instead of ech */ + size_t cech; /* capacity in bytes of ech */ +#endif +}; + +/* An incoming event from the tty. + * + * Given the event type, the following fields are relevant: + * + * when TB_EVENT_KEY: (key XOR ch, one will be zero), mod. Note there is + * overlap between TB_MOD_CTRL and TB_KEY_CTRL_*. + * TB_MOD_CTRL and TB_MOD_SHIFT are only set as + * modifiers to TB_KEY_ARROW_*. + * + * when TB_EVENT_RESIZE: w, h + * + * when TB_EVENT_MOUSE: key (TB_KEY_MOUSE_*), x, y + */ +struct tb_event { + uint8_t type; /* one of TB_EVENT_* constants */ + uint8_t mod; /* bit-wise TB_MOD_* constants */ + uint16_t key; /* one of TB_KEY_* constants */ + uint32_t ch; /* a Unicode code point */ + int32_t w; /* resize width */ + int32_t h; /* resize height */ + int32_t x; /* mouse x */ + int32_t y; /* mouse y */ +}; + +/* Initializes the termbox library. This function should be called before any + * other functions. tb_init() is equivalent to tb_init_file("/dev/tty"). After + * successful initialization, the library must be finalized using the + * tb_shutdown() function. + */ +int tb_init(); +int tb_init_file(const char *path); +int tb_init_fd(int ttyfd); +int tb_init_rwfd(int rfd, int wfd); +int tb_shutdown(); + +/* Returns the size of the internal back buffer (which is the same as terminal's + * window size in rows and columns). The internal buffer can be resized after + * tb_clear() or tb_present() function calls. Both dimensions have an + * unspecified negative value when called before tb_init() or after + * tb_shutdown(). + */ +int tb_width(); +int tb_height(); + +/* Clears the internal back buffer using TB_DEFAULT color or the + * color/attributes set by tb_set_clear_attrs() function. + */ +int tb_clear(); +int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg); + +/* Synchronizes the internal back buffer with the terminal by writing to tty. */ +int tb_present(); + +/* Sets the position of the cursor. Upper-left character is (0, 0). */ +int tb_set_cursor(int cx, int cy); +int tb_hide_cursor(); + +/* Set cell contents in the internal back buffer at the specified position. Use + * tb_set_cell_ex() for rendering grapheme clusters (e.g., combining diacritical + * marks). Function tb_set_cell(x, y, ch, fg, bg) is equivalent to + * tb_set_cell_ex(x, y, &ch, 1, fg, bg). tb_extend_cell() is a shortcut for + * appending 1 code point to cell->ech. + */ +int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg); +int tb_set_cell_ex(int x, int y, uint32_t *ch, size_t nch, uintattr_t fg, + uintattr_t bg); +int tb_extend_cell(int x, int y, uint32_t ch); + +/* Sets the input mode. Termbox has two input modes: + * + * 1. TB_INPUT_ESC + * When escape (\x1b) is in the buffer and there's no match for an escape + * sequence, a key event for TB_KEY_ESC is returned. + * + * 2. TB_INPUT_ALT + * When escape (\x1b) is in the buffer and there's no match for an escape + * sequence, the next keyboard event is returned with a TB_MOD_ALT modifier. + * + * You can also apply TB_INPUT_MOUSE via bitwise OR operation to either of the + * modes (e.g., TB_INPUT_ESC | TB_INPUT_MOUSE) to receive TB_EVENT_MOUSE events. + * If none of the main two modes were set, but the mouse mode was, TB_INPUT_ESC + * mode is used. If for some reason you've decided to use + * (TB_INPUT_ESC | TB_INPUT_ALT) combination, it will behave as if only + * TB_INPUT_ESC was selected. + * + * If mode is TB_INPUT_CURRENT, the function returns the current input mode. + * + * The default input mode is TB_INPUT_ESC. + */ +int tb_set_input_mode(int mode); + +/* Sets the termbox output mode. Termbox has three output modes: + * + * 1. TB_OUTPUT_NORMAL => [1..8] + * This mode provides 8 different colors: + * TB_BLACK, TB_RED, TB_GREEN, TB_YELLOW, + * TB_BLUE, TB_MAGENTA, TB_CYAN, TB_WHITE + * Colors may be bitwise OR'd with attributes: + * TB_BOLD, TB_UNDERLINE, TB_REVERSE, TB_ITALIC, TB_BLINK + * + * Some notes: TB_REVERSE can be applied as either fg or bg attributes for + * the same effect. TB_BOLD, TB_UNDERLINE, TB_ITALIC, TB_BLINK apply as fg + * attributes only, and are ignored as bg attributes. + * + * Example usage: + * tb_set_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED); + * + * 2. TB_OUTPUT_256 => [0..255] + * In this mode you get 256 distinct colors: + * 0x00 - 0x07: the 8 colors as in TB_OUTPUT_NORMAL + * 0x08 - 0x0f: bright versions of the above + * 0x10 - 0xe7: 216 different colors + * 0xe8 - 0xff: 24 different shades of grey + * + * Example usage: + * tb_set_cell(x, y, '@', 184, 240); + * tb_set_cell(x, y, '@', 0xb8, 0xf0); + * + * 3. TB_OUTPUT_216 => [0..215] + * This mode supports the 3rd range of TB_OUTPUT_256 only, but you don't + * need to provide an offset. + * + * 4. TB_OUTPUT_GRAYSCALE => [0..23] + * This mode supports the 4th range of TB_OUTPUT_256 only, but you don't + * need to provide an offset. + * + * 5. TB_OUTPUT_TRUECOLOR => [0x000000..0xffffff] + * This mode provides 24-bit color on supported terminals. The format is + * 0xRRGGBB. Colors may be bitwise OR'd with `TB_TRUECOLOR_*` attributes. + * + * If mode is TB_OUTPUT_CURRENT, the function returns the current output mode. + * + * The default output mode is TB_OUTPUT_NORMAL. + * + * Note, not all terminals support all output modes, especially beyond + * TB_OUTPUT_NORMAL. There is also no very reliable way to determine color + * support dynamically. If portability is desired, callers are recommended to + * use TB_OUTPUT_NORMAL or make output mode end-user configurable. + */ +int tb_set_output_mode(int mode); + +/* Wait for an event up to timeout_ms milliseconds and fill the event structure + * with it. If no event is available within the timeout period, TB_ERR_NO_EVENT + * is returned. On a resize event, the underlying select(2) call may be + * interrupted, yielding a return code of TB_ERR_POLL. In this case, you may + * check errno via tb_last_errno(). If it's EINTR, you can safely ignore that + * and call tb_peek_event() again. + */ +int tb_peek_event(struct tb_event *event, int timeout_ms); + +/* Same as tb_peek_event except no timeout. */ +int tb_poll_event(struct tb_event *event); + +/* Internal termbox FDs that can be used with poll() / select(). Must call + * tb_poll_event() / tb_peek_event() if activity is detected. */ +int tb_get_fds(int *ttyfd, int *resizefd); + +/* Print and printf functions. Specify param out_w to determine width of printed + * string. + */ +int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str); +int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt, ...); +int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, + const char *str); +int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, + const char *fmt, ...); + +/* Send raw bytes to terminal. */ +int tb_send(const char *buf, size_t nbuf); +int tb_sendf(const char *fmt, ...); + +/* Set custom functions. fn_type is one of TB_FUNC_* constants, fn is a + * compatible function pointer, or NULL to clear. + * + * TB_FUNC_EXTRACT_PRE: + * If specified, invoke this function BEFORE termbox tries to extract any + * escape sequences from the input buffer. + * + * TB_FUNC_EXTRACT_POST: + * If specified, invoke this function AFTER termbox tries (and fails) to + * extract any escape sequences from the input buffer. + */ +int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *)); + +/* Utility functions. */ +int tb_utf8_char_length(char c); +int tb_utf8_char_to_unicode(uint32_t *out, const char *c); +int tb_utf8_unicode_to_char(char *out, uint32_t c); +int tb_last_errno(); +const char *tb_strerror(int err); +struct tb_cell *tb_cell_buffer(); +int tb_has_truecolor(); +int tb_has_egc(); + +#ifdef __cplusplus +} +#endif + +#endif /* __TERMBOX_H */ + +#ifdef TB_IMPL + +#define if_err_return(rv, expr) \ + if (((rv) = (expr)) != TB_OK) \ + return (rv) +#define if_err_break(rv, expr) \ + if (((rv) = (expr)) != TB_OK) \ + break +#define if_ok_return(rv, expr) \ + if (((rv) = (expr)) == TB_OK) \ + return (rv) +#define if_ok_or_need_more_return(rv, expr) \ + if (((rv) = (expr)) == TB_OK || (rv) == TB_ERR_NEED_MORE) \ + return (rv) + +#define send_literal(rv, a) \ + if_err_return((rv), bytebuf_nputs(&global.out, (a), sizeof(a) - 1)) + +#define send_num(rv, nbuf, n) \ + if_err_return((rv), \ + bytebuf_nputs(&global.out, (nbuf), convert_num((n), (nbuf)))) + +#define snprintf_or_return(rv, str, sz, fmt, ...) \ + do { \ + (rv) = snprintf((str), (sz), (fmt), __VA_ARGS__); \ + if ((rv) < 0 || (rv) >= (int)(sz)) \ + return TB_ERR; \ + } while (0) + +#define if_not_init_return() \ + if (!global.initialized) \ + return TB_ERR_NOT_INIT + +struct bytebuf_t { + char *buf; + size_t len; + size_t cap; +}; + +struct cellbuf_t { + int width; + int height; + struct tb_cell *cells; +}; + +struct cap_trie_t { + char c; + struct cap_trie_t *children; + size_t nchildren; + int is_leaf; + uint16_t key; + uint8_t mod; +}; + +struct tb_global_t { + int ttyfd; + int rfd; + int wfd; + int ttyfd_open; + int resize_pipefd[2]; + int width; + int height; + int cursor_x; + int cursor_y; + int last_x; + int last_y; + uintattr_t fg; + uintattr_t bg; + uintattr_t last_fg; + uintattr_t last_bg; + int input_mode; + int output_mode; + char *terminfo; + size_t nterminfo; + const char *caps[TB_CAP__COUNT]; + struct cap_trie_t cap_trie; + struct bytebuf_t in; + struct bytebuf_t out; + struct cellbuf_t back; + struct cellbuf_t front; + struct termios orig_tios; + int has_orig_tios; + int last_errno; + int initialized; + int (*fn_extract_esc_pre)(struct tb_event *, size_t *); + int (*fn_extract_esc_post)(struct tb_event *, size_t *); +}; + +static struct tb_global_t global = {0}; + +/* BEGIN codegen c */ +/* Produced by ./codegen.sh on Sun, 19 Sep 2021 01:02:03 +0000 */ + +static const int16_t terminfo_cap_indexes[] = { + 66, // kf1 (TB_CAP_F1) + 68, // kf2 (TB_CAP_F2) + 69, // kf3 (TB_CAP_F3) + 70, // kf4 (TB_CAP_F4) + 71, // kf5 (TB_CAP_F5) + 72, // kf6 (TB_CAP_F6) + 73, // kf7 (TB_CAP_F7) + 74, // kf8 (TB_CAP_F8) + 75, // kf9 (TB_CAP_F9) + 67, // kf10 (TB_CAP_F10) + 216, // kf11 (TB_CAP_F11) + 217, // kf12 (TB_CAP_F12) + 77, // kich1 (TB_CAP_INSERT) + 59, // kdch1 (TB_CAP_DELETE) + 76, // khome (TB_CAP_HOME) + 164, // kend (TB_CAP_END) + 82, // kpp (TB_CAP_PGUP) + 81, // knp (TB_CAP_PGDN) + 87, // kcuu1 (TB_CAP_ARROW_UP) + 61, // kcud1 (TB_CAP_ARROW_DOWN) + 79, // kcub1 (TB_CAP_ARROW_LEFT) + 83, // kcuf1 (TB_CAP_ARROW_RIGHT) + 148, // kcbt (TB_CAP_BACK_TAB) + 28, // smcup (TB_CAP_ENTER_CA) + 40, // rmcup (TB_CAP_EXIT_CA) + 16, // cnorm (TB_CAP_SHOW_CURSOR) + 13, // civis (TB_CAP_HIDE_CURSOR) + 5, // clear (TB_CAP_CLEAR_SCREEN) + 39, // sgr0 (TB_CAP_SGR0) + 36, // smul (TB_CAP_UNDERLINE) + 27, // bold (TB_CAP_BOLD) + 26, // blink (TB_CAP_BLINK) + 311, // sitm (TB_CAP_ITALIC) + 34, // rev (TB_CAP_REVERSE) + 89, // smkx (TB_CAP_ENTER_KEYPAD) + 88, // rmkx (TB_CAP_EXIT_KEYPAD) +}; + +// xterm +static const char *xterm_caps[] = { + "\033OP", // kf1 (TB_CAP_F1) + "\033OQ", // kf2 (TB_CAP_F2) + "\033OR", // kf3 (TB_CAP_F3) + "\033OS", // kf4 (TB_CAP_F4) + "\033[15~", // kf5 (TB_CAP_F5) + "\033[17~", // kf6 (TB_CAP_F6) + "\033[18~", // kf7 (TB_CAP_F7) + "\033[19~", // kf8 (TB_CAP_F8) + "\033[20~", // kf9 (TB_CAP_F9) + "\033[21~", // kf10 (TB_CAP_F10) + "\033[23~", // kf11 (TB_CAP_F11) + "\033[24~", // kf12 (TB_CAP_F12) + "\033[2~", // kich1 (TB_CAP_INSERT) + "\033[3~", // kdch1 (TB_CAP_DELETE) + "\033OH", // khome (TB_CAP_HOME) + "\033OF", // kend (TB_CAP_END) + "\033[5~", // kpp (TB_CAP_PGUP) + "\033[6~", // knp (TB_CAP_PGDN) + "\033OA", // kcuu1 (TB_CAP_ARROW_UP) + "\033OB", // kcud1 (TB_CAP_ARROW_DOWN) + "\033OD", // kcub1 (TB_CAP_ARROW_LEFT) + "\033OC", // kcuf1 (TB_CAP_ARROW_RIGHT) + "\033[Z", // kcbt (TB_CAP_BACK_TAB) + "\033[?1049h\033[22;0;0t", // smcup (TB_CAP_ENTER_CA) + "\033[?1049l\033[23;0;0t", // rmcup (TB_CAP_EXIT_CA) + "\033[?12l\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) + "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) + "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) + "\033(B\033[m", // sgr0 (TB_CAP_SGR0) + "\033[4m", // smul (TB_CAP_UNDERLINE) + "\033[1m", // bold (TB_CAP_BOLD) + "\033[5m", // blink (TB_CAP_BLINK) + "\033[3m", // sitm (TB_CAP_ITALIC) + "\033[7m", // rev (TB_CAP_REVERSE) + "\033[?1h\033=", // smkx (TB_CAP_ENTER_KEYPAD) + "\033[?1l\033>", // rmkx (TB_CAP_EXIT_KEYPAD) +}; + +// linux +static const char *linux_caps[] = { + "\033[[A", // kf1 (TB_CAP_F1) + "\033[[B", // kf2 (TB_CAP_F2) + "\033[[C", // kf3 (TB_CAP_F3) + "\033[[D", // kf4 (TB_CAP_F4) + "\033[[E", // kf5 (TB_CAP_F5) + "\033[17~", // kf6 (TB_CAP_F6) + "\033[18~", // kf7 (TB_CAP_F7) + "\033[19~", // kf8 (TB_CAP_F8) + "\033[20~", // kf9 (TB_CAP_F9) + "\033[21~", // kf10 (TB_CAP_F10) + "\033[23~", // kf11 (TB_CAP_F11) + "\033[24~", // kf12 (TB_CAP_F12) + "\033[2~", // kich1 (TB_CAP_INSERT) + "\033[3~", // kdch1 (TB_CAP_DELETE) + "\033[1~", // khome (TB_CAP_HOME) + "\033[4~", // kend (TB_CAP_END) + "\033[5~", // kpp (TB_CAP_PGUP) + "\033[6~", // knp (TB_CAP_PGDN) + "\033[A", // kcuu1 (TB_CAP_ARROW_UP) + "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) + "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) + "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) + "\033[Z", // kcbt (TB_CAP_BACK_TAB) + "", // smcup (TB_CAP_ENTER_CA) + "", // rmcup (TB_CAP_EXIT_CA) + "\033[?25h\033[?0c", // cnorm (TB_CAP_SHOW_CURSOR) + "\033[?25l\033[?1c", // civis (TB_CAP_HIDE_CURSOR) + "\033[H\033[J", // clear (TB_CAP_CLEAR_SCREEN) + "\033[m\017", // sgr0 (TB_CAP_SGR0) + "\033[4m", // smul (TB_CAP_UNDERLINE) + "\033[1m", // bold (TB_CAP_BOLD) + "\033[5m", // blink (TB_CAP_BLINK) + "", // sitm (TB_CAP_ITALIC) + "\033[7m", // rev (TB_CAP_REVERSE) + "", // smkx (TB_CAP_ENTER_KEYPAD) + "", // rmkx (TB_CAP_EXIT_KEYPAD) +}; + +// screen +static const char *screen_caps[] = { + "\033OP", // kf1 (TB_CAP_F1) + "\033OQ", // kf2 (TB_CAP_F2) + "\033OR", // kf3 (TB_CAP_F3) + "\033OS", // kf4 (TB_CAP_F4) + "\033[15~", // kf5 (TB_CAP_F5) + "\033[17~", // kf6 (TB_CAP_F6) + "\033[18~", // kf7 (TB_CAP_F7) + "\033[19~", // kf8 (TB_CAP_F8) + "\033[20~", // kf9 (TB_CAP_F9) + "\033[21~", // kf10 (TB_CAP_F10) + "\033[23~", // kf11 (TB_CAP_F11) + "\033[24~", // kf12 (TB_CAP_F12) + "\033[2~", // kich1 (TB_CAP_INSERT) + "\033[3~", // kdch1 (TB_CAP_DELETE) + "\033[1~", // khome (TB_CAP_HOME) + "\033[4~", // kend (TB_CAP_END) + "\033[5~", // kpp (TB_CAP_PGUP) + "\033[6~", // knp (TB_CAP_PGDN) + "\033OA", // kcuu1 (TB_CAP_ARROW_UP) + "\033OB", // kcud1 (TB_CAP_ARROW_DOWN) + "\033OD", // kcub1 (TB_CAP_ARROW_LEFT) + "\033OC", // kcuf1 (TB_CAP_ARROW_RIGHT) + "\033[Z", // kcbt (TB_CAP_BACK_TAB) + "\033[?1049h", // smcup (TB_CAP_ENTER_CA) + "\033[?1049l", // rmcup (TB_CAP_EXIT_CA) + "\033[34h\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) + "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) + "\033[H\033[J", // clear (TB_CAP_CLEAR_SCREEN) + "\033[m\017", // sgr0 (TB_CAP_SGR0) + "\033[4m", // smul (TB_CAP_UNDERLINE) + "\033[1m", // bold (TB_CAP_BOLD) + "\033[5m", // blink (TB_CAP_BLINK) + "", // sitm (TB_CAP_ITALIC) + "\033[7m", // rev (TB_CAP_REVERSE) + "\033[?1h\033=", // smkx (TB_CAP_ENTER_KEYPAD) + "\033[?1l\033>", // rmkx (TB_CAP_EXIT_KEYPAD) +}; + +// rxvt-256color +static const char *rxvt_256color_caps[] = { + "\033[11~", // kf1 (TB_CAP_F1) + "\033[12~", // kf2 (TB_CAP_F2) + "\033[13~", // kf3 (TB_CAP_F3) + "\033[14~", // kf4 (TB_CAP_F4) + "\033[15~", // kf5 (TB_CAP_F5) + "\033[17~", // kf6 (TB_CAP_F6) + "\033[18~", // kf7 (TB_CAP_F7) + "\033[19~", // kf8 (TB_CAP_F8) + "\033[20~", // kf9 (TB_CAP_F9) + "\033[21~", // kf10 (TB_CAP_F10) + "\033[23~", // kf11 (TB_CAP_F11) + "\033[24~", // kf12 (TB_CAP_F12) + "\033[2~", // kich1 (TB_CAP_INSERT) + "\033[3~", // kdch1 (TB_CAP_DELETE) + "\033[7~", // khome (TB_CAP_HOME) + "\033[8~", // kend (TB_CAP_END) + "\033[5~", // kpp (TB_CAP_PGUP) + "\033[6~", // knp (TB_CAP_PGDN) + "\033[A", // kcuu1 (TB_CAP_ARROW_UP) + "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) + "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) + "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) + "\033[Z", // kcbt (TB_CAP_BACK_TAB) + "\0337\033[?47h", // smcup (TB_CAP_ENTER_CA) + "\033[2J\033[?47l\0338", // rmcup (TB_CAP_EXIT_CA) + "\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) + "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) + "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) + "\033[m\017", // sgr0 (TB_CAP_SGR0) + "\033[4m", // smul (TB_CAP_UNDERLINE) + "\033[1m", // bold (TB_CAP_BOLD) + "\033[5m", // blink (TB_CAP_BLINK) + "", // sitm (TB_CAP_ITALIC) + "\033[7m", // rev (TB_CAP_REVERSE) + "\033=", // smkx (TB_CAP_ENTER_KEYPAD) + "\033>", // rmkx (TB_CAP_EXIT_KEYPAD) +}; + +// rxvt-unicode +static const char *rxvt_unicode_caps[] = { + "\033[11~", // kf1 (TB_CAP_F1) + "\033[12~", // kf2 (TB_CAP_F2) + "\033[13~", // kf3 (TB_CAP_F3) + "\033[14~", // kf4 (TB_CAP_F4) + "\033[15~", // kf5 (TB_CAP_F5) + "\033[17~", // kf6 (TB_CAP_F6) + "\033[18~", // kf7 (TB_CAP_F7) + "\033[19~", // kf8 (TB_CAP_F8) + "\033[20~", // kf9 (TB_CAP_F9) + "\033[21~", // kf10 (TB_CAP_F10) + "\033[23~", // kf11 (TB_CAP_F11) + "\033[24~", // kf12 (TB_CAP_F12) + "\033[2~", // kich1 (TB_CAP_INSERT) + "\033[3~", // kdch1 (TB_CAP_DELETE) + "\033[7~", // khome (TB_CAP_HOME) + "\033[8~", // kend (TB_CAP_END) + "\033[5~", // kpp (TB_CAP_PGUP) + "\033[6~", // knp (TB_CAP_PGDN) + "\033[A", // kcuu1 (TB_CAP_ARROW_UP) + "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) + "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) + "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) + "\033[Z", // kcbt (TB_CAP_BACK_TAB) + "\033[?1049h", // smcup (TB_CAP_ENTER_CA) + "\033[r\033[?1049l", // rmcup (TB_CAP_EXIT_CA) + "\033[?12l\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) + "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) + "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) + "\033[m\033(B", // sgr0 (TB_CAP_SGR0) + "\033[4m", // smul (TB_CAP_UNDERLINE) + "\033[1m", // bold (TB_CAP_BOLD) + "\033[5m", // blink (TB_CAP_BLINK) + "\033[3m", // sitm (TB_CAP_ITALIC) + "\033[7m", // rev (TB_CAP_REVERSE) + "\033=", // smkx (TB_CAP_ENTER_KEYPAD) + "\033>", // rmkx (TB_CAP_EXIT_KEYPAD) +}; + +// Eterm +static const char *eterm_caps[] = { + "\033[11~", // kf1 (TB_CAP_F1) + "\033[12~", // kf2 (TB_CAP_F2) + "\033[13~", // kf3 (TB_CAP_F3) + "\033[14~", // kf4 (TB_CAP_F4) + "\033[15~", // kf5 (TB_CAP_F5) + "\033[17~", // kf6 (TB_CAP_F6) + "\033[18~", // kf7 (TB_CAP_F7) + "\033[19~", // kf8 (TB_CAP_F8) + "\033[20~", // kf9 (TB_CAP_F9) + "\033[21~", // kf10 (TB_CAP_F10) + "\033[23~", // kf11 (TB_CAP_F11) + "\033[24~", // kf12 (TB_CAP_F12) + "\033[2~", // kich1 (TB_CAP_INSERT) + "\033[3~", // kdch1 (TB_CAP_DELETE) + "\033[7~", // khome (TB_CAP_HOME) + "\033[8~", // kend (TB_CAP_END) + "\033[5~", // kpp (TB_CAP_PGUP) + "\033[6~", // knp (TB_CAP_PGDN) + "\033[A", // kcuu1 (TB_CAP_ARROW_UP) + "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) + "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) + "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) + "", // kcbt (TB_CAP_BACK_TAB) + "\0337\033[?47h", // smcup (TB_CAP_ENTER_CA) + "\033[2J\033[?47l\0338", // rmcup (TB_CAP_EXIT_CA) + "\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) + "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) + "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) + "\033[m\017", // sgr0 (TB_CAP_SGR0) + "\033[4m", // smul (TB_CAP_UNDERLINE) + "\033[1m", // bold (TB_CAP_BOLD) + "\033[5m", // blink (TB_CAP_BLINK) + "", // sitm (TB_CAP_ITALIC) + "\033[7m", // rev (TB_CAP_REVERSE) + "", // smkx (TB_CAP_ENTER_KEYPAD) + "", // rmkx (TB_CAP_EXIT_KEYPAD) +}; + +static struct { + const char *name; + const char **caps; + const char *alias; +} builtin_terms[] = { + {"xterm", xterm_caps, "" }, + {"linux", linux_caps, "" }, + {"screen", screen_caps, "tmux"}, + {"rxvt-256color", rxvt_256color_caps, "" }, + {"rxvt-unicode", rxvt_unicode_caps, "rxvt"}, + {"Eterm", eterm_caps, "" }, + {NULL, NULL, NULL }, +}; + +/* END codegen c */ + +static struct { + const char *cap; + const uint16_t key; + const uint8_t mod; +} builtin_mod_caps[] = { + // xterm arrows + {"\x1b[1;2A", TB_KEY_ARROW_UP, TB_MOD_SHIFT }, + {"\x1b[1;3A", TB_KEY_ARROW_UP, TB_MOD_ALT }, + {"\x1b[1;4A", TB_KEY_ARROW_UP, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5A", TB_KEY_ARROW_UP, TB_MOD_CTRL }, + {"\x1b[1;6A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2B", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT }, + {"\x1b[1;3B", TB_KEY_ARROW_DOWN, TB_MOD_ALT }, + {"\x1b[1;4B", TB_KEY_ARROW_DOWN, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL }, + {"\x1b[1;6B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2C", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT }, + {"\x1b[1;3C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT }, + {"\x1b[1;4C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL }, + {"\x1b[1;6C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2D", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT }, + {"\x1b[1;3D", TB_KEY_ARROW_LEFT, TB_MOD_ALT }, + {"\x1b[1;4D", TB_KEY_ARROW_LEFT, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL }, + {"\x1b[1;6D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + // xterm keys + {"\x1b[1;2H", TB_KEY_HOME, TB_MOD_SHIFT }, + {"\x1b[1;3H", TB_KEY_HOME, TB_MOD_ALT }, + {"\x1b[1;4H", TB_KEY_HOME, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5H", TB_KEY_HOME, TB_MOD_CTRL }, + {"\x1b[1;6H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2F", TB_KEY_END, TB_MOD_SHIFT }, + {"\x1b[1;3F", TB_KEY_END, TB_MOD_ALT }, + {"\x1b[1;4F", TB_KEY_END, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5F", TB_KEY_END, TB_MOD_CTRL }, + {"\x1b[1;6F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[2;2~", TB_KEY_INSERT, TB_MOD_SHIFT }, + {"\x1b[2;3~", TB_KEY_INSERT, TB_MOD_ALT }, + {"\x1b[2;4~", TB_KEY_INSERT, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[2;5~", TB_KEY_INSERT, TB_MOD_CTRL }, + {"\x1b[2;6~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[2;7~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[2;8~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[3;2~", TB_KEY_DELETE, TB_MOD_SHIFT }, + {"\x1b[3;3~", TB_KEY_DELETE, TB_MOD_ALT }, + {"\x1b[3;4~", TB_KEY_DELETE, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[3;5~", TB_KEY_DELETE, TB_MOD_CTRL }, + {"\x1b[3;6~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[3;7~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[3;8~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[5;2~", TB_KEY_PGUP, TB_MOD_SHIFT }, + {"\x1b[5;3~", TB_KEY_PGUP, TB_MOD_ALT }, + {"\x1b[5;4~", TB_KEY_PGUP, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[5;5~", TB_KEY_PGUP, TB_MOD_CTRL }, + {"\x1b[5;6~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[5;7~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[5;8~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[6;2~", TB_KEY_PGDN, TB_MOD_SHIFT }, + {"\x1b[6;3~", TB_KEY_PGDN, TB_MOD_ALT }, + {"\x1b[6;4~", TB_KEY_PGDN, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[6;5~", TB_KEY_PGDN, TB_MOD_CTRL }, + {"\x1b[6;6~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[6;7~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[6;8~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2P", TB_KEY_F1, TB_MOD_SHIFT }, + {"\x1b[1;3P", TB_KEY_F1, TB_MOD_ALT }, + {"\x1b[1;4P", TB_KEY_F1, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5P", TB_KEY_F1, TB_MOD_CTRL }, + {"\x1b[1;6P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2Q", TB_KEY_F2, TB_MOD_SHIFT }, + {"\x1b[1;3Q", TB_KEY_F2, TB_MOD_ALT }, + {"\x1b[1;4Q", TB_KEY_F2, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5Q", TB_KEY_F2, TB_MOD_CTRL }, + {"\x1b[1;6Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2R", TB_KEY_F3, TB_MOD_SHIFT }, + {"\x1b[1;3R", TB_KEY_F3, TB_MOD_ALT }, + {"\x1b[1;4R", TB_KEY_F3, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5R", TB_KEY_F3, TB_MOD_CTRL }, + {"\x1b[1;6R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2S", TB_KEY_F4, TB_MOD_SHIFT }, + {"\x1b[1;3S", TB_KEY_F4, TB_MOD_ALT }, + {"\x1b[1;4S", TB_KEY_F4, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5S", TB_KEY_F4, TB_MOD_CTRL }, + {"\x1b[1;6S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[15;2~", TB_KEY_F5, TB_MOD_SHIFT }, + {"\x1b[15;3~", TB_KEY_F5, TB_MOD_ALT }, + {"\x1b[15;4~", TB_KEY_F5, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[15;5~", TB_KEY_F5, TB_MOD_CTRL }, + {"\x1b[15;6~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[15;7~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[15;8~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[17;2~", TB_KEY_F6, TB_MOD_SHIFT }, + {"\x1b[17;3~", TB_KEY_F6, TB_MOD_ALT }, + {"\x1b[17;4~", TB_KEY_F6, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[17;5~", TB_KEY_F6, TB_MOD_CTRL }, + {"\x1b[17;6~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[17;7~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[17;8~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[18;2~", TB_KEY_F7, TB_MOD_SHIFT }, + {"\x1b[18;3~", TB_KEY_F7, TB_MOD_ALT }, + {"\x1b[18;4~", TB_KEY_F7, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[18;5~", TB_KEY_F7, TB_MOD_CTRL }, + {"\x1b[18;6~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[18;7~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[18;8~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[19;2~", TB_KEY_F8, TB_MOD_SHIFT }, + {"\x1b[19;3~", TB_KEY_F8, TB_MOD_ALT }, + {"\x1b[19;4~", TB_KEY_F8, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[19;5~", TB_KEY_F8, TB_MOD_CTRL }, + {"\x1b[19;6~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[19;7~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[19;8~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[20;2~", TB_KEY_F9, TB_MOD_SHIFT }, + {"\x1b[20;3~", TB_KEY_F9, TB_MOD_ALT }, + {"\x1b[20;4~", TB_KEY_F9, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[20;5~", TB_KEY_F9, TB_MOD_CTRL }, + {"\x1b[20;6~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[20;7~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[20;8~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[21;2~", TB_KEY_F10, TB_MOD_SHIFT }, + {"\x1b[21;3~", TB_KEY_F10, TB_MOD_ALT }, + {"\x1b[21;4~", TB_KEY_F10, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[21;5~", TB_KEY_F10, TB_MOD_CTRL }, + {"\x1b[21;6~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[21;7~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[21;8~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[23;2~", TB_KEY_F11, TB_MOD_SHIFT }, + {"\x1b[23;3~", TB_KEY_F11, TB_MOD_ALT }, + {"\x1b[23;4~", TB_KEY_F11, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[23;5~", TB_KEY_F11, TB_MOD_CTRL }, + {"\x1b[23;6~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[23;7~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[23;8~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[24;2~", TB_KEY_F12, TB_MOD_SHIFT }, + {"\x1b[24;3~", TB_KEY_F12, TB_MOD_ALT }, + {"\x1b[24;4~", TB_KEY_F12, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[24;5~", TB_KEY_F12, TB_MOD_CTRL }, + {"\x1b[24;6~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[24;7~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[24;8~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + // rxvt arrows + {"\x1b[a", TB_KEY_ARROW_UP, TB_MOD_SHIFT }, + {"\x1b\x1b[A", TB_KEY_ARROW_UP, TB_MOD_ALT }, + {"\x1b\x1b[a", TB_KEY_ARROW_UP, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1bOa", TB_KEY_ARROW_UP, TB_MOD_CTRL }, + {"\x1b\x1bOa", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT }, + + {"\x1b[b", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT }, + {"\x1b\x1b[B", TB_KEY_ARROW_DOWN, TB_MOD_ALT }, + {"\x1b\x1b[b", TB_KEY_ARROW_DOWN, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1bOb", TB_KEY_ARROW_DOWN, TB_MOD_CTRL }, + {"\x1b\x1bOb", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT }, + + {"\x1b[c", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT }, + {"\x1b\x1b[C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT }, + {"\x1b\x1b[c", TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1bOc", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL }, + {"\x1b\x1bOc", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT }, + + {"\x1b[d", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT }, + {"\x1b\x1b[D", TB_KEY_ARROW_LEFT, TB_MOD_ALT }, + {"\x1b\x1b[d", TB_KEY_ARROW_LEFT, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1bOd", TB_KEY_ARROW_LEFT, TB_MOD_CTRL }, + {"\x1b\x1bOd", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT }, + + // rxvt keys + {"\x1b[7$", TB_KEY_HOME, TB_MOD_SHIFT }, + {"\x1b\x1b[7~", TB_KEY_HOME, TB_MOD_ALT }, + {"\x1b\x1b[7$", TB_KEY_HOME, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[7^", TB_KEY_HOME, TB_MOD_CTRL }, + {"\x1b[7@", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b\x1b[7^", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[7@", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b\x1b[8~", TB_KEY_END, TB_MOD_ALT }, + {"\x1b\x1b[8$", TB_KEY_END, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[8^", TB_KEY_END, TB_MOD_CTRL }, + {"\x1b\x1b[8^", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[8@", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[8@", TB_KEY_END, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[8$", TB_KEY_END, TB_MOD_SHIFT }, + + {"\x1b\x1b[2~", TB_KEY_INSERT, TB_MOD_ALT }, + {"\x1b\x1b[2$", TB_KEY_INSERT, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[2^", TB_KEY_INSERT, TB_MOD_CTRL }, + {"\x1b\x1b[2^", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[2@", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[2@", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[2$", TB_KEY_INSERT, TB_MOD_SHIFT }, + + {"\x1b\x1b[3~", TB_KEY_DELETE, TB_MOD_ALT }, + {"\x1b\x1b[3$", TB_KEY_DELETE, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[3^", TB_KEY_DELETE, TB_MOD_CTRL }, + {"\x1b\x1b[3^", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[3@", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[3@", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[3$", TB_KEY_DELETE, TB_MOD_SHIFT }, + + {"\x1b\x1b[5~", TB_KEY_PGUP, TB_MOD_ALT }, + {"\x1b\x1b[5$", TB_KEY_PGUP, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[5^", TB_KEY_PGUP, TB_MOD_CTRL }, + {"\x1b\x1b[5^", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[5@", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[5@", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[5$", TB_KEY_PGUP, TB_MOD_SHIFT }, + + {"\x1b\x1b[6~", TB_KEY_PGDN, TB_MOD_ALT }, + {"\x1b\x1b[6$", TB_KEY_PGDN, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[6^", TB_KEY_PGDN, TB_MOD_CTRL }, + {"\x1b\x1b[6^", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[6@", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[6@", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[6$", TB_KEY_PGDN, TB_MOD_SHIFT }, + + {"\x1b\x1b[11~", TB_KEY_F1, TB_MOD_ALT }, + {"\x1b\x1b[23~", TB_KEY_F1, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[11^", TB_KEY_F1, TB_MOD_CTRL }, + {"\x1b\x1b[11^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[23^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[23^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[23~", TB_KEY_F1, TB_MOD_SHIFT }, + + {"\x1b\x1b[12~", TB_KEY_F2, TB_MOD_ALT }, + {"\x1b\x1b[24~", TB_KEY_F2, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[12^", TB_KEY_F2, TB_MOD_CTRL }, + {"\x1b\x1b[12^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[24^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[24^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[24~", TB_KEY_F2, TB_MOD_SHIFT }, + + {"\x1b\x1b[13~", TB_KEY_F3, TB_MOD_ALT }, + {"\x1b\x1b[25~", TB_KEY_F3, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[13^", TB_KEY_F3, TB_MOD_CTRL }, + {"\x1b\x1b[13^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[25^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[25^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[25~", TB_KEY_F3, TB_MOD_SHIFT }, + + {"\x1b\x1b[14~", TB_KEY_F4, TB_MOD_ALT }, + {"\x1b\x1b[26~", TB_KEY_F4, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[14^", TB_KEY_F4, TB_MOD_CTRL }, + {"\x1b\x1b[14^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[26^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[26^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[26~", TB_KEY_F4, TB_MOD_SHIFT }, + + {"\x1b\x1b[15~", TB_KEY_F5, TB_MOD_ALT }, + {"\x1b\x1b[28~", TB_KEY_F5, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[15^", TB_KEY_F5, TB_MOD_CTRL }, + {"\x1b\x1b[15^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[28^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[28^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[28~", TB_KEY_F5, TB_MOD_SHIFT }, + + {"\x1b\x1b[17~", TB_KEY_F6, TB_MOD_ALT }, + {"\x1b\x1b[29~", TB_KEY_F6, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[17^", TB_KEY_F6, TB_MOD_CTRL }, + {"\x1b\x1b[17^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[29^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[29^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[29~", TB_KEY_F6, TB_MOD_SHIFT }, + + {"\x1b\x1b[18~", TB_KEY_F7, TB_MOD_ALT }, + {"\x1b\x1b[31~", TB_KEY_F7, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[18^", TB_KEY_F7, TB_MOD_CTRL }, + {"\x1b\x1b[18^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[31^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[31^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[31~", TB_KEY_F7, TB_MOD_SHIFT }, + + {"\x1b\x1b[19~", TB_KEY_F8, TB_MOD_ALT }, + {"\x1b\x1b[32~", TB_KEY_F8, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[19^", TB_KEY_F8, TB_MOD_CTRL }, + {"\x1b\x1b[19^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[32^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[32^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[32~", TB_KEY_F8, TB_MOD_SHIFT }, + + {"\x1b\x1b[20~", TB_KEY_F9, TB_MOD_ALT }, + {"\x1b\x1b[33~", TB_KEY_F9, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[20^", TB_KEY_F9, TB_MOD_CTRL }, + {"\x1b\x1b[20^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[33^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[33^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[33~", TB_KEY_F9, TB_MOD_SHIFT }, + + {"\x1b\x1b[21~", TB_KEY_F10, TB_MOD_ALT }, + {"\x1b\x1b[34~", TB_KEY_F10, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[21^", TB_KEY_F10, TB_MOD_CTRL }, + {"\x1b\x1b[21^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[34^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[34^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[34~", TB_KEY_F10, TB_MOD_SHIFT }, + + {"\x1b\x1b[23~", TB_KEY_F11, TB_MOD_ALT }, + {"\x1b\x1b[23$", TB_KEY_F11, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[23^", TB_KEY_F11, TB_MOD_CTRL }, + {"\x1b\x1b[23^", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[23@", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[23@", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[23$", TB_KEY_F11, TB_MOD_SHIFT }, + + {"\x1b\x1b[24~", TB_KEY_F12, TB_MOD_ALT }, + {"\x1b\x1b[24$", TB_KEY_F12, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[24^", TB_KEY_F12, TB_MOD_CTRL }, + {"\x1b\x1b[24^", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[24@", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[24@", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[24$", TB_KEY_F12, TB_MOD_SHIFT }, + + // linux console/putty arrows + {"\x1b[A", TB_KEY_ARROW_UP, TB_MOD_SHIFT }, + {"\x1b[B", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT }, + {"\x1b[C", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT }, + {"\x1b[D", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT }, + + // more putty arrows + {"\x1bOA", TB_KEY_ARROW_UP, TB_MOD_CTRL }, + {"\x1b\x1bOA", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1bOB", TB_KEY_ARROW_DOWN, TB_MOD_CTRL }, + {"\x1b\x1bOB", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1bOC", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL }, + {"\x1b\x1bOC", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1bOD", TB_KEY_ARROW_LEFT, TB_MOD_CTRL }, + {"\x1b\x1bOD", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT }, + + {NULL, 0, 0 }, +}; + +static const unsigned char utf8_length[256] = {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, 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, 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, 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, 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, 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, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 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, + 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, + 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1}; + +static const unsigned char utf8_mask[6] = {0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01}; + +static int tb_reset(); +static int tb_printf_inner(int x, int y, uintattr_t fg, uintattr_t bg, + size_t *out_w, const char *fmt, va_list vl); +static int init_term_attrs(); +static int init_term_caps(); +static int init_cap_trie(); +static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod); +static int cap_trie_find(const char *buf, size_t nbuf, struct cap_trie_t **last, + size_t *depth); +static int cap_trie_deinit(struct cap_trie_t *node); +static int init_resize_handler(); +static int send_init_escape_codes(); +static int send_clear(); +static int update_term_size(); +static int update_term_size_via_esc(); +static int init_cellbuf(); +static int tb_deinit(); +static int load_terminfo(); +static int load_terminfo_from_path(const char *path, const char *term); +static int read_terminfo_path(const char *path); +static int parse_terminfo_caps(); +static int load_builtin_caps(); +static const char *get_terminfo_string(int16_t str_offsets_pos, + int16_t str_table_pos, int16_t str_table_len, int16_t str_index); +static int wait_event(struct tb_event *event, int timeout); +static int extract_event(struct tb_event *event); +static int extract_esc(struct tb_event *event); +static int extract_esc_user(struct tb_event *event, int is_post); +static int extract_esc_cap(struct tb_event *event); +static int extract_esc_mouse(struct tb_event *event); +static int resize_cellbufs(); +static void handle_resize(int sig); +static int send_attr(uintattr_t fg, uintattr_t bg); +static int send_sgr(uintattr_t fg, uintattr_t bg); +static int send_cursor_if(int x, int y); +static int send_char(int x, int y, uint32_t ch); +static int send_cluster(int x, int y, uint32_t *ch, size_t nch); +static int convert_num(uint32_t num, char *buf); +static int cell_cmp(struct tb_cell *a, struct tb_cell *b); +static int cell_copy(struct tb_cell *dst, struct tb_cell *src); +static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch, + uintattr_t fg, uintattr_t bg); +static int cell_reserve_ech(struct tb_cell *cell, size_t n); +static int cell_free(struct tb_cell *cell); +static int cellbuf_init(struct cellbuf_t *c, int w, int h); +static int cellbuf_free(struct cellbuf_t *c); +static int cellbuf_clear(struct cellbuf_t *c); +static int cellbuf_get(struct cellbuf_t *c, int x, int y, struct tb_cell **out); +static int cellbuf_resize(struct cellbuf_t *c, int w, int h); +static int bytebuf_puts(struct bytebuf_t *b, const char *str); +static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr); +static int bytebuf_shift(struct bytebuf_t *b, size_t n); +static int bytebuf_flush(struct bytebuf_t *b, int fd); +static int bytebuf_reserve(struct bytebuf_t *b, size_t sz); +static int bytebuf_free(struct bytebuf_t *b); + +int tb_init() { + return tb_init_file("/dev/tty"); +} + +int tb_init_file(const char *path) { + if (global.initialized) { + return TB_ERR_INIT_ALREADY; + } + int ttyfd = open(path, O_RDWR); + if (ttyfd < 0) { + global.last_errno = errno; + return TB_ERR_INIT_OPEN; + } + global.ttyfd_open = 1; + return tb_init_fd(ttyfd); +} + +int tb_init_fd(int ttyfd) { + return tb_init_rwfd(ttyfd, ttyfd); +} + +int tb_init_rwfd(int rfd, int wfd) { + int rv; + + tb_reset(); + global.ttyfd = rfd == wfd && isatty(rfd) ? rfd : -1; + global.rfd = rfd; + global.wfd = wfd; + + do { + if_err_break(rv, init_term_attrs()); + if_err_break(rv, init_term_caps()); + if_err_break(rv, init_cap_trie()); + if_err_break(rv, init_resize_handler()); + if_err_break(rv, send_init_escape_codes()); + if_err_break(rv, send_clear()); + if_err_break(rv, update_term_size()); + if_err_break(rv, init_cellbuf()); + global.initialized = 1; + } while (0); + + if (rv != TB_OK) { + tb_deinit(); + } + + return rv; +} + +int tb_shutdown() { + if_not_init_return(); + tb_deinit(); + return TB_OK; +} + +int tb_width() { + if_not_init_return(); + return global.width; +} + +int tb_height() { + if_not_init_return(); + return global.height; +} + +int tb_clear() { + if_not_init_return(); + return cellbuf_clear(&global.back); +} + +int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg) { + if_not_init_return(); + global.fg = fg; + global.bg = bg; + return TB_OK; +} + +int tb_present() { + if_not_init_return(); + + int rv; + + // TODO Assert global.back.(width,height) == global.front.(width,height) + + global.last_x = -1; + global.last_y = -1; + + int x, y, i; + for (y = 0; y < global.front.height; y++) { + for (x = 0; x < global.front.width;) { + struct tb_cell *back, *front; + if_err_return(rv, cellbuf_get(&global.back, x, y, &back)); + if_err_return(rv, cellbuf_get(&global.front, x, y, &front)); + + int w; + { +#ifdef TB_OPT_EGC + if (back->nech > 0) + w = wcswidth((wchar_t *)back->ech, back->nech); + else +#endif + w = wcwidth(back->ch); + } + if (w < 1) { + w = 1; + } + + if (cell_cmp(back, front) != 0) { + cell_copy(front, back); + + send_attr(back->fg, back->bg); + if (w > 1 && x >= global.front.width - (w - 1)) { + for (i = x; i < global.front.width; i++) { + send_char(i, y, ' '); + } + } else { + { +#ifdef TB_OPT_EGC + if (back->nech > 0) + send_cluster(x, y, back->ech, back->nech); + else +#endif + send_char(x, y, back->ch); + } + for (i = 1; i < w; i++) { + struct tb_cell *front_wide; + if_err_return(rv, + cellbuf_get(&global.front, x + i, y, &front_wide)); + if_err_return(rv, + cell_set(front_wide, 0, 1, back->fg, back->bg)); + } + } + } + x += w; + } + } + + if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y)); + if_err_return(rv, bytebuf_flush(&global.out, global.wfd)); + + return TB_OK; +} + +int tb_set_cursor(int cx, int cy) { + if_not_init_return(); + int rv; + if (cx < 0) + cx = 0; + if (cy < 0) + cy = 0; + if (global.cursor_x == -1) { + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR])); + } + if_err_return(rv, send_cursor_if(cx, cy)); + global.cursor_x = cx; + global.cursor_y = cy; + return TB_OK; +} + +int tb_hide_cursor() { + if_not_init_return(); + int rv; + if (global.cursor_x >= 0) { + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_HIDE_CURSOR])); + } + global.cursor_x = -1; + global.cursor_y = -1; + return TB_OK; +} + +int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg) { + if_not_init_return(); + return tb_set_cell_ex(x, y, &ch, 1, fg, bg); +} + +int tb_set_cell_ex(int x, int y, uint32_t *ch, size_t nch, uintattr_t fg, + uintattr_t bg) { + if_not_init_return(); + int rv; + struct tb_cell *cell; + if_err_return(rv, cellbuf_get(&global.back, x, y, &cell)); + if_err_return(rv, cell_set(cell, ch, nch, fg, bg)); + return TB_OK; +} + +int tb_extend_cell(int x, int y, uint32_t ch) { + if_not_init_return(); +#ifdef TB_OPT_EGC + int rv; + struct tb_cell *cell; + size_t nech; + if_err_return(rv, cellbuf_get(&global.back, x, y, &cell)); + if (cell->nech > 0) { // append to ech + nech = cell->nech + 1; + if_err_return(rv, cell_reserve_ech(cell, nech)); + cell->ech[nech - 1] = ch; + } else { // make new ech + nech = 2; + if_err_return(rv, cell_reserve_ech(cell, nech)); + cell->ech[0] = cell->ch; + cell->ech[1] = ch; + } + cell->ech[nech] = '\0'; + cell->nech = nech; + return TB_OK; +#else + (void)x; + (void)y; + (void)ch; + return TB_ERR; +#endif +} + +int tb_set_input_mode(int mode) { + if_not_init_return(); + if (mode == TB_INPUT_CURRENT) { + return global.input_mode; + } + + if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0) { + mode |= TB_INPUT_ESC; + } + + if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT)) + { + mode &= ~TB_INPUT_ALT; + } + + if (mode & TB_INPUT_MOUSE) { + bytebuf_puts(&global.out, TB_HARDCAP_ENTER_MOUSE); + bytebuf_flush(&global.out, global.wfd); + } else { + bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE); + bytebuf_flush(&global.out, global.wfd); + } + + global.input_mode = mode; + return TB_OK; +} + +int tb_set_output_mode(int mode) { + if_not_init_return(); + switch (mode) { + case TB_OUTPUT_CURRENT: + return global.output_mode; + case TB_OUTPUT_NORMAL: + case TB_OUTPUT_256: + case TB_OUTPUT_216: + case TB_OUTPUT_GRAYSCALE: +#ifdef TB_OPT_TRUECOLOR + case TB_OUTPUT_TRUECOLOR: +#endif + global.output_mode = mode; + return TB_OK; + } + return TB_ERR; +} + +int tb_peek_event(struct tb_event *event, int timeout_ms) { + if_not_init_return(); + return wait_event(event, timeout_ms); +} + +int tb_poll_event(struct tb_event *event) { + if_not_init_return(); + return wait_event(event, -1); +} + +int tb_get_fds(int *ttyfd, int *resizefd) { + if_not_init_return(); + + *ttyfd = global.rfd; + *resizefd = global.resize_pipefd[0]; + + return TB_OK; +} + +int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str) { + return tb_print_ex(x, y, fg, bg, NULL, str); +} + +int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, + const char *str) { + int rv; + uint32_t uni; + int w, ix = x; + if (out_w) { + *out_w = 0; + } + while (*str) { + str += tb_utf8_char_to_unicode(&uni, str); + w = wcwidth(uni); + if (w < 0) { + w = 1; + } + if (w == 0 && x > ix) { + if_err_return(rv, tb_extend_cell(x - 1, y, uni)); + } else { + if_err_return(rv, tb_set_cell(x, y, uni, fg, bg)); + } + x += w; + if (out_w) { + *out_w += w; + } + } + return TB_OK; +} + +int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt, + ...) { + int rv; + va_list vl; + va_start(vl, fmt); + rv = tb_printf_inner(x, y, fg, bg, NULL, fmt, vl); + va_end(vl); + return rv; +} + +int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, + const char *fmt, ...) { + int rv; + va_list vl; + va_start(vl, fmt); + rv = tb_printf_inner(x, y, fg, bg, out_w, fmt, vl); + va_end(vl); + return rv; +} + +int tb_send(const char *buf, size_t nbuf) { + return bytebuf_nputs(&global.out, buf, nbuf); +} + +int tb_sendf(const char *fmt, ...) { + int rv; + char buf[TB_OPT_PRINTF_BUF]; + va_list vl; + va_start(vl, fmt); + rv = vsnprintf(buf, sizeof(buf), fmt, vl); + va_end(vl); + if (rv < 0 || rv >= (int)sizeof(buf)) { + return TB_ERR; + } + return tb_send(buf, (size_t)rv); +} + +int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *)) { + switch (fn_type) { + case TB_FUNC_EXTRACT_PRE: + global.fn_extract_esc_pre = fn; + return TB_OK; + case TB_FUNC_EXTRACT_POST: + global.fn_extract_esc_post = fn; + return TB_OK; + } + return TB_ERR; +} + +struct tb_cell *tb_cell_buffer() { + if (!global.initialized) + return NULL; + return global.back.cells; +} + +int tb_utf8_char_length(char c) { + return utf8_length[(unsigned char)c]; +} + +int tb_utf8_char_to_unicode(uint32_t *out, const char *c) { + if (*c == 0) { + return TB_ERR; + } + + int i; + unsigned char len = tb_utf8_char_length(*c); + unsigned char mask = utf8_mask[len - 1]; + uint32_t result = c[0] & mask; + for (i = 1; i < len; ++i) { + result <<= 6; + result |= c[i] & 0x3f; + } + + *out = result; + return (int)len; +} + +int tb_utf8_unicode_to_char(char *out, uint32_t c) { + int len = 0; + int first; + int i; + + if (c < 0x80) { + first = 0; + len = 1; + } else if (c < 0x800) { + first = 0xc0; + len = 2; + } else if (c < 0x10000) { + first = 0xe0; + len = 3; + } else if (c < 0x200000) { + first = 0xf0; + len = 4; + } else if (c < 0x4000000) { + first = 0xf8; + len = 5; + } else { + first = 0xfc; + len = 6; + } + + for (i = len - 1; i > 0; --i) { + out[i] = (c & 0x3f) | 0x80; + c >>= 6; + } + out[0] = c | first; + + return len; +} + +int tb_last_errno() { + return global.last_errno; +} + +const char *tb_strerror(int err) { + switch (err) { + case TB_OK: + return "Success"; + case TB_ERR_NEED_MORE: + return "Not enough input"; + case TB_ERR_INIT_ALREADY: + return "Termbox initialized already"; + case TB_ERR_MEM: + return "Out of memory"; + case TB_ERR_NO_EVENT: + return "No event"; + case TB_ERR_NO_TERM: + return "No TERM in environment"; + case TB_ERR_NOT_INIT: + return "Termbox not initialized"; + case TB_ERR_OUT_OF_BOUNDS: + return "Out of bounds"; + case TB_ERR_UNSUPPORTED_TERM: + return "Unsupported terminal"; + case TB_ERR_CAP_COLLISION: + return "Termcaps collision"; + case TB_ERR_RESIZE_SSCANF: + return "Terminal width/height not received by sscanf() after " + "resize"; + case TB_ERR: + case TB_ERR_INIT_OPEN: + case TB_ERR_READ: + case TB_ERR_RESIZE_IOCTL: + case TB_ERR_RESIZE_PIPE: + case TB_ERR_RESIZE_SIGACTION: + case TB_ERR_POLL: + case TB_ERR_TCGETATTR: + case TB_ERR_TCSETATTR: + case TB_ERR_RESIZE_WRITE: + case TB_ERR_RESIZE_POLL: + case TB_ERR_RESIZE_READ: + default: + return strerror(global.last_errno); + } +} + +int tb_has_truecolor() { +#ifdef TB_OPT_TRUECOLOR + return 1; +#else + return 0; +#endif +} + +int tb_has_egc() { +#ifdef TB_OPT_EGC + return 1; +#else + return 0; +#endif +} + +static int tb_reset() { + int ttyfd_open = global.ttyfd_open; + memset(&global, 0, sizeof(global)); + global.ttyfd = -1; + global.rfd = -1; + global.wfd = -1; + global.ttyfd_open = ttyfd_open; + global.resize_pipefd[0] = -1; + global.resize_pipefd[1] = -1; + global.width = -1; + global.height = -1; + global.cursor_x = -1; + global.cursor_y = -1; + global.last_x = -1; + global.last_y = -1; + global.fg = TB_DEFAULT; + global.bg = TB_DEFAULT; + global.last_fg = ~global.fg; + global.last_bg = ~global.bg; + global.input_mode = TB_INPUT_ESC; + global.output_mode = TB_OUTPUT_NORMAL; + return TB_OK; +} + +static int init_term_attrs() { + if (global.ttyfd < 0) { + return TB_OK; + } + + if (tcgetattr(global.ttyfd, &global.orig_tios) != 0) { + global.last_errno = errno; + return TB_ERR_TCGETATTR; + } + + struct termios tios; + memcpy(&tios, &global.orig_tios, sizeof(tios)); + global.has_orig_tios = 1; + + cfmakeraw(&tios); + tios.c_cc[VMIN] = 1; + tios.c_cc[VTIME] = 0; + + if (tcsetattr(global.ttyfd, TCSAFLUSH, &tios) != 0) { + global.last_errno = errno; + return TB_ERR_TCSETATTR; + } + + return TB_OK; +} + +int tb_printf_inner(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, + const char *fmt, va_list vl) { + int rv; + char buf[TB_OPT_PRINTF_BUF]; + rv = vsnprintf(buf, sizeof(buf), fmt, vl); + if (rv < 0 || rv >= (int)sizeof(buf)) { + return TB_ERR; + } + return tb_print_ex(x, y, fg, bg, out_w, buf); +} + +static int init_term_caps() { + if (load_terminfo() == TB_OK) { + return parse_terminfo_caps(); + } + return load_builtin_caps(); +} + +static int init_cap_trie() { + int rv, i; + + // Add caps from terminfo or built-in + for (i = 0; i < TB_CAP__COUNT_KEYS; i++) { + if_err_return(rv, cap_trie_add(global.caps[i], tb_key_i(i), 0)); + } + + // Add built-in mod caps + for (i = 0; builtin_mod_caps[i].cap != NULL; i++) { + rv = cap_trie_add(builtin_mod_caps[i].cap, builtin_mod_caps[i].key, + builtin_mod_caps[i].mod); + // Collisions are OK. This can happen if global.caps collides with + // builtin_mod_caps. It is desirable to give precedence to global.caps + // here. + if (rv != TB_OK && rv != TB_ERR_CAP_COLLISION) { + return rv; + } + } + + return TB_OK; +} + +static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod) { + struct cap_trie_t *next, *node = &global.cap_trie; + size_t i, j; + for (i = 0; cap[i] != '\0'; i++) { + char c = cap[i]; + next = NULL; + + // Check if c is already a child of node + for (j = 0; j < node->nchildren; j++) { + if (node->children[j].c == c) { + next = &node->children[j]; + break; + } + } + if (!next) { + // We need to add a new child to node + node->nchildren += 1; + node->children = + tb_realloc(node->children, sizeof(*node) * node->nchildren); + if (!node->children) { + return TB_ERR_MEM; + } + next = &node->children[node->nchildren - 1]; + memset(next, 0, sizeof(*next)); + next->c = c; + } + + // Continue + node = next; + } + + if (node->is_leaf) { + // Already a leaf here + return TB_ERR_CAP_COLLISION; + } + + node->is_leaf = 1; + node->key = key; + node->mod = mod; + return TB_OK; +} + +static int cap_trie_find(const char *buf, size_t nbuf, struct cap_trie_t **last, + size_t *depth) { + struct cap_trie_t *next, *node = &global.cap_trie; + size_t i, j; + *last = node; + *depth = 0; + for (i = 0; i < nbuf; i++) { + char c = buf[i]; + next = NULL; + + // Find c in node.children + for (j = 0; j < node->nchildren; j++) { + if (node->children[j].c == c) { + next = &node->children[j]; + break; + } + } + if (!next) { + // Not found + return TB_OK; + } + node = next; + *last = node; + *depth += 1; + if (node->is_leaf && node->nchildren < 1) { + break; + } + } + return TB_OK; +} + +static int cap_trie_deinit(struct cap_trie_t *node) { + size_t j; + for (j = 0; j < node->nchildren; j++) { + cap_trie_deinit(&node->children[j]); + } + if (node->children) { + tb_free(node->children); + } + memset(node, 0, sizeof(*node)); + return TB_OK; +} + +static int init_resize_handler() { + if (pipe(global.resize_pipefd) != 0) { + global.last_errno = errno; + return TB_ERR_RESIZE_PIPE; + } + + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handle_resize; + if (sigaction(SIGWINCH, &sa, NULL) != 0) { + global.last_errno = errno; + return TB_ERR_RESIZE_SIGACTION; + } + + return TB_OK; +} + +static int send_init_escape_codes() { + int rv; + if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_ENTER_CA])); + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_ENTER_KEYPAD])); + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_HIDE_CURSOR])); + return TB_OK; +} + +static int send_clear() { + int rv; + + if_err_return(rv, send_attr(global.fg, global.bg)); + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_CLEAR_SCREEN])); + + if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y)); + if_err_return(rv, bytebuf_flush(&global.out, global.wfd)); + + global.last_x = -1; + global.last_y = -1; + + return TB_OK; +} + +static int update_term_size() { + int rv, ioctl_errno; + + if (global.ttyfd < 0) { + return TB_OK; + } + + struct winsize sz; + memset(&sz, 0, sizeof(sz)); + + // Try ioctl TIOCGWINSZ + if (ioctl(global.ttyfd, TIOCGWINSZ, &sz) == 0) { + global.width = sz.ws_col; + global.height = sz.ws_row; + return TB_OK; + } + ioctl_errno = errno; + + // Try >cursor(9999,9999), >u7, = 0) { + bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); + bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0]); + bytebuf_puts(&global.out, global.caps[TB_CAP_CLEAR_SCREEN]); + bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_CA]); + bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_KEYPAD]); + bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE); + bytebuf_flush(&global.out, global.wfd); + } + if (global.ttyfd >= 0) { + if (global.has_orig_tios) { + tcsetattr(global.ttyfd, TCSAFLUSH, &global.orig_tios); + } + if (global.ttyfd_open) { + close(global.ttyfd); + global.ttyfd_open = 0; + } + } + + sigaction(SIGWINCH, &(struct sigaction){.sa_handler = SIG_DFL}, NULL); + if (global.resize_pipefd[0] >= 0) + close(global.resize_pipefd[0]); + if (global.resize_pipefd[1] >= 0) + close(global.resize_pipefd[1]); + + cellbuf_free(&global.back); + cellbuf_free(&global.front); + bytebuf_free(&global.in); + bytebuf_free(&global.out); + + if (global.terminfo) + tb_free(global.terminfo); + + cap_trie_deinit(&global.cap_trie); + + tb_reset(); + return TB_OK; +} + +static int load_terminfo() { + int rv; + char tmp[PATH_MAX]; + + // See terminfo(5) "Fetching Compiled Descriptions" for a description of + // this behavior. Some of these paths are compile-time ncurses options, so + // best guesses are used here. + const char *term = getenv("TERM"); + if (!term) { + return TB_ERR; + } + + // If TERMINFO is set, try that directory and stop + const char *terminfo = getenv("TERMINFO"); + if (terminfo) { + return load_terminfo_from_path(terminfo, term); + } + + // Next try ~/.terminfo + const char *home = getenv("HOME"); + if (home) { + snprintf_or_return(rv, tmp, sizeof(tmp), "%s/.terminfo", home); + if_ok_return(rv, load_terminfo_from_path(tmp, term)); + } + + // Next try TERMINFO_DIRS + // + // Note, empty entries are supposed to be interpretted as the "compiled-in + // default", which is of course system-dependent. Previously /etc/terminfo + // was used here. Let's skip empty entries altogether rather than give + // precedence to a guess, and check common paths after this loop. + const char *dirs = getenv("TERMINFO_DIRS"); + if (dirs) { + snprintf_or_return(rv, tmp, sizeof(tmp), "%s", dirs); + char *dir = strtok(tmp, ":"); + while (dir) { + const char *cdir = dir; + if (*cdir != '\0') { + if_ok_return(rv, load_terminfo_from_path(cdir, term)); + } + dir = strtok(NULL, ":"); + } + } + +#ifdef TB_TERMINFO_DIR + if_ok_return(rv, load_terminfo_from_path(TB_TERMINFO_DIR, term)); +#endif + if_ok_return(rv, load_terminfo_from_path("/usr/local/etc/terminfo", term)); + if_ok_return(rv, + load_terminfo_from_path("/usr/local/share/terminfo", term)); + if_ok_return(rv, load_terminfo_from_path("/usr/local/lib/terminfo", term)); + if_ok_return(rv, load_terminfo_from_path("/etc/terminfo", term)); + if_ok_return(rv, load_terminfo_from_path("/usr/share/terminfo", term)); + if_ok_return(rv, load_terminfo_from_path("/usr/lib/terminfo", term)); + if_ok_return(rv, load_terminfo_from_path("/usr/share/lib/terminfo", term)); + if_ok_return(rv, load_terminfo_from_path("/lib/terminfo", term)); + + return TB_ERR; +} + +static int load_terminfo_from_path(const char *path, const char *term) { + int rv; + char tmp[PATH_MAX]; + + // Look for term at this terminfo location, e.g., /x/xterm + snprintf_or_return(rv, tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term); + if_ok_return(rv, read_terminfo_path(tmp)); + +#ifdef __APPLE__ + // Try the Darwin equivalent path, e.g., /78/xterm + snprintf_or_return(rv, tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term); + return read_terminfo_path(tmp); +#endif + + return TB_ERR; +} + +static int read_terminfo_path(const char *path) { + FILE *fp = fopen(path, "rb"); + if (!fp) { + return TB_ERR; + } + + struct stat st; + if (fstat(fileno(fp), &st) != 0) { + fclose(fp); + return TB_ERR; + } + + size_t fsize = st.st_size; + char *data = tb_malloc(fsize); + if (!data) { + fclose(fp); + return TB_ERR; + } + + if (fread(data, 1, fsize, fp) != fsize) { + fclose(fp); + tb_free(data); + return TB_ERR; + } + + global.terminfo = data; + global.nterminfo = fsize; + + fclose(fp); + return TB_OK; +} + +static int parse_terminfo_caps() { + // See term(5) "LEGACY STORAGE FORMAT" and "EXTENDED STORAGE FORMAT" for a + // description of this behavior. + + // Ensure there's at least a header's worth of data + if (global.nterminfo < 6) { + return TB_ERR; + } + + int16_t *header = (int16_t *)global.terminfo; + // header[0] the magic number (octal 0432 or 01036) + // header[1] the size, in bytes, of the names section + // header[2] the number of bytes in the boolean section + // header[3] the number of short integers in the numbers section + // header[4] the number of offsets (short integers) in the strings section + // header[5] the size, in bytes, of the string table + + // Legacy ints are 16-bit, extended ints are 32-bit + const int bytes_per_int = header[0] == 01036 ? 4 // 32-bit + : 2; // 16-bit + + // > Between the boolean section and the number section, a null byte will be + // > inserted, if necessary, to ensure that the number section begins on an + // > even byte + const int align_offset = (header[1] + header[2]) % 2 != 0 ? 1 : 0; + + const int pos_str_offsets = + (6 * sizeof(int16_t)) // header (12 bytes) + + header[1] // length of names section + + header[2] // length of boolean section + + align_offset + + (header[3] * bytes_per_int); // length of numbers section + + const int pos_str_table = + pos_str_offsets + + (header[4] * sizeof(int16_t)); // length of string offsets table + + // Load caps + int i; + for (i = 0; i < TB_CAP__COUNT; i++) { + const char *cap = get_terminfo_string(pos_str_offsets, pos_str_table, + header[5], terminfo_cap_indexes[i]); + if (!cap) { + // Something is not right + return TB_ERR; + } + global.caps[i] = cap; + } + + return TB_OK; +} + +static int load_builtin_caps() { + int i, j; + const char *term = getenv("TERM"); + + if (!term) { + return TB_ERR_NO_TERM; + } + + // Check for exact TERM match + for (i = 0; builtin_terms[i].name != NULL; i++) { + if (strcmp(term, builtin_terms[i].name) == 0) { + for (j = 0; j < TB_CAP__COUNT; j++) { + global.caps[j] = builtin_terms[i].caps[j]; + } + return TB_OK; + } + } + + // Check for partial TERM or alias match + for (i = 0; builtin_terms[i].name != NULL; i++) { + if (strstr(term, builtin_terms[i].name) != NULL || + (*(builtin_terms[i].alias) != '\0' && + strstr(term, builtin_terms[i].alias) != NULL)) + { + for (j = 0; j < TB_CAP__COUNT; j++) { + global.caps[j] = builtin_terms[i].caps[j]; + } + return TB_OK; + } + } + + return TB_ERR_UNSUPPORTED_TERM; +} + +static const char *get_terminfo_string(int16_t str_offsets_pos, + int16_t str_table_pos, int16_t str_table_len, int16_t str_index) { + const int16_t *str_offset = + (int16_t *)(global.terminfo + (int)str_offsets_pos + + ((int)str_index * (int)sizeof(int16_t))); + if (*str_offset >= str_table_len) { + // Invalid string offset + return NULL; + } + if (((size_t)((int)str_table_pos + (int)*str_offset)) >= global.nterminfo) { + // Truncated/corrupt terminfo? + return NULL; + } + return ( + const char *)(global.terminfo + (int)str_table_pos + (int)*str_offset); +} + +static int wait_event(struct tb_event *event, int timeout) { + int rv; + char buf[TB_OPT_READ_BUF]; + + memset(event, 0, sizeof(*event)); + if_ok_return(rv, extract_event(event)); + + fd_set fds; + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000; + + do { + FD_ZERO(&fds); + FD_SET(global.rfd, &fds); + FD_SET(global.resize_pipefd[0], &fds); + + int maxfd = global.resize_pipefd[0] > global.rfd + ? global.resize_pipefd[0] + : global.rfd; + + int select_rv = + select(maxfd + 1, &fds, NULL, NULL, (timeout < 0) ? NULL : &tv); + + if (select_rv < 0) { + // Let EINTR/EAGAIN bubble up + global.last_errno = errno; + return TB_ERR_POLL; + } else if (select_rv == 0) { + return TB_ERR_NO_EVENT; + } + + int tty_has_events = (FD_ISSET(global.rfd, &fds)); + int resize_has_events = (FD_ISSET(global.resize_pipefd[0], &fds)); + + if (tty_has_events) { + ssize_t read_rv = read(global.rfd, buf, sizeof(buf)); + if (read_rv < 0) { + global.last_errno = errno; + return TB_ERR_READ; + } else if (read_rv > 0) { + bytebuf_nputs(&global.in, buf, read_rv); + } + } + + if (resize_has_events) { + int ignore = 0; + read(global.resize_pipefd[0], &ignore, sizeof(ignore)); + // TODO Harden against errors encountered mid-resize + if_err_return(rv, update_term_size()); + if_err_return(rv, resize_cellbufs()); + event->type = TB_EVENT_RESIZE; + event->w = global.width; + event->h = global.height; + return TB_OK; + } + + memset(event, 0, sizeof(*event)); + if_ok_return(rv, extract_event(event)); + } while (timeout == -1); + + return rv; +} + +static int extract_event(struct tb_event *event) { + int rv; + struct bytebuf_t *in = &global.in; + + if (in->len == 0) { + return TB_ERR; + } + + if (in->buf[0] == '\x1b') { + // Escape sequence? + // In TB_INPUT_ESC, skip if the buffer is a single escape char + if (!((global.input_mode & TB_INPUT_ESC) && in->len == 1)) { + if_ok_or_need_more_return(rv, extract_esc(event)); + } + + // Escape key? + if (global.input_mode & TB_INPUT_ESC) { + event->type = TB_EVENT_KEY; + event->ch = 0; + event->key = TB_KEY_ESC; + event->mod = 0; + bytebuf_shift(in, 1); + return TB_OK; + } + + // Recurse for alt key + event->mod |= TB_MOD_ALT; + bytebuf_shift(in, 1); + return extract_event(event); + } + + // ASCII control key? + if ((uint16_t)in->buf[0] < TB_KEY_SPACE || in->buf[0] == TB_KEY_BACKSPACE2) + { + event->type = TB_EVENT_KEY; + event->ch = 0; + event->key = (uint16_t)in->buf[0]; + event->mod |= TB_MOD_CTRL; + bytebuf_shift(in, 1); + return TB_OK; + } + + // UTF-8? + if (in->len >= (size_t)tb_utf8_char_length(in->buf[0])) { + event->type = TB_EVENT_KEY; + tb_utf8_char_to_unicode(&event->ch, in->buf); + event->key = 0; + bytebuf_shift(in, tb_utf8_char_length(in->buf[0])); + return TB_OK; + } + + // Need more input + return TB_ERR; +} + +static int extract_esc(struct tb_event *event) { + int rv; + if_ok_or_need_more_return(rv, extract_esc_user(event, 0)); + if_ok_or_need_more_return(rv, extract_esc_cap(event)); + if_ok_or_need_more_return(rv, extract_esc_mouse(event)); + if_ok_or_need_more_return(rv, extract_esc_user(event, 1)); + return TB_ERR; +} + +static int extract_esc_user(struct tb_event *event, int is_post) { + int rv; + size_t consumed = 0; + struct bytebuf_t *in = &global.in; + int (*fn)(struct tb_event *, size_t *); + + fn = is_post ? global.fn_extract_esc_post : global.fn_extract_esc_pre; + + if (!fn) { + return TB_ERR; + } + + rv = fn(event, &consumed); + if (rv == TB_OK) { + bytebuf_shift(in, consumed); + } + + if_ok_or_need_more_return(rv, rv); + return TB_ERR; +} + +static int extract_esc_cap(struct tb_event *event) { + int rv; + struct bytebuf_t *in = &global.in; + struct cap_trie_t *node; + size_t depth; + + if_err_return(rv, cap_trie_find(in->buf, in->len, &node, &depth)); + if (node->is_leaf) { + // Found a leaf node + event->type = TB_EVENT_KEY; + event->ch = 0; + event->key = node->key; + event->mod = node->mod; + bytebuf_shift(in, depth); + return TB_OK; + } else if (node->nchildren > 0 && in->len <= depth) { + // Found a branch node (not enough input) + return TB_ERR_NEED_MORE; + } + + return TB_ERR; +} + +static int extract_esc_mouse(struct tb_event *event) { + struct bytebuf_t *in = &global.in; + + enum type { TYPE_VT200 = 0, TYPE_1006, TYPE_1015, TYPE_MAX }; + + char *cmp[TYPE_MAX] = {// + // X10 mouse encoding, the simplest one + // \x1b [ M Cb Cx Cy + [TYPE_VT200] = "\x1b[M", + // xterm 1006 extended mode or urxvt 1015 extended mode + // xterm: \x1b [ < Cb ; Cx ; Cy (M or m) + [TYPE_1006] = "\x1b[<", + // urxvt: \x1b [ Cb ; Cx ; Cy M + [TYPE_1015] = "\x1b["}; + + enum type type = 0; + int ret = TB_ERR; + + // Unrolled at compile-time (probably) + for (; type < TYPE_MAX; type++) { + size_t size = strlen(cmp[type]); + + if (in->len >= size && (strncmp(cmp[type], in->buf, size)) == 0) { + break; + } + } + + if (type == TYPE_MAX) { + ret = TB_ERR; // No match + return ret; + } + + size_t buf_shift = 0; + + switch (type) { + case TYPE_VT200: + if (in->len >= 6) { + int b = in->buf[3] - 0x20; + int fail = 0; + + switch (b & 3) { + case 0: + event->key = ((b & 64) != 0) ? TB_KEY_MOUSE_WHEEL_UP + : TB_KEY_MOUSE_LEFT; + break; + case 1: + event->key = ((b & 64) != 0) ? TB_KEY_MOUSE_WHEEL_DOWN + : TB_KEY_MOUSE_MIDDLE; + break; + case 2: + event->key = TB_KEY_MOUSE_RIGHT; + break; + case 3: + event->key = TB_KEY_MOUSE_RELEASE; + break; + default: + ret = TB_ERR; + fail = 1; + break; + } + + if (!fail) { + if ((b & 32) != 0) { + event->mod |= TB_MOD_MOTION; + } + + // the coord is 1,1 for upper left + event->x = ((uint8_t)in->buf[4]) - 0x21; + event->y = ((uint8_t)in->buf[5]) - 0x21; + + ret = TB_OK; + } + + buf_shift = 6; + } + break; + case TYPE_1006: + // fallthrough + case TYPE_1015: { + size_t index_fail = (size_t)-1; + + enum { + FIRST_M = 0, + FIRST_SEMICOLON, + LAST_SEMICOLON, + FIRST_LAST_MAX + }; + + size_t indices[FIRST_LAST_MAX] = {index_fail, index_fail, + index_fail}; + int m_is_capital = 0; + + for (size_t i = 0; i < in->len; i++) { + if (in->buf[i] == ';') { + if (indices[FIRST_SEMICOLON] == index_fail) { + indices[FIRST_SEMICOLON] = i; + } else { + indices[LAST_SEMICOLON] = i; + } + } else if (indices[FIRST_M] == index_fail) { + if (in->buf[i] == 'm' || in->buf[i] == 'M') { + m_is_capital = (in->buf[i] == 'M'); + indices[FIRST_M] = i; + } + } + } + + if (indices[FIRST_M] == index_fail || + indices[FIRST_SEMICOLON] == index_fail || + indices[LAST_SEMICOLON] == index_fail) + { + ret = TB_ERR; + } else { + int start = (type == TYPE_1015 ? 2 : 3); + + int n1 = strtoul(&in->buf[start], NULL, 10); + int n2 = + strtoul(&in->buf[indices[FIRST_SEMICOLON] + 1], NULL, 10); + int n3 = + strtoul(&in->buf[indices[LAST_SEMICOLON] + 1], NULL, 10); + + if (type == TYPE_1015) { + n1 -= 0x20; + } + + int fail = 0; + + switch (n1 & 3) { + case 0: + event->key = ((n1 & 64) != 0) ? TB_KEY_MOUSE_WHEEL_UP + : TB_KEY_MOUSE_LEFT; + break; + case 1: + event->key = ((n1 & 64) != 0) ? TB_KEY_MOUSE_WHEEL_DOWN + : TB_KEY_MOUSE_MIDDLE; + break; + case 2: + event->key = TB_KEY_MOUSE_RIGHT; + break; + case 3: + event->key = TB_KEY_MOUSE_RELEASE; + break; + default: + ret = TB_ERR; + fail = 1; + break; + } + + buf_shift = in->len; + + if (!fail) { + if (!m_is_capital) { + // on xterm mouse release is signaled by lowercase m + event->key = TB_KEY_MOUSE_RELEASE; + } + + if ((n1 & 32) != 0) { + event->mod |= TB_MOD_MOTION; + } + + event->x = ((uint8_t)n2) - 1; + event->y = ((uint8_t)n3) - 1; + + ret = TB_OK; + } + } + } break; + case TYPE_MAX: + ret = TB_ERR; + } + + if (buf_shift > 0) { + bytebuf_shift(in, buf_shift); + } + + if (ret == TB_OK) { + event->type = TB_EVENT_MOUSE; + } + + return ret; +} + +static int resize_cellbufs() { + int rv; + if_err_return(rv, + cellbuf_resize(&global.back, global.width, global.height)); + if_err_return(rv, + cellbuf_resize(&global.front, global.width, global.height)); + if_err_return(rv, cellbuf_clear(&global.front)); + if_err_return(rv, send_clear()); + return TB_OK; +} + +static void handle_resize(int sig) { + int errno_copy = errno; + write(global.resize_pipefd[1], &sig, sizeof(sig)); + errno = errno_copy; +} + +static int send_attr(uintattr_t fg, uintattr_t bg) { + int rv; + + if (fg == global.last_fg && bg == global.last_bg) { + return TB_OK; + } + + if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0])); + + uintattr_t cfg, cbg; + switch (global.output_mode) { + default: + case TB_OUTPUT_NORMAL: + cfg = fg & 0x0f; + cbg = bg & 0x0f; + break; + + case TB_OUTPUT_256: + cfg = fg & 0xff; + cbg = bg & 0xff; + break; + + case TB_OUTPUT_216: + cfg = fg & 0xff; + if (cfg > 215) + cfg = 7; + cbg = bg & 0xff; + if (cbg > 215) + cbg = 0; + cfg += 0x10; + cbg += 0x10; + break; + + case TB_OUTPUT_GRAYSCALE: + cfg = fg & 0xff; + if (cfg > 23) + cfg = 23; + cbg = bg & 0xff; + if (cbg > 23) + cbg = 0; + cfg += 0xe8; + cbg += 0xe8; + break; + +#ifdef TB_OPT_TRUECOLOR + case TB_OUTPUT_TRUECOLOR: + cfg = fg; + cbg = bg; + break; +#endif + } + + uintattr_t attr_bold, attr_blink, attr_italic, attr_underline, attr_reverse; +#ifdef TB_OPT_TRUECOLOR + if (global.output_mode == TB_OUTPUT_TRUECOLOR) { + attr_bold = TB_TRUECOLOR_BOLD; + attr_blink = TB_TRUECOLOR_BLINK; + attr_italic = TB_TRUECOLOR_ITALIC; + attr_underline = TB_TRUECOLOR_UNDERLINE; + attr_reverse = TB_TRUECOLOR_REVERSE; + } else +#endif + { + attr_bold = TB_BOLD; + attr_blink = TB_BLINK; + attr_italic = TB_ITALIC; + attr_underline = TB_UNDERLINE; + attr_reverse = TB_REVERSE; + } + + if (fg & attr_bold) + if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BOLD])); + + if (fg & attr_blink) + if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BLINK])); + + if (fg & attr_underline) + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_UNDERLINE])); + + if (fg & attr_italic) + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_ITALIC])); + + if ((fg & attr_reverse) || (bg & attr_reverse)) + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_REVERSE])); + + if_err_return(rv, send_sgr(cfg, cbg)); + + global.last_fg = fg; + global.last_bg = bg; + + return TB_OK; +} + +static int send_sgr(uintattr_t fg, uintattr_t bg) { + int rv; + char nbuf[32]; + + if ( +#ifdef TB_OPT_TRUECOLOR + global.output_mode != TB_OUTPUT_TRUECOLOR && +#endif + fg == TB_DEFAULT && bg == TB_DEFAULT) + { + return TB_OK; + } + + switch (global.output_mode) { + default: + case TB_OUTPUT_NORMAL: + send_literal(rv, "\x1b["); + if (fg != TB_DEFAULT) { + send_literal(rv, "3"); + send_num(rv, nbuf, fg - 1); + if (bg != TB_DEFAULT) { + send_literal(rv, ";"); + } + } + if (bg != TB_DEFAULT) { + send_literal(rv, "4"); + send_num(rv, nbuf, bg - 1); + } + send_literal(rv, "m"); + break; + + case TB_OUTPUT_256: + case TB_OUTPUT_216: + case TB_OUTPUT_GRAYSCALE: + send_literal(rv, "\x1b["); + if (fg != TB_DEFAULT) { + send_literal(rv, "38;5;"); + send_num(rv, nbuf, fg); + if (bg != TB_DEFAULT) { + send_literal(rv, ";"); + } + } + if (bg != TB_DEFAULT) { + send_literal(rv, "48;5;"); + send_num(rv, nbuf, bg); + } + send_literal(rv, "m"); + break; + +#ifdef TB_OPT_TRUECOLOR + case TB_OUTPUT_TRUECOLOR: + send_literal(rv, "\x1b[38;2;"); + send_num(rv, nbuf, (fg >> 16) & 0xff); + send_literal(rv, ";"); + send_num(rv, nbuf, (fg >> 8) & 0xff); + send_literal(rv, ";"); + send_num(rv, nbuf, fg & 0xff); + send_literal(rv, ";48;2;"); + send_num(rv, nbuf, (bg >> 16) & 0xff); + send_literal(rv, ";"); + send_num(rv, nbuf, (bg >> 8) & 0xff); + send_literal(rv, ";"); + send_num(rv, nbuf, bg & 0xff); + send_literal(rv, "m"); + break; +#endif + } + return TB_OK; +} + +static int send_cursor_if(int x, int y) { + int rv; + char nbuf[32]; + if (x < 0 || y < 0) { + return TB_OK; + } + send_literal(rv, "\x1b["); + send_num(rv, nbuf, y + 1); + send_literal(rv, ";"); + send_num(rv, nbuf, x + 1); + send_literal(rv, "H"); + return TB_OK; +} + +static int send_char(int x, int y, uint32_t ch) { + return send_cluster(x, y, &ch, 1); +} + +static int send_cluster(int x, int y, uint32_t *ch, size_t nch) { + int rv; + char abuf[8]; + + if (global.last_x != x - 1 || global.last_y != y) { + if_err_return(rv, send_cursor_if(x, y)); + } + global.last_x = x; + global.last_y = y; + + int i; + for (i = 0; i < (int)nch; i++) { + uint32_t ach = *(ch + i); + int aw = tb_utf8_unicode_to_char(abuf, ach); + if (!ach) { + abuf[0] = ' '; + } + if_err_return(rv, bytebuf_nputs(&global.out, abuf, (size_t)aw)); + } + + return TB_OK; +} + +static int convert_num(uint32_t num, char *buf) { + int i, l = 0; + int ch; + do { + buf[l++] = '0' + (num % 10); + num /= 10; + } while (num); + for (i = 0; i < l / 2; i++) { + ch = buf[i]; + buf[i] = buf[l - 1 - i]; + buf[l - 1 - i] = ch; + } + return l; +} + +static int cell_cmp(struct tb_cell *a, struct tb_cell *b) { + if (a->ch != b->ch || a->fg != b->fg || a->bg != b->bg) { + return 1; + } +#ifdef TB_OPT_EGC + if (a->nech != b->nech) { + return 1; + } else if (a->nech > 0) { // a->nech == b->nech + return memcmp(a->ech, b->ech, a->nech); + } +#endif + return 0; +} + +static int cell_copy(struct tb_cell *dst, struct tb_cell *src) { +#ifdef TB_OPT_EGC + if (src->nech > 0) { + return cell_set(dst, src->ech, src->nech, src->fg, src->bg); + } +#endif + return cell_set(dst, &src->ch, 1, src->fg, src->bg); +} + +static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch, + uintattr_t fg, uintattr_t bg) { + cell->ch = ch ? *ch : 0; + cell->fg = fg; + cell->bg = bg; +#ifdef TB_OPT_EGC + if (nch <= 1) { + cell->nech = 0; + } else { + int rv; + if_err_return(rv, cell_reserve_ech(cell, nch + 1)); + memcpy(cell->ech, ch, nch); + cell->ech[nch] = '\0'; + cell->nech = nch; + } +#else + (void)nch; + (void)cell_reserve_ech; +#endif + return TB_OK; +} + +static int cell_reserve_ech(struct tb_cell *cell, size_t n) { +#ifdef TB_OPT_EGC + if (cell->cech >= n) { + return TB_OK; + } + if (!(cell->ech = tb_realloc(cell->ech, n * sizeof(cell->ch)))) { + return TB_ERR_MEM; + } + cell->cech = n; + return TB_OK; +#else + (void)cell; + (void)n; + return TB_ERR; +#endif +} + +static int cell_free(struct tb_cell *cell) { +#ifdef TB_OPT_EGC + if (cell->ech) { + tb_free(cell->ech); + } +#endif + memset(cell, 0, sizeof(*cell)); + return TB_OK; +} + +static int cellbuf_init(struct cellbuf_t *c, int w, int h) { + c->cells = tb_malloc(sizeof(struct tb_cell) * w * h); + if (!c->cells) { + return TB_ERR_MEM; + } + memset(c->cells, 0, sizeof(struct tb_cell) * w * h); + c->width = w; + c->height = h; + return TB_OK; +} + +static int cellbuf_free(struct cellbuf_t *c) { + if (c->cells) { + int i; + for (i = 0; i < c->width * c->height; i++) { + cell_free(&c->cells[i]); + } + tb_free(c->cells); + } + memset(c, 0, sizeof(*c)); + return TB_OK; +} + +static int cellbuf_clear(struct cellbuf_t *c) { + int rv, i; + uint32_t space = (uint32_t)' '; + for (i = 0; i < c->width * c->height; i++) { + if_err_return(rv, + cell_set(&c->cells[i], &space, 1, global.fg, global.bg)); + } + return TB_OK; +} + +static int cellbuf_get(struct cellbuf_t *c, int x, int y, + struct tb_cell **out) { + if (x < 0 || x >= c->width || y < 0 || y >= c->height) { + *out = NULL; + return TB_ERR_OUT_OF_BOUNDS; + } + *out = &c->cells[(y * c->width) + x]; + return TB_OK; +} + +static int cellbuf_resize(struct cellbuf_t *c, int w, int h) { + int rv; + + int ow = c->width; + int oh = c->height; + + if (ow == w && oh == h) { + return TB_OK; + } + + w = w < 1 ? 1 : w; + h = h < 1 ? 1 : h; + + int minw = (w < ow) ? w : ow; + int minh = (h < oh) ? h : oh; + + struct tb_cell *prev = c->cells; + + if_err_return(rv, cellbuf_init(c, w, h)); + if_err_return(rv, cellbuf_clear(c)); + + int x, y; + for (x = 0; x < minw; x++) { + for (y = 0; y < minh; y++) { + struct tb_cell *src, *dst; + src = &prev[(y * ow) + x]; + if_err_return(rv, cellbuf_get(c, x, y, &dst)); + if_err_return(rv, cell_copy(dst, src)); + } + } + + tb_free(prev); + + return TB_OK; +} + +static int bytebuf_puts(struct bytebuf_t *b, const char *str) { + return bytebuf_nputs(b, str, (size_t)strlen(str)); +} + +static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr) { + int rv; + if_err_return(rv, bytebuf_reserve(b, b->len + nstr + 1)); + memcpy(b->buf + b->len, str, nstr); + b->len += nstr; + b->buf[b->len] = '\0'; + return TB_OK; +} + +static int bytebuf_shift(struct bytebuf_t *b, size_t n) { + if (n > b->len) { + n = b->len; + } + size_t nmove = b->len - n; + memmove(b->buf, b->buf + n, nmove); + b->len -= n; + return TB_OK; +} + +static int bytebuf_flush(struct bytebuf_t *b, int fd) { + if (b->len <= 0) { + return TB_OK; + } + ssize_t write_rv = write(fd, b->buf, b->len); + if (write_rv < 0 || (size_t)write_rv != b->len) { + // Note, errno will be 0 on partial write + global.last_errno = errno; + return TB_ERR; + } + b->len = 0; + return TB_OK; +} + +static int bytebuf_reserve(struct bytebuf_t *b, size_t sz) { + if (b->cap >= sz) { + return TB_OK; + } + size_t newcap = b->cap > 0 ? b->cap : 1; + while (newcap < sz) { + newcap *= 2; + } + char *newbuf; + if (b->buf) { + newbuf = tb_realloc(b->buf, newcap); + } else { + newbuf = tb_malloc(newcap); + } + if (!newbuf) { + return TB_ERR_MEM; + } + b->buf = newbuf; + b->cap = newcap; + return TB_OK; +} + +static int bytebuf_free(struct bytebuf_t *b) { + if (b->buf) { + tb_free(b->buf); + } + memset(b, 0, sizeof(*b)); + return TB_OK; +} + +#endif /* TB_IMPL */ diff --git a/termbox_defs.lua b/termbox_defs.lua new file mode 100644 index 0000000..95ae027 --- /dev/null +++ b/termbox_defs.lua @@ -0,0 +1,93 @@ +TB_KEY_CTRL_TILDE = 0x00; +TB_KEY_CTRL_2 = 0x00; +TB_KEY_CTRL_A = 0x01; +TB_KEY_CTRL_B = 0x02; +TB_KEY_CTRL_C = 0x03; +TB_KEY_CTRL_D = 0x04; +TB_KEY_CTRL_E = 0x05; +TB_KEY_CTRL_F = 0x06; +TB_KEY_CTRL_G = 0x07; +TB_KEY_BACKSPACE = 0x08; +TB_KEY_CTRL_H = 0x08; +TB_KEY_TAB = 0x09; +TB_KEY_CTRL_I = 0x09; +TB_KEY_CTRL_J = 0x0a; +TB_KEY_CTRL_K = 0x0b; +TB_KEY_CTRL_L = 0x0c; +TB_KEY_ENTER = 0x0d; +TB_KEY_CTRL_M = 0x0d; +TB_KEY_CTRL_N = 0x0e; +TB_KEY_CTRL_O = 0x0f; +TB_KEY_CTRL_P = 0x10; +TB_KEY_CTRL_Q = 0x11; +TB_KEY_CTRL_R = 0x12; +TB_KEY_CTRL_S = 0x13; +TB_KEY_CTRL_T = 0x14; +TB_KEY_CTRL_U = 0x15; +TB_KEY_CTRL_V = 0x16; +TB_KEY_CTRL_W = 0x17; +TB_KEY_CTRL_X = 0x18; +TB_KEY_CTRL_Y = 0x19; +TB_KEY_CTRL_Z = 0x1a; +TB_KEY_ESC = 0x1b; +TB_KEY_CTRL_LSQ_BRACKET = 0x1b; +TB_KEY_CTRL_3 = 0x1b; +TB_KEY_CTRL_4 = 0x1c; +TB_KEY_CTRL_BACKSLASH = 0x1c; +TB_KEY_CTRL_5 = 0x1d; +TB_KEY_CTRL_RSQ_BRACKET = 0x1d; +TB_KEY_CTRL_6 = 0x1e; +TB_KEY_CTRL_7 = 0x1f; +TB_KEY_CTRL_SLASH = 0x1f; +TB_KEY_CTRL_UNDERSCORE = 0x1f; +TB_KEY_SPACE = 0x20; +TB_KEY_BACKSPACE2 = 0x7f; +TB_KEY_CTRL_8 = 0x7f; +TB_KEY_F1 = (0xffff - 0); +TB_KEY_F2 = (0xffff - 1); +TB_KEY_F3 = (0xffff - 2); +TB_KEY_F4 = (0xffff - 3); +TB_KEY_F5 = (0xffff - 4); +TB_KEY_F6 = (0xffff - 5); +TB_KEY_F7 = (0xffff - 6); +TB_KEY_F8 = (0xffff - 7); +TB_KEY_F9 = (0xffff - 8); +TB_KEY_F10 = (0xffff - 9 ); +TB_KEY_F11 = (0xffff - 10); +TB_KEY_F12 = (0xffff - 11); +TB_KEY_INSERT = (0xffff - 12); +TB_KEY_DELETE = (0xffff - 13); +TB_KEY_HOME = (0xffff - 14); +TB_KEY_END = (0xffff - 15); +TB_KEY_PGUP = (0xffff - 16); +TB_KEY_PGDN = (0xffff - 17); +TB_KEY_ARROW_UP = (0xffff - 18); +TB_KEY_ARROW_DOWN = (0xffff - 19); +TB_KEY_ARROW_LEFT = (0xffff - 20); +TB_KEY_ARROW_RIGHT = (0xffff - 21); +TB_KEY_BACK_TAB = (0xffff - 22); +TB_KEY_MOUSE_LEFT = (0xffff - 23); +TB_KEY_MOUSE_RIGHT = (0xffff - 24); +TB_KEY_MOUSE_MIDDLE = (0xffff - 25); +TB_KEY_MOUSE_RELEASE = (0xffff - 26); +TB_KEY_MOUSE_WHEEL_UP = (0xffff - 27); +TB_KEY_MOUSE_WHEEL_DOWN = (0xffff - 28); +TB_DEFAULT = 0x0000; +TB_BLACK = 0x0001; +TB_RED = 0x0002; +TB_GREEN = 0x0003; +TB_YELLOW = 0x0004; +TB_BLUE = 0x0005; +TB_MAGENTA = 0x0006; +TB_CYAN = 0x0007; +TB_WHITE = 0x0008; +TB_BOLD = 0x0100; +TB_UNDERLINE = 0x0200; +TB_REVERSE = 0x0400; +TB_ITALIC = 0x0800; +TB_BLINK = 0x1000; +TB_TRUECOLOR_BOLD = 0x01000000; +TB_TRUECOLOR_UNDERLINE = 0x02000000; +TB_TRUECOLOR_REVERSE = 0x04000000; +TB_TRUECOLOR_ITALIC = 0x08000000; +TB_TRUECOLOR_BLINK = 0x10000000; diff --git a/termbox_render.h b/termbox_render.h new file mode 100644 index 0000000..609e0fe --- /dev/null +++ b/termbox_render.h @@ -0,0 +1,26 @@ +#ifndef TERMBOX_RENDER_H +#define TERMBOX_RENDER_H + +#include +#include +#include +#include +#include "termbox.h" +#include "oct_termbox_sprite.h" +#define OCT_MAX_SPRITE_SIZE 101 +#define OCT_NUM_SPRITES 100 + +void oct_render_termbox_sprite(struct oct_tb_sprite* ots) { + char shape_copy[OCT_MAX_SPRITE_SIZE]; + strncpy(shape_copy, ots->shape, OCT_MAX_SPRITE_SIZE-1); + char* line = NULL; + line = strtok(shape_copy, "\n\r"); + int x = ots->x; + int y = ots->y; + + while (line) { + tb_print(x, y++, ots->fg, ots->bg, line); + line = strtok(NULL, "\n\r"); + } +} +#endif diff --git a/test b/test new file mode 100755 index 0000000000000000000000000000000000000000..d2bbb74259f759e1c0634abaefdd6c7bad3034b6 GIT binary patch literal 98888 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 O*Q;B0`uw-Q|NjFfK$?C4 literal 0 HcmV?d00001 diff --git a/test.c b/test.c new file mode 100644 index 0000000..74ed6ba --- /dev/null +++ b/test.c @@ -0,0 +1,27 @@ +#define TB_IMPL +#include "termbox.h" +#include + +int main(int argc, char **argv) { + struct tb_event ev; + int y = 0; + + tb_init(); + + tb_printf(0, y++, TB_GREEN, 0, "hello from termbox"); + tb_printf(0, y++, 0, 0, "width=%d height=%d", tb_width(), tb_height()); + tb_printf(0, y++, 0, 0, "press any key..."); + tb_present(); + + tb_poll_event(&ev); + + y++; + tb_printf(0, y++, 0, 0, "event type=%d key=%d ch=%c", ev.type, ev.key, ev.ch); + tb_printf(0, y++, 0, 0, "press any key to quit..."); + tb_present(); + + tb_poll_event(&ev); + tb_shutdown(); + + return 0; +}