From f37cce374cc9da243fc2babbea9a7051f15db80a Mon Sep 17 00:00:00 2001 From: Raymaekers Luca Date: Thu, 24 Oct 2024 23:10:31 +0200 Subject: Archive code into v1/ directory The code was written too fast and I deemed that it would be better to make a new draft. --- .gitignore | 6 - README.md | 53 - build.sh | 6 - client.c | 265 ---- common.h | 179 --- compile_flags.txt | 5 - recv.c | 57 - send.c | 55 - server.c | 147 --- termbox2.h | 3517 -------------------------------------------------- v1/.gitignore | 7 + v1/README.md | 58 + v1/arena.h | 116 ++ v1/build.sh | 6 + v1/client.c | 259 ++++ v1/common.h | 181 +++ v1/compile_flags.txt | 5 + v1/recv.c | 59 + v1/send.c | 61 + v1/server.c | 148 +++ v1/termbox2.h | 3517 ++++++++++++++++++++++++++++++++++++++++++++++++++ 21 files changed, 4417 insertions(+), 4290 deletions(-) delete mode 100644 .gitignore delete mode 100644 README.md delete mode 100755 build.sh delete mode 100644 client.c delete mode 100644 common.h delete mode 100644 compile_flags.txt delete mode 100644 recv.c delete mode 100644 send.c delete mode 100644 server.c delete mode 100644 termbox2.h create mode 100644 v1/.gitignore create mode 100644 v1/README.md create mode 100644 v1/arena.h create mode 100755 v1/build.sh create mode 100644 v1/client.c create mode 100644 v1/common.h create mode 100644 v1/compile_flags.txt create mode 100644 v1/recv.c create mode 100644 v1/send.c create mode 100644 v1/server.c create mode 100644 v1/termbox2.h diff --git a/.gitignore b/.gitignore deleted file mode 100644 index fd52ccf..0000000 --- a/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -chatty -server -recv -send -tags -tmp diff --git a/README.md b/README.md deleted file mode 100644 index 28d96a2..0000000 --- a/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# Chatty -The idea is the following: -- tcp server that you can send messages to -- history upon connecting -- date of messages sent -- authentication -- encrypted communication (tls?) -- client for reading the messages and sending them at the same time - -# Common -- use memory arena's to manage memory -- manage memory for what if it will not fit - - for just do nothing when the limit is reached - -# Server -- min height & width -- wrapping input -- [ ] history -- [x] max y for new messages and make them scroll -- [x] check resize event -- [x] asynchronously receive/send a message -- [x] send message to all other clients -- [x] fix receiving messages with arbitrary text length -- [x] bug: server copying the bytes correctly - -- rooms -- encryption -- authentication - -# Client -- bug: when having multiple messages and resizing a lot, the output will be in shambles -- bug: when resizing afters sending messages over network it crashes -- bug: all messages using the same buffer for text -- use pointer for add_message -- validation of sent/received messages -- handle disconnection - -# Questions -- will two consecutive sends be read in one recv -- can you recv a message in two messages - -# Message protocol -Version 1 -1 version byte -4 length bytes -12 message_author bytes -- 11 chars + \0 -9 timestamp bytes -- 8chars + \0 -x text bytes -- x bytes + \0 - -The variable text bytes can be calculated by substracting the author and timestamp from the length diff --git a/build.sh b/build.sh deleted file mode 100755 index 39e3f42..0000000 --- a/build.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -set -x -gcc -g -Wall -pedantic -std=c99 -o chatty client.c -gcc -g -Wall -pedantic -std=c99 -o server server.c -gcc -g -Wall -pedantic -std=c99 -o send send.c -gcc -g -Wall -pedantic -std=c99 -o recv recv.c diff --git a/client.c b/client.c deleted file mode 100644 index 7a727af..0000000 --- a/client.c +++ /dev/null @@ -1,265 +0,0 @@ -// Client for chatty - -// initial size for the messages array -#define MESSAGES_SIZE 5 - -// clang-format off -#define TB_IMPL -#include "termbox2.h" -// clang-format on -#include "common.h" - -#include -#include -#include -#include -#include -#include -#include - -enum { FD_SERVER = 0, - FD_TTY, - FD_RESIZE, - FD_MAX }; - -// offset of the input prompt -int curs_offs_x = 2; -int prompt_offs_y = 3; - -// filedescriptor for server -static int serverfd; -// Input message to be send -struct message input = { - .author = USERNAME, - .timestamp = {0}, - .len = 0, -}; -// current amount of messages -int nmessages = 0; -// length of messages array -int messages_size = MESSAGES_SIZE; -// All messages sent and received in order -struct message messages[MESSAGES_SIZE] = {0}; -// incremented each time a new message is printed -int msg_y = 0; - -// Cleans up resources, should called before exiting. -void cleanup(void); -// Displays an error message msg, followed by the errno variable and exits exeuction. -void err_exit(const char *msg); -// Display the welcome ui screen containing the prompt and messages array. -void scren_welcome(void); -// Append msg to the messages array. Returns -1 if there was no space in the messages array -// otherwise returns 0 on success. -u8 message_add(struct message msg); - -void cleanup(void) -{ - tb_shutdown(); - if (serverfd) - if (close(serverfd)) - writef("Error while closing server socket. errno: %d\n", errno); -} - -// panic -void err_exit(const char *msg) -{ - cleanup(); - writef("%s errno: %d\n", msg, errno); - _exit(1); -} - -void screen_welcome(void) -{ - tb_set_cursor(curs_offs_x, global.height - prompt_offs_y); - tb_print(0, global.height - prompt_offs_y, 0, 0, ">"); - - // if there is not enough space to fit all messages, skip the n first messages of the array. - int skip = 0; - int lines_available = global.height - prompt_offs_y - 1; // pad by 1 from prompt - if (lines_available - nmessages < 0) - skip = nmessages - lines_available; - for (msg_y = skip; msg_y < nmessages; msg_y++) { - tb_printf(0, msg_y - skip, 0, 0, "%s [%s]: %s", messages[msg_y].timestamp, messages[msg_y].author, messages[msg_y].text); - } -} - -u8 message_add(struct message msg) -{ - if (nmessages == messages_size) { - return -1; - } - - int i; - messages[nmessages].text = input.text; - ; - messages[nmessages].text[input.len] = 0; - messages[nmessages].len = input.len; - for (i = 0; (messages[nmessages].timestamp[i] = msg.timestamp[i]); i++) - ; - messages[nmessages].timestamp[i] = 0; - for (i = 0; (messages[nmessages].author[i] = msg.author[i]); i++) - ; - - nmessages++; - msg_y++; - - return 0; -} - -int main(void) -{ - // current event - struct tb_event ev; - // time for a new entered message - time_t now; - // localtime of new sent message - struct tm *ltime; - char buf[MESSAGE_MAX]; - input.text = buf; - - int serverfd, ttyfd, resizefd; - struct message msg_recv = {0}; - const struct sockaddr_in address = { - AF_INET, - htons(PORT), - {0}, - }; - - tb_init(); - bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); - - screen_welcome(); - tb_present(); - - tb_get_fds(&ttyfd, &resizefd); - serverfd = socket(AF_INET, SOCK_STREAM, 0); - - struct pollfd fds[FD_MAX] = { - {serverfd, POLLIN, 0}, // FD_SERVER - { ttyfd, POLLIN, 0}, // FD_TTY - {resizefd, POLLIN, 0}, // FD_RESIZE - }; - - if (connect(serverfd, (struct sockaddr *)&address, sizeof(address))) - err_exit("Error while connecting."); - - for (;;) { - if (poll(fds, FD_MAX, 50000) == -1) { - // check if it was a resize event that interrupted the system call - if (errno == EINTR) { - tb_peek_event(&ev, 80); - if (ev.type != TB_EVENT_RESIZE) - err_exit("Error while polling."); - else { - tb_clear(); - screen_welcome(); - } - } - } - - if (fds[FD_TTY].revents & POLLIN) { - tb_poll_event(&ev); - switch (ev.key) { - // exit - case TB_KEY_CTRL_C: - case TB_KEY_CTRL_D: - case TB_KEY_ESC: - goto exit_loop; - // remove line till cursor - case TB_KEY_CTRL_U: - while (global.cursor_x > curs_offs_x) { - global.cursor_x--; - tb_print(global.cursor_x, global.cursor_y, 0, 0, " "); - } - tb_set_cursor(curs_offs_x, global.cursor_y); - input.len = 0; - break; - // send message - case TB_KEY_CTRL_M: - if (input.len <= 0) - break; - while (global.cursor_x > curs_offs_x) { - global.cursor_x--; - tb_print(global.cursor_x, global.cursor_y, 0, 0, " "); - } - tb_set_cursor(curs_offs_x, global.cursor_y); - - // zero terminate - input.text[input.len] = 0; - - // print new message - time(&now); - ltime = localtime(&now); - strftime(input.timestamp, sizeof(input.timestamp), "%H:%M:%S", ltime); - - message_add(input); - - if (send(serverfd, &input, sizeof(input), 0) == -1) - err_exit("Error while sending message."); - - // reset buffer - input.len = 0; - - // update the screen - // NOTE: kind of wasteful cause we should only display new message - tb_clear(); - screen_welcome(); - - break; - // remove word - case TB_KEY_CTRL_W: - // Delete consecutive space - while (input.text[input.len - 1] == ' ' && global.cursor_x > curs_offs_x) { - global.cursor_x--; - input.len--; - tb_print(global.cursor_x, global.cursor_y, 0, 0, " "); - } - // Delete until next non-space - while (input.text[input.len - 1] != ' ' && global.cursor_x > curs_offs_x) { - global.cursor_x--; - input.len--; - tb_print(global.cursor_x, global.cursor_y, 0, 0, " "); - } - input.text[input.len] = 0; - break; - } - - // append pressed character to input.text - // TODO: wrap instead, allocate more ram for the message instead - if (ev.ch > 0 && input.len < MESSAGE_MAX && input.len < global.width - 3 - 1) { - tb_printf(global.cursor_x, global.cursor_y, 0, 0, "%c", ev.ch); - global.cursor_x++; - - input.text[input.len++] = ev.ch; - } - - } else if (fds[FD_SERVER].revents & POLLIN) { - int nrecv = recv(serverfd, &msg_recv, sizeof(struct message), 0); - - if (nrecv == 0) { - // Server closed - // TODO: error message like (disconnected) - break; - } else if (nrecv == -1) { - err_exit("Error while receiveiving from server."); - } - message_add(msg_recv); - tb_clear(); - screen_welcome(); - - } else if (fds[FD_RESIZE].revents & POLLIN) { - tb_poll_event(&ev); - if (ev.type == TB_EVENT_RESIZE) { - tb_clear(); - screen_welcome(); - } - } - - tb_present(); - } -exit_loop:; - - cleanup(); - return 0; -} diff --git a/common.h b/common.h deleted file mode 100644 index e220735..0000000 --- a/common.h +++ /dev/null @@ -1,179 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#define PORT 9983 -// max buffer size sent over network -// TODO: choose a better size -#define BUF_MAX 256 -// max size for a message sent -#define MESSAGE_MAX 256 -// max length of author field -#define MESSAGE_AUTHOR_LEN 12 -// max length of timestamp field -#define MESSAGE_TIMESTAMP_LEN 9 -// current user's name -#define USERNAME "Jef Koek" - -typedef uint8_t u8; -typedef uint16_t u16; -typedef uint32_t u32; - -// To serialize the text that could be arbitrary length the lenght is encoded after the author -// string and before the text. -struct message { - char author[MESSAGE_AUTHOR_LEN]; - char timestamp[MESSAGE_TIMESTAMP_LEN]; // HH:MM:SS - u16 len; // length of the text including null terminator - char *text; -}; - -// printf without buffering using write syscall, works when using sockets -void writef(char *format, ...); - -u16 str_len(char *str); -void str_cpy(char *to, char *from); - -// save the message msg to file in binary format, returns zero on success, returns 1 if the msg.text -// was empty which should not be allowed. -u8 save_message(struct message *msg, FILE *f); -// load the message msg from file f, returns zero on success, returns 1 if the msg.text -// was empty which should not be allowed. -u8 load_message(struct message *msg, FILE *f); - -// Send a stream of bytes containing msg -// return -1 if send() returns -1. Otherwise returns number of bytes sent. -u32 send_message(struct message msg, u32 serverfd); -// Receives a stream of bytes and populates msg with the data received -// if recv() returns 0 or -1 it will return early and return 0 or -1 accordingly. -// Otherwise returns the number of bytes received -u32 receive_message(struct message *msg, u32 clientfd); - -void writef(char *format, ...) -{ - char buf[255 + 1]; - va_list args; - va_start(args, format); - - vsnprintf(buf, sizeof(buf), format, args); - va_end(args); - - int n = 0; - while (*(buf + n) != 0) - n++; - write(0, buf, n); -} - -// Returns the length of the string plus the null terminator -u16 str_len(char *str) -{ - if (*str == 0) - return 0; - - u16 i = 0; - while (str[i]) - i++; - - return i + 1; -} - -void str_cpy(char *to, char *from) -{ - while ((*to++ = *from++)) - ; -} - -// Save msg to file f -// Returns 0 on success, returns 1 if msg->text is NULL, returns 2 if mfg->len is 0 -u8 save_message(struct message *msg, FILE *f) -{ - if (msg->text == NULL) { - return 1; - } else if (msg->len == 0) - return 2; - - fwrite(&msg->timestamp, sizeof(*msg->timestamp) * MESSAGE_TIMESTAMP_LEN, 1, f); - fwrite(&msg->author, sizeof(*msg->author) * MESSAGE_AUTHOR_LEN, 1, f); - fwrite(&msg->len, sizeof(msg->len), 1, f); - fputs(msg->text, f); - - return 0; -} - -u8 load_message(struct message *msg, FILE *f) -{ - fread(msg, sizeof(*msg->timestamp) * MESSAGE_TIMESTAMP_LEN + sizeof(*msg->author) * MESSAGE_AUTHOR_LEN, 1, f); - u16 len; - fread(&len, sizeof(len), 1, f); - if (len == 0) { - // TODO: Error: empty message should not be allowed - // empty message - msg->text = ""; - return 1; - } - char txt[len]; - fgets(txt, len, f); - msg->text = txt; - - return 0; -} - -u32 send_message(struct message msg, u32 serverfd) -{ - // stream length : message author : message timestamp : message text + \0 - u32 buf_len = sizeof(buf_len) + MESSAGE_AUTHOR_LEN + MESSAGE_TIMESTAMP_LEN + msg.len; - char buf[buf_len]; - u32 offset; - - memcpy(buf, &buf_len, sizeof(buf_len)); - offset = sizeof(buf_len); - memcpy(buf + offset, msg.author, MESSAGE_AUTHOR_LEN); - offset += MESSAGE_AUTHOR_LEN; - memcpy(buf + offset, msg.timestamp, MESSAGE_TIMESTAMP_LEN); - offset += MESSAGE_TIMESTAMP_LEN; - memcpy(buf + offset, msg.text, msg.len); - - u32 n = send(serverfd, &buf, buf_len, 0); - if (n == -1) - return n; - - writef("%d bytes sent.\n", n); - return n; -} - -u32 receive_message(struct message *msg, u32 clientfd) -{ - // must all be of the s - u32 nrecv, buf_len; - // limit on what can be received with recv() - u32 buf_size = 20; - // temporary buffer to receive message data over a stream - char recv_buf[BUF_MAX]; - - nrecv = recv(clientfd, recv_buf, buf_size, 0); - if (nrecv == 0 || nrecv == -1) - return nrecv; - - memcpy(&buf_len, recv_buf, sizeof(buf_len)); - - u32 i = 0; - while (nrecv < buf_len) { - // advance the copying by the amounts of bytes received each time - i = recv(clientfd, recv_buf + nrecv, buf_size, 0); - if (i == 0 || i == -1) - return nrecv; - nrecv += i; - } - - struct message received = {0}; - memcpy(&received, recv_buf + sizeof(buf_len), MESSAGE_AUTHOR_LEN + MESSAGE_TIMESTAMP_LEN); - received.text = recv_buf + sizeof(buf_len) + MESSAGE_AUTHOR_LEN + MESSAGE_TIMESTAMP_LEN; - received.len = buf_len - sizeof(buf_len) - MESSAGE_AUTHOR_LEN - MESSAGE_TIMESTAMP_LEN; - - // assume clientfd is serverfd + 1; - writef("Received %d bytes from client(%d): %s [%s] (%d)%s\n", nrecv, clientfd - 3, received.timestamp, received.author, received.len, received.text); - return nrecv; -} diff --git a/compile_flags.txt b/compile_flags.txt deleted file mode 100644 index 1a62790..0000000 --- a/compile_flags.txt +++ /dev/null @@ -1,5 +0,0 @@ --Wall --Werror --pedantic --std=c99 --O3 diff --git a/recv.c b/recv.c deleted file mode 100644 index e4433ae..0000000 --- a/recv.c +++ /dev/null @@ -1,57 +0,0 @@ -// Minimal server implementation for probing out things - -#include "common.h" -#include -#include -#include -#include -#include - -int main(void) -{ - u32 serverfd, clientfd; - u8 on = 1; - - const struct sockaddr_in address = { - AF_INET, - htons(PORT), - {0}, - }; - - serverfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)); - if (bind(serverfd, (struct sockaddr *)&address, sizeof(address))) - return 1; - - listen(serverfd, 256); - - clientfd = accept(serverfd, 0, 0); - - struct pollfd fds[1] = { - {clientfd, POLLIN, 0}, - }; - - for (;;) { - int ret = poll(fds, 1, 50000); - if (ret == -1) - return 2; - - if (fds[0].revents & POLLIN) { - u8 recv_buf[BUF_MAX]; - u32 nrecv = recv(clientfd, recv_buf, sizeof(recv_buf), 0); - - writef("client(%d): %d bytes received.\n", clientfd, nrecv); - if (nrecv == -1) { - return errno; - } else if (nrecv == 0) { - writef("client(%d): disconnected.\n", clientfd); - fds[0].fd = -1; - fds[0].revents = 0; - close(clientfd); - return 0; - } - } - } - - return 0; -} diff --git a/send.c b/send.c deleted file mode 100644 index cea4c2d..0000000 --- a/send.c +++ /dev/null @@ -1,55 +0,0 @@ -// minimal client implementation -#include "common.h" -#include -#include -#include -#include -#include -#include - -u32 serverfd; - -// NOTE: Errno could be unset and contain an error for a previous command -void debug_panic(const char *msg) -{ - writef("%s errno: %d\n", msg, errno); - raise(SIGINT); -} - -// get current time in timestamp string -void timestamp(char timestamp[MESSAGE_TIMESTAMP_LEN]) -{ - time_t now; - struct tm *ltime; - time(&now); - ltime = localtime(&now); - strftime(timestamp, MESSAGE_TIMESTAMP_LEN, "%H:%M:%S", ltime); -} - -int main(void) -{ - serverfd = socket(AF_INET, SOCK_STREAM, 0); - if (serverfd == -1) - debug_panic("Error while getting socket."); - - const struct sockaddr_in address = { - AF_INET, - htons(PORT), - {0}, - }; - - if (connect(serverfd, (struct sockaddr *)&address, sizeof(address))) - debug_panic("Error while connecting."); - - struct message input = { - .author = "Friendship", - }; - input.text = "Hello from send"; - input.len = str_len(input.text); - printf("input.len: %d\n", input.len); - timestamp(input.timestamp); - - send_message(input, serverfd); - - return 0; -} diff --git a/server.c b/server.c deleted file mode 100644 index 1a24bcc..0000000 --- a/server.c +++ /dev/null @@ -1,147 +0,0 @@ -// Server for chatty -#include "common.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#define MAX_CONNECTIONS 5 -#define FD_MAX MAX_CONNECTIONS + 1 - -static const char *filename = "history.dat"; - -enum { FD_SERVER = 0 }; -u32 serverfd; - -void err_exit(const char *msg) -{ - if (serverfd) - if (close(serverfd)) - writef("Error while closing server socket. errno: %d\n", errno); - fprintf(stderr, "%s errno: %d\n", msg, errno); - _exit(1); -} - -int main(void) -{ - u32 clientfd; - u16 nclient = 0; - u32 on = 1; - struct message msg_recv = {0}; - - serverfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP); - if (setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on))) - err_exit("Error while setting socket option."); - - const struct sockaddr_in address = { - AF_INET, - htons(PORT), - {0}, - }; - - if (bind(serverfd, (struct sockaddr *)&address, sizeof(address))) - err_exit("Error while binding."); - - if (listen(serverfd, BUF_MAX)) - err_exit("Error while listening"); - - writef("Listening on localhost:%d\n", PORT); - - struct pollfd fds[MAX_CONNECTIONS + 1] = { - {serverfd, POLLIN, 0}, // FD_SERVER - { -1, POLLIN, 0}, - { -1, POLLIN, 0}, - { -1, POLLIN, 0}, - { -1, POLLIN, 0}, - { -1, POLLIN, 0}, - }; - - for (;;) { - u32 ret = poll(fds, FD_MAX, 50000); - if (ret == -1) - err_exit("Error while polling"); - else if (ret == 0) { - writef("polling timed out.\n"); - continue; - } - - // New client tries to connect to serverfd - if (fds[FD_SERVER].revents & POLLIN) { - clientfd = accept(serverfd, NULL, NULL); - - // When MAX_CONNECTIONS is reached, close new clients trying to connect. - if (nclient == MAX_CONNECTIONS) { - writef("Max connections reached.\n"); - if (send(clientfd, 0, 0, 0) == -1) - err_exit("Error while sending EOF to client socket."); - if (shutdown(clientfd, SHUT_RDWR)) - err_exit("Error while shutting down client socket."); - if (close(clientfd)) - err_exit("Error while closing client socket."); - } else if (clientfd != -1) { - nclient++; - - // get a new available spot in the fds array - u32 i; - for (i = 0; i < MAX_CONNECTIONS; i++) - if (fds[i].fd == -1) - break; - fds[i].fd = clientfd; - writef("New client: %d, %d\n", i, clientfd); - - } else { - writef("Could not accept client errno: %d\n", errno); - } - } - - // Check for events on connected clients - for (u32 i = 1; i <= nclient; i++) { - if (!(fds[i].revents & POLLIN)) - continue; - - u32 nrecv; - - clientfd = fds[i].fd; - - nrecv = receive_message(&msg_recv, clientfd); - if (nrecv == 0) { - printf("client %d disconnected.\n", i); - fds[i].fd = -1; - fds[i].revents = 0; - if (shutdown(clientfd, SHUT_RDWR)) - err_exit("Error while shutting down client %d socket."); - if (close(clientfd)) - err_exit("Error while cloing client socket."); - nclient--; - break; - } else if (nrecv == -1) { - // TODO: this can happen when connect is reset by pear - err_exit("Error while receiving from client socket."); - } - - // TODO: - for (u32 j = 1; j <= nclient; j++) { - // skip the client that sent the message - if (j == i) - continue; - if (send(fds[j].fd, &msg_recv, nrecv, 0) == -1) - printf("Error while sendig message to client %d. errno: %d\n", j, errno); - else - printf("Retransmitted message to client %d.\n", j); - } - - // // TODO: Serialize received message - // FILE *f = fopen(filename, "wb"); - // save_message(&msg_recv, f); - // fclose(f); - // // return 0; - - } - } - - return 0; -} diff --git a/termbox2.h b/termbox2.h deleted file mode 100644 index 265cdab..0000000 --- a/termbox2.h +++ /dev/null @@ -1,3517 +0,0 @@ -/* -MIT License - -Copyright (c) 2010-2020 nsf - 2015-2024 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_INCL -#define TERMBOX_H_INCL - -#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 -#include - -#ifdef PATH_MAX -#define TB_PATH_MAX PATH_MAX -#else -#define TB_PATH_MAX 4096 -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -// __ffi_start - -#define TB_VERSION_STR "2.5.0-dev" - -/* The following compile-time options are supported: - * - * TB_OPT_ATTR_W: Integer width of fg and bg attributes. Valid values - * (assuming system support) are 16, 32, and 64. (See - * uintattr_t). 32 or 64 enables output mode - * TB_OUTPUT_TRUECOLOR. 64 enables additional style - * attributes. (See tb_set_output_mode.) Larger values - * consume more memory in exchange for more features. - * Defaults to 16. - * - * TB_OPT_EGC: If set, enable extended grapheme cluster support - * (tb_extend_cell, tb_set_cell_ex). Consumes more memory. - * Defaults off. - * - * TB_OPT_PRINTF_BUF: Write buffer size for printf operations. Represents the - * largest string that can be sent in one call to tb_print* - * and tb_send* functions. Defaults to 4096. - * - * TB_OPT_READ_BUF: Read buffer size for tty reads. Defaults to 64. - * - * TB_OPT_TRUECOLOR: Deprecated. Sets TB_OPT_ATTR_W to 32 if not already set. - */ - -#if defined(TB_LIB_OPTS) || 0 // __tb_lib_opts -/* Ensure consistent compile-time options when using as a shared library */ -#undef TB_OPT_ATTR_W -#undef TB_OPT_EGC -#undef TB_OPT_PRINTF_BUF -#undef TB_OPT_READ_BUF -#define TB_OPT_ATTR_W 64 -#define TB_OPT_EGC -#endif - -/* Ensure sane `TB_OPT_ATTR_W` (16, 32, or 64) */ -#if defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 16 -#elif defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 32 -#elif defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 64 -#else -#undef TB_OPT_ATTR_W -#if defined TB_OPT_TRUECOLOR // Deprecated. Back-compat for old flag. -#define TB_OPT_ATTR_W 32 -#else -#define TB_OPT_ATTR_W 16 -#endif -#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 caps */ -/* BEGIN codegen h */ -/* Produced by ./codegen.sh on Tue, 03 Sep 2024 04:17:47 +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_DIM 36 -#define TB_CAP_INVISIBLE 37 -#define TB_CAP__COUNT 38 -/* 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" -#define TB_HARDCAP_STRIKEOUT "\x1b[9m" -#define TB_HARDCAP_UNDERLINE_2 "\x1b[21m" -#define TB_HARDCAP_OVERLINE "\x1b[53m" - -/* 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 - -#if TB_OPT_ATTR_W == 16 -#define TB_BOLD 0x0100 -#define TB_UNDERLINE 0x0200 -#define TB_REVERSE 0x0400 -#define TB_ITALIC 0x0800 -#define TB_BLINK 0x1000 -#define TB_HI_BLACK 0x2000 -#define TB_BRIGHT 0x4000 -#define TB_DIM 0x8000 -#define TB_256_BLACK TB_HI_BLACK // `TB_256_BLACK` is deprecated -#else -// `TB_OPT_ATTR_W` is 32 or 64 -#define TB_BOLD 0x01000000 -#define TB_UNDERLINE 0x02000000 -#define TB_REVERSE 0x04000000 -#define TB_ITALIC 0x08000000 -#define TB_BLINK 0x10000000 -#define TB_HI_BLACK 0x20000000 -#define TB_BRIGHT 0x40000000 -#define TB_DIM 0x80000000 -#define TB_TRUECOLOR_BOLD TB_BOLD // `TB_TRUECOLOR_*` is deprecated -#define TB_TRUECOLOR_UNDERLINE TB_UNDERLINE -#define TB_TRUECOLOR_REVERSE TB_REVERSE -#define TB_TRUECOLOR_ITALIC TB_ITALIC -#define TB_TRUECOLOR_BLINK TB_BLINK -#define TB_TRUECOLOR_BLACK TB_HI_BLACK -#endif - -#if TB_OPT_ATTR_W == 64 -#define TB_STRIKEOUT 0x0000000100000000 -#define TB_UNDERLINE_2 0x0000000200000000 -#define TB_OVERLINE 0x0000000400000000 -#define TB_INVISIBLE 0x0000000800000000 -#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 -#if TB_OPT_ATTR_W >= 32 -#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 - -/* Deprecated. 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 - -#if TB_OPT_ATTR_W == 64 -typedef uint64_t uintattr_t; -#elif TB_OPT_ATTR_W == 32 -typedef uint32_t uintattr_t; -#else // 16 -typedef uint16_t uintattr_t; -#endif - -/* A cell in a 2d grid representing the terminal screen. - * - * The terminal screen is represented as 2d array of cells. The structure is - * optimized for dealing with single-width (`wcwidth==1`) Unicode codepoints, - * however some support for grapheme clusters (e.g., combining diacritical - * marks) and wide codepoints (e.g., Hiragana) is provided through `ech`, - * `nech`, and `cech` via `tb_set_cell_ex`. `ech` is only valid when `nech>0`, - * otherwise `ch` is used. - * - * For non-single-width codepoints, 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. Callers may use - * `tb_set_cell_ex` or `tb_print*` to render `N==0` combining - * characters. - * - * 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` codepoint, 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 codepoint - uintattr_t fg; // bitwise foreground attributes - uintattr_t bg; // bitwise background attributes -#ifdef TB_OPT_EGC - uint32_t *ech; // a grapheme cluster of Unicode codepoints, 0-terminated - size_t nech; // num elements in ech, 0 means use ch instead of ech - size_t cech; // num elements allocated for 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) and `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` and `h` - * - * when `TB_EVENT_MOUSE`: `key` (`TB_KEY_MOUSE_*`), `x`, and `y` - */ -struct tb_event { - uint8_t type; // one of `TB_EVENT_*` constants - uint8_t mod; // bitwise `TB_MOD_*` constants - uint16_t key; // one of `TB_KEY_*` constants - uint32_t ch; // a Unicode codepoint - int32_t w; // resize width - int32_t h; // resize height - int32_t x; // mouse x - int32_t y; // mouse y -}; - -/* Initialize 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 `tb_shutdown`. - */ -int tb_init(void); -int tb_init_file(const char *path); -int tb_init_fd(int ttyfd); -int tb_init_rwfd(int rfd, int wfd); -int tb_shutdown(void); - -/* Return 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` calls. Both dimensions have an unspecified - * negative value when called before `tb_init` or after `tb_shutdown`. - */ -int tb_width(void); -int tb_height(void); - -/* Clear the internal back buffer using `TB_DEFAULT` or the attributes set by - * `tb_set_clear_attrs`. - */ -int tb_clear(void); -int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg); - -/* Synchronize the internal back buffer with the terminal by writing to tty. */ -int tb_present(void); - -/* Clear the internal front buffer effectively forcing a complete re-render of - * the back buffer to the tty. It is not necessary to call this under normal - * circumstances. */ -int tb_invalidate(void); - -/* Set the position of the cursor. Upper-left cell is (0, 0). */ -int tb_set_cursor(int cx, int cy); -int tb_hide_cursor(void); - -/* 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). - * - * Calling `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 codepoint to `tb_cell.ech`. - * - * Non-printable (`iswprint(3)`) codepoints are replaced with `U+FFFD` at render - * time. - */ -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); - -/* Set 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` is used. If for some reason you've decided to use - * `TB_INPUT_ESC | TB_INPUT_ALT`, it will behave as if only `TB_INPUT_ESC` was - * selected. - * - * If mode is `TB_INPUT_CURRENT`, return the current input mode. - * - * The default input mode is `TB_INPUT_ESC`. - */ -int tb_set_input_mode(int mode); - -/* Set the output mode. Termbox has multiple output modes: - * - * 1. `TB_OUTPUT_NORMAL` => [0..8] - * - * This mode provides 8 different colors: - * `TB_BLACK`, `TB_RED`, `TB_GREEN`, `TB_YELLOW`, - * `TB_BLUE`, `TB_MAGENTA`, `TB_CYAN`, `TB_WHITE` - * - * Plus `TB_DEFAULT` which skips sending a color code (i.e., uses the - * terminal's default color). - * - * Colors (including `TB_DEFAULT`) may be bitwise OR'd with attributes: - * `TB_BOLD`, `TB_UNDERLINE`, `TB_REVERSE`, `TB_ITALIC`, `TB_BLINK`, - * `TB_BRIGHT`, `TB_DIM` - * - * The following style attributes are also available if compiled with - * `TB_OPT_ATTR_W` set to 64: - * `TB_STRIKEOUT`, `TB_UNDERLINE_2`, `TB_OVERLINE`, `TB_INVISIBLE` - * - * As in all modes, the value 0 is interpreted as `TB_DEFAULT` for - * convenience. - * - * Some notes: `TB_REVERSE` and `TB_BRIGHT` can be applied as either `fg` or - * `bg` attributes for the same effect. The rest of the attributes apply to - * `fg` 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] + `TB_HI_BLACK` - * - * In this mode you get 256 distinct colors (plus default): - * 0x00 (1): `TB_DEFAULT` - * `TB_HI_BLACK` (1): `TB_BLACK` in `TB_OUTPUT_NORMAL` - * 0x01..0x07 (7): the next 7 colors as in `TB_OUTPUT_NORMAL` - * 0x08..0x0f (8): bright versions of the above - * 0x10..0xe7 (216): 216 different colors - * 0xe8..0xff (24): 24 different shades of gray - * - * All `TB_*` style attributes except `TB_BRIGHT` may be bitwise OR'd as in - * `TB_OUTPUT_NORMAL`. - * - * Note `TB_HI_BLACK` must be used for black, as 0x00 represents default. - * - * 3. `TB_OUTPUT_216` => [0..216] - * - * This mode supports the 216-color range of `TB_OUTPUT_256` only, but you - * don't need to provide an offset: - * 0x00 (1): `TB_DEFAULT` - * 0x01..0xd8 (216): 216 different colors - * - * 4. `TB_OUTPUT_GRAYSCALE` => [0..24] - * - * This mode supports the 24-color range of `TB_OUTPUT_256` only, but you - * don't need to provide an offset: - * 0x00 (1): `TB_DEFAULT` - * 0x01..0x18 (24): 24 different shades of gray - * - * 5. `TB_OUTPUT_TRUECOLOR` => [0x000000..0xffffff] + `TB_HI_BLACK` - * - * This mode provides 24-bit color on supported terminals. The format is - * 0xRRGGBB. - * - * All `TB_*` style attributes except `TB_BRIGHT` may be bitwise OR'd as in - * `TB_OUTPUT_NORMAL`. - * - * Note `TB_HI_BLACK` must be used for black, as 0x000000 represents default. - * - * To use the terminal default color (i.e., to not send an escape code), pass - * `TB_DEFAULT`. For convenience, the value 0 is interpreted as `TB_DEFAULT` in - * all modes. - * - * Note, cell attributes persist after switching output modes. Any translation - * between, for example, `TB_OUTPUT_NORMAL`'s `TB_RED` and - * `TB_OUTPUT_TRUECOLOR`'s 0xff0000 must be performed by the caller. Also note - * that cells previously rendered in one mode may persist unchanged until the - * front buffer is cleared (such as after a resize event) at which point it will - * be re-interpreted and flushed according to the current mode. Callers may - * invoke `tb_invalidate` if it is desirable to immediately re-interpret and - * flush the entire screen according to the current mode. - * - * 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. The same - * advice applies to style attributes. - * - * If mode is `TB_OUTPUT_CURRENT`, return the current output mode. - * - * The default output mode is `TB_OUTPUT_NORMAL`. - */ -int tb_set_output_mode(int mode); - -/* Wait for an event up to `timeout_ms` milliseconds and populate `event` 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 may elect to 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(2)`, `select(2)`, etc. - * externally. Callers must invoke `tb_poll_event` or `tb_peek_event` if - * fds become readable. */ -int tb_get_fds(int *ttyfd, int *resizefd); - -/* Print and printf functions. Specify param `out_w` to determine width of - * printed string. Strings are interpreted as UTF-8. - * - * Non-printable characters (`iswprint(3)`) and truncated UTF-8 byte sequences - * are replaced with U+FFFD. - * - * Newlines (`\n`) are supported with the caveat that `out_w` will return the - * width of the string as if it were on a single line. - * - * If the starting coordinate is out of bounds, `TB_ERR_OUT_OF_BOUNDS` is - * returned. If the starting coordinate is in bounds, but goes out of bounds, - * then the out-of-bounds portions of the string are ignored. - * - * For finer control, use `tb_set_cell`. - */ -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, ...); - -/* Deprecated. Set custom callbacks. `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 *)); - -/* Return byte length of codepoint given first byte of UTF-8 sequence (1-6). */ -int tb_utf8_char_length(char c); - -/* Convert UTF-8 null-terminated byte sequence to UTF-32 codepoint. - * - * If `c` is an empty C string, return 0. `out` is left unchanged. - * - * If a null byte is encountered in the middle of the codepoint, return a - * negative number indicating how many bytes were processed. `out` is left - * unchanged. - * - * Otherwise, return byte length of codepoint (1-6). - */ -int tb_utf8_char_to_unicode(uint32_t *out, const char *c); - -/* Convert UTF-32 codepoint to UTF-8 null-terminated byte sequence. - * - * `out` must be char[7] or greater. Return byte length of codepoint (1-6). - */ -int tb_utf8_unicode_to_char(char *out, uint32_t c); - -/* Library utility functions */ -int tb_last_errno(void); -const char *tb_strerror(int err); -struct tb_cell *tb_cell_buffer(void); // Deprecated -int tb_has_truecolor(void); -int tb_has_egc(void); -int tb_attr_width(void); -const char *tb_version(void); - -/* Deprecation notice! - * - * The following will be removed in version 3.x (ABI version 3): - * - * TB_256_BLACK (use TB_HI_BLACK) - * TB_OPT_TRUECOLOR (use TB_OPT_ATTR_W) - * TB_TRUECOLOR_BOLD (use TB_BOLD) - * TB_TRUECOLOR_UNDERLINE (use TB_UNDERLINE) - * TB_TRUECOLOR_REVERSE (use TB_REVERSE) - * TB_TRUECOLOR_ITALIC (use TB_ITALICe) - * TB_TRUECOLOR_BLINK (use TB_BLINK) - * TB_TRUECOLOR_BLACK (use TB_HI_BLACK) - * tb_cell_buffer - * tb_set_func - * TB_FUNC_EXTRACT_PRE - * TB_FUNC_EXTRACT_POST - */ - -#ifdef __cplusplus -} -#endif - -#endif // TERMBOX_H_INCL - -#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 *); - char errbuf[1024]; -}; - -static struct tb_global_t global = {0}; - -/* BEGIN codegen c */ -/* Produced by ./codegen.sh on Tue, 03 Sep 2024 04:17:48 +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) - 30, // dim (TB_CAP_DIM) - 32, // invis (TB_CAP_INVISIBLE) -}; - -// 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) - "\033[2m", // dim (TB_CAP_DIM) - "\033[8m", // invis (TB_CAP_INVISIBLE) -}; - -// 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\011", // 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) - "\033[2m", // dim (TB_CAP_DIM) - "", // invis (TB_CAP_INVISIBLE) -}; - -// 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) - "\033[2m", // dim (TB_CAP_DIM) - "", // invis (TB_CAP_INVISIBLE) -}; - -// 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) - "", // dim (TB_CAP_DIM) - "", // invis (TB_CAP_INVISIBLE) -}; - -// 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) - "", // dim (TB_CAP_DIM) - "", // invis (TB_CAP_INVISIBLE) -}; - -// 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) - "", // dim (TB_CAP_DIM) - "", // invis (TB_CAP_INVISIBLE) -}; - -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(void); -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(void); -static int init_term_caps(void); -static int init_cap_trie(void); -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(void); -static int send_init_escape_codes(void); -static int send_clear(void); -static int update_term_size(void); -static int update_term_size_via_esc(void); -static int init_cellbuf(void); -static int tb_deinit(void); -static int load_terminfo(void); -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(void); -static int load_builtin_caps(void); -static const char *get_terminfo_string(int16_t str_offsets_pos, - int16_t str_offsets_len, 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(void); -static void handle_resize(int sig); -static int send_attr(uintattr_t fg, uintattr_t bg); -static int send_sgr(uint32_t fg, uint32_t bg, int fg_is_default, - int bg_is_default); -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_in_bounds(struct cellbuf_t *c, int x, int y); -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(void) { - 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(void) { - if_not_init_return(); - tb_deinit(); - return TB_OK; -} - -int tb_width(void) { - if_not_init_return(); - return global.width; -} - -int tb_height(void) { - if_not_init_return(); - return global.height; -} - -int tb_clear(void) { - 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(void) { - 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 - // wcwidth simply returns -1 on overflow of wchar_t - w = wcwidth((wchar_t)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)) { - // Not enough room for wide char, send spaces - 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); - } - - // When wcwidth>1, we need to advance the cursor by more - // than 1, thereby skipping some cells. Set these skipped - // cells to an invalid codepoint in the front buffer, so - // that if this cell is later replaced by a wcwidth==1 char, - // we'll get a cell_cmp diff for the skipped cells and - // properly re-render. - for (i = 1; i < w; i++) { - struct tb_cell *front_wide; - uint32_t invalid = -1; - if_err_return(rv, - cellbuf_get(&global.front, x + i, y, &front_wide)); - if_err_return(rv, - cell_set(front_wide, &invalid, 1, -1, -1)); - } - } - } - 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_invalidate(void) { - int rv; - if_not_init_return(); - if_err_return(rv, resize_cellbufs()); - 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(void) { - 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) { - 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: -#if TB_OPT_ATTR_W >= 32 - case TB_OUTPUT_TRUECOLOR: -#endif - global.last_fg = ~global.fg; - global.last_bg = ~global.bg; - 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, w, ix; - uint32_t uni; - - if_not_init_return(); - - if (!cellbuf_in_bounds(&global.back, x, y)) { - return TB_ERR_OUT_OF_BOUNDS; - } - - ix = x; - if (out_w) *out_w = 0; - - while (*str) { - rv = tb_utf8_char_to_unicode(&uni, str); - - if (rv < 0) { - uni = 0xfffd; // replace invalid UTF-8 char with U+FFFD - str += rv * -1; - } else if (rv > 0) { - str += rv; - } else { - break; // shouldn't get here - } - - if (uni == '\n') { // TODO: \r, \t, \v, \f, etc? - x = ix; - y += 1; - continue; - } else if (!iswprint((wint_t)uni)) { - uni = 0xfffd; // replace non-printable with U+FFFD - } - - w = wcwidth((wchar_t)uni); - if (w < 0) { - return TB_ERR; // shouldn't happen if iswprint - } else if (w == 0) { // combining character - if (cellbuf_in_bounds(&global.back, x - 1, y)) { - if_err_return(rv, tb_extend_cell(x - 1, y, uni)); - } - } else { - if (cellbuf_in_bounds(&global.back, x, y)) { - 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(void) { - 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 0; - - 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 && c[i] != '\0'; ++i) { - result <<= 6; - result |= c[i] & 0x3f; - } - - if (i != len) return i * -1; - - *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; - out[len] = '\0'; - - return len; -} - -int tb_last_errno(void) { - 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: - strerror_r(global.last_errno, global.errbuf, sizeof(global.errbuf)); - return (const char *)global.errbuf; - } -} - -int tb_has_truecolor(void) { -#if TB_OPT_ATTR_W >= 32 - return 1; -#else - return 0; -#endif -} - -int tb_has_egc(void) { -#ifdef TB_OPT_EGC - return 1; -#else - return 0; -#endif -} - -int tb_attr_width(void) { - return TB_OPT_ATTR_W; -} - -const char *tb_version(void) { - return TB_VERSION_STR; -} - -static int tb_reset(void) { - 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(void) { - 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(void) { - if (load_terminfo() == TB_OK) { - return parse_terminfo_caps(); - } - return load_builtin_caps(); -} - -static int init_cap_trie(void) { - int rv, i; - - // Add caps from terminfo or built-in - // - // Collisions are expected as some terminfo entries have dupes. (For - // example, att605-pc collides on TB_CAP_F4 and TB_CAP_DELETE.) First cap - // in TB_CAP_* index order will win. - // - // TODO: Reorder TB_CAP_* so more critical caps come first. - for (i = 0; i < TB_CAP__COUNT_KEYS; i++) { - rv = cap_trie_add(global.caps[i], tb_key_i(i), 0); - if (rv != TB_OK && rv != TB_ERR_CAP_COLLISION) return rv; - } - - // Add built-in mod caps - // - // Collisions are OK here as well. This can happen if global.caps collides - // with builtin_mod_caps. It is desirable to give precedence to global.caps - // here. - 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); - 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; - - if (!cap || strlen(cap) <= 0) return TB_OK; // Nothing to do for empty caps - - 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 = (struct cap_trie_t *)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(void) { - 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(void) { - 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(void) { - 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(void) { - 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; - } - } - - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = SIG_DFL; - sigaction(SIGWINCH, &sa, 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(void) { - int rv; - char tmp[TB_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[TB_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 = (char *)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(void) { - // 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, header[4], - 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(void) { - 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_offsets_len, int16_t str_table_pos, int16_t str_table_len, - int16_t str_index) { - const int str_byte_index = (int)str_index * (int)sizeof(int16_t); - if (str_byte_index >= (int)str_offsets_len * (int)sizeof(int16_t)) { - // An offset beyond the table indicates absent - // See `convert_strings` in tinfo `read_entry.c` - return ""; - } - const int16_t *str_offset = - (int16_t *)(global.terminfo + (int)str_offsets_pos + str_byte_index); - if ((char *)str_offset >= global.terminfo + global.nterminfo) { - // str_offset points beyond end of entry - // Truncated/corrupt terminfo entry? - return NULL; - } - if (*str_offset < 0 || *str_offset >= str_table_len) { - // A negative offset indicates absent - // An offset beyond the table indicates absent - // See `convert_strings` in tinfo `read_entry.c` - return ""; - } - if (((size_t)((int)str_table_pos + (int)*str_offset)) >= global.nterminfo) { - // string points beyond end of entry - // Truncated/corrupt terminfo entry? - 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_VT200 = 0, TYPE_1006, TYPE_1015, TYPE_MAX }; - - const 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["}; - - int 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); - - unsigned n1 = strtoul(&in->buf[start], NULL, 10); - unsigned n2 = - strtoul(&in->buf[indices[FIRST_SEMICOLON] + 1], NULL, 10); - unsigned 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(void) { - 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])); - - uint32_t cfg, cbg; - switch (global.output_mode) { - default: - case TB_OUTPUT_NORMAL: - // The minus 1 below is because our colors are 1-indexed starting - // from black. Black is represented by a 30, 40, 90, or 100 for fg, - // bg, bright fg, or bright bg respectively. Red is 31, 41, 91, - // 101, etc. - cfg = (fg & TB_BRIGHT ? 90 : 30) + (fg & 0x0f) - 1; - cbg = (bg & TB_BRIGHT ? 100 : 40) + (bg & 0x0f) - 1; - break; - - case TB_OUTPUT_256: - cfg = fg & 0xff; - cbg = bg & 0xff; - if (fg & TB_HI_BLACK) cfg = 0; - if (bg & TB_HI_BLACK) cbg = 0; - break; - - case TB_OUTPUT_216: - cfg = fg & 0xff; - cbg = bg & 0xff; - if (cfg > 216) cfg = 216; - if (cbg > 216) cbg = 216; - cfg += 0x0f; - cbg += 0x0f; - break; - - case TB_OUTPUT_GRAYSCALE: - cfg = fg & 0xff; - cbg = bg & 0xff; - if (cfg > 24) cfg = 24; - if (cbg > 24) cbg = 24; - cfg += 0xe7; - cbg += 0xe7; - break; - -#if TB_OPT_ATTR_W >= 32 - case TB_OUTPUT_TRUECOLOR: - cfg = fg & 0xffffff; - cbg = bg & 0xffffff; - if (fg & TB_HI_BLACK) cfg = 0; - if (bg & TB_HI_BLACK) cbg = 0; - break; -#endif - } - - if (fg & TB_BOLD) - if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BOLD])); - - if (fg & TB_BLINK) - if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BLINK])); - - if (fg & TB_UNDERLINE) - if_err_return(rv, - bytebuf_puts(&global.out, global.caps[TB_CAP_UNDERLINE])); - - if (fg & TB_ITALIC) - if_err_return(rv, - bytebuf_puts(&global.out, global.caps[TB_CAP_ITALIC])); - - if (fg & TB_DIM) - if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_DIM])); - -#if TB_OPT_ATTR_W == 64 - if (fg & TB_STRIKEOUT) - if_err_return(rv, bytebuf_puts(&global.out, TB_HARDCAP_STRIKEOUT)); - - if (fg & TB_UNDERLINE_2) - if_err_return(rv, bytebuf_puts(&global.out, TB_HARDCAP_UNDERLINE_2)); - - if (fg & TB_OVERLINE) - if_err_return(rv, bytebuf_puts(&global.out, TB_HARDCAP_OVERLINE)); - - if (fg & TB_INVISIBLE) - if_err_return(rv, - bytebuf_puts(&global.out, global.caps[TB_CAP_INVISIBLE])); -#endif - - if ((fg & TB_REVERSE) || (bg & TB_REVERSE)) - if_err_return(rv, - bytebuf_puts(&global.out, global.caps[TB_CAP_REVERSE])); - - int fg_is_default = (fg & 0xff) == 0; - int bg_is_default = (bg & 0xff) == 0; - if (global.output_mode == TB_OUTPUT_256) { - if (fg & TB_HI_BLACK) fg_is_default = 0; - if (bg & TB_HI_BLACK) bg_is_default = 0; - } -#if TB_OPT_ATTR_W >= 32 - if (global.output_mode == TB_OUTPUT_TRUECOLOR) { - fg_is_default = ((fg & 0xffffff) == 0) && ((fg & TB_HI_BLACK) == 0); - bg_is_default = ((bg & 0xffffff) == 0) && ((bg & TB_HI_BLACK) == 0); - } -#endif - - if_err_return(rv, send_sgr(cfg, cbg, fg_is_default, bg_is_default)); - - global.last_fg = fg; - global.last_bg = bg; - - return TB_OK; -} - -static int send_sgr(uint32_t cfg, uint32_t cbg, int fg_is_default, - int bg_is_default) { - int rv; - char nbuf[32]; - - if (fg_is_default && bg_is_default) { - return TB_OK; - } - - switch (global.output_mode) { - default: - case TB_OUTPUT_NORMAL: - send_literal(rv, "\x1b["); - if (!fg_is_default) { - send_num(rv, nbuf, cfg); - if (!bg_is_default) { - send_literal(rv, ";"); - } - } - if (!bg_is_default) { - send_num(rv, nbuf, cbg); - } - send_literal(rv, "m"); - break; - - case TB_OUTPUT_256: - case TB_OUTPUT_216: - case TB_OUTPUT_GRAYSCALE: - send_literal(rv, "\x1b["); - if (!fg_is_default) { - send_literal(rv, "38;5;"); - send_num(rv, nbuf, cfg); - if (!bg_is_default) { - send_literal(rv, ";"); - } - } - if (!bg_is_default) { - send_literal(rv, "48;5;"); - send_num(rv, nbuf, cbg); - } - send_literal(rv, "m"); - break; - -#if TB_OPT_ATTR_W >= 32 - case TB_OUTPUT_TRUECOLOR: - send_literal(rv, "\x1b["); - if (!fg_is_default) { - send_literal(rv, "38;2;"); - send_num(rv, nbuf, (cfg >> 16) & 0xff); - send_literal(rv, ";"); - send_num(rv, nbuf, (cfg >> 8) & 0xff); - send_literal(rv, ";"); - send_num(rv, nbuf, cfg & 0xff); - if (!bg_is_default) { - send_literal(rv, ";"); - } - } - if (!bg_is_default) { - send_literal(rv, "48;2;"); - send_num(rv, nbuf, (cbg >> 16) & 0xff); - send_literal(rv, ";"); - send_num(rv, nbuf, (cbg >> 8) & 0xff); - send_literal(rv, ";"); - send_num(rv, nbuf, cbg & 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 chu8[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 ch32 = *(ch + i); - if (!iswprint((wint_t)ch32)) { - ch32 = 0xfffd; // replace non-printable codepoints with U+FFFD - } - int chu8_len = tb_utf8_unicode_to_char(chu8, ch32); - if_err_return(rv, bytebuf_nputs(&global.out, chu8, (size_t)chu8_len)); - } - - return TB_OK; -} - -static int convert_num(uint32_t num, char *buf) { - int i, l = 0; - char ch; - do { - buf[l++] = (char)('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, sizeof(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 = (struct tb_cell *)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 (!cellbuf_in_bounds(c, x, y)) { - *out = NULL; - return TB_ERR_OUT_OF_BOUNDS; - } - *out = &c->cells[(y * c->width) + x]; - return TB_OK; -} - -static int cellbuf_in_bounds(struct cellbuf_t *c, int x, int y) { - if (x < 0 || x >= c->width || y < 0 || y >= c->height) { - return 0; - } - return 1; -} - -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) { - if (!str || strlen(str) <= 0) return TB_OK; // Nothing to do for empty caps - 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 = (char *)tb_realloc(b->buf, newcap); - } else { - newbuf = (char *)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/v1/.gitignore b/v1/.gitignore new file mode 100644 index 0000000..a9ac961 --- /dev/null +++ b/v1/.gitignore @@ -0,0 +1,7 @@ +chatty +server +recv +send +client + +tags diff --git a/v1/README.md b/v1/README.md new file mode 100644 index 0000000..2cf0aee --- /dev/null +++ b/v1/README.md @@ -0,0 +1,58 @@ +# Chatty +The idea is the following: +- tcp server that you can send messages to +- history upon connecting +- date of messages sent +- authentication +- encrypted communication (tls?) +- client for reading the messages and sending them at the same time + +# Common +- use memory arena's to manage memory +- manage memory for what if it will not fit + - for just do nothing when the limit is reached + +# Server +- min height & width +- wrapping input +- [ ] bug: retransmissed message have no text +- [ ] history +- [x] max y for new messages and make them scroll +- [x] check resize event +- [x] asynchronously receive/send a message +- [x] send message to all other clients +- [x] fix receiving messages with arbitrary text length +- [x] bug: server copying the bytes correctly + +- rooms +- encryption +- authentication + +# Client +- bug: when having multiple messages and resizing a lot, the output will be in shambles +- bug: when resizing afters sending messages over network it crashes +- bug: all messages using the same buffer for text +- bug: 1. open chatty, send a message with send, messages won't be received by the server +- bug: memcpy is overlapping a byte in the next message when messages_add +- use pointer for add_message +- validation of sent/received messages +- handle disconnection + +# Questions +- will two consecutive sends be read in one recv + - not always. +- can you recv a message in two messages + - yes, done. + +# Message protocol +Version 1 +1 version byte +4 length bytes +12 message_author bytes +- 11 chars + \0 +9 timestamp bytes +- 8chars + \0 +x text bytes +- x bytes + \0 + +The variable text bytes can be calculated by substracting the author and timestamp from the length diff --git a/v1/arena.h b/v1/arena.h new file mode 100644 index 0000000..6f371f4 --- /dev/null +++ b/v1/arena.h @@ -0,0 +1,116 @@ +#ifndef ARENA_IMPL +#define ARENA_IMPL + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PAGESIZE 4096 + +#ifndef ARENA_MEMORY +#define ARENA_MEMORY PAGESIZE +#endif + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; + +struct Arena { + void *memory; + u64 size; + u64 pos; +} typedef Arena; + +// Create an arena +Arena *ArenaAlloc(void); +// Destroy an arena +void ArenaRelease(Arena *arena); + +// Push bytes on to the arena | allocating +void *ArenaPush(Arena *arena, u64 size); +void *ArenaPushZero(Arena *arena, u64 size); + +#define PushArray(arena, type, count) (type *)ArenaPush((arena), sizeof(type) * (count)) +#define PushArrayZero(arena, type, count) (type *)ArenaPushZero((arena), sizeof(type) * (count)) +#define PushStruct(arena, type) PushArray((arena), (type), 1) +#define PushStructZero(arena, type) PushArrayZero((arena), (type), 1) + +// Free some bytes by popping the stack +void ArenaPop(Arena *arena, u64 size); +// Get the number of bytes allocated +u64 ArenaGetPos(Arena *arena); + +void ArenaSetPosBack(Arena *arena, u64 pos); +void ArenaClear(Arena *arena); + +Arena *ArenaAlloc(void) +{ + // NOTE: If the arena is created here the pointer to the memory get's overwritten with size in + // ArenaPush, so we are forced to use malloc + Arena *arena = malloc(sizeof(Arena)); + + arena->memory = mmap(NULL, ARENA_MEMORY, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (arena->memory == MAP_FAILED) + return NULL; + + arena->pos = 0; + arena->size = ARENA_MEMORY; + + return arena; +} + +void ArenaRelease(Arena *arena) +{ + munmap(arena->memory, ARENA_MEMORY); + free(arena); +} + +void *ArenaPush(Arena *arena, u64 size) +{ + u64 *mem; + mem = (u64 *)arena->memory + arena->pos; + arena->pos += size; + return mem; +} + +void *ArenaPushZero(Arena *arena, u64 size) +{ + u64 *mem; + mem = (u64 *)arena->memory + arena->pos; + bzero(mem, size); + arena->pos += size; + return mem; +} + +void ArenaPop(Arena *arena, u64 size) +{ + arena->pos -= size; +} + +u64 ArenaGetPos(Arena *arena) +{ + return arena->pos; +} + +void ArenaSetPosBack(Arena *arena, u64 pos) +{ + arena->pos -= pos; +} + +void ArenaClear(Arena *arena) +{ + bzero(arena->memory, arena->size); + arena->pos = 0; +} + +#endif diff --git a/v1/build.sh b/v1/build.sh new file mode 100755 index 0000000..a80b193 --- /dev/null +++ b/v1/build.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -x +gcc -ggdb -Wall -pedantic -std=c99 -o client client.c +gcc -ggdb -Wall -pedantic -std=c99 -o server server.c +gcc -ggdb -Wall -pedantic -std=c99 -o send send.c +gcc -ggdb -Wall -pedantic -std=c99 -o recv recv.c diff --git a/v1/client.c b/v1/client.c new file mode 100644 index 0000000..878b678 --- /dev/null +++ b/v1/client.c @@ -0,0 +1,259 @@ +// Client for chatty + +// initial size for the messages array +#define MESSAGES_SIZE 5 + +// clang-format off +#define TB_IMPL +#include "termbox2.h" +// clang-format on +#include "common.h" + +#include +#include +#include +#include +#include +#include +#include + +enum { FD_SERVER = 0, + FD_TTY, + FD_RESIZE, + FD_MAX }; + +// offset of the input prompt +int curs_offs_x = 2; +int prompt_offs_y = 3; + +// filedescriptor for server +static int serverfd; +// Input message to be send +Message input = { + .author = USERNAME, + .timestamp = {0}, + .text_len = 0, +}; +// current amount of messages +int nmessages = 0; +// length of messages array +int messages_size = MESSAGES_SIZE; +// All messages sent and received in order +Message messages[MESSAGES_SIZE] = {0}; +// incremented each time a new message is printed +int msg_y = 0; + +// Cleans up resources, should called before exiting. +void cleanup(void); +// Displays an error message msg, followed by the errno variable and exits exeuction. +void err_exit(const char *msg); +// Display the welcome ui screen containing the prompt and messages array. +void scren_welcome(void); +// Append msg to the messages array. Returns -1 if there was no space in the messages array +// otherwise returns 0 on success. +u8 messages_add(Message *msg); + +void cleanup(void) +{ + tb_shutdown(); + if (serverfd) + if (close(serverfd)) + writef("Error while closing server socket. errno: %d\n", errno); +} + +// panic +void err_exit(const char *msg) +{ + cleanup(); + writef("%s errno: %d\n", msg, errno); + _exit(1); +} + +void screen_welcome(void) +{ + tb_set_cursor(curs_offs_x, global.height - prompt_offs_y); + tb_print(0, global.height - prompt_offs_y, 0, 0, ">"); + + // if there is not enough space to fit all messages, skip the n first messages of the array. + int skip = 0; + int lines_available = global.height - prompt_offs_y - 1; // pad by 1 from prompt + if (lines_available - nmessages < 0) + skip = nmessages - lines_available; + for (msg_y = skip; msg_y < nmessages; msg_y++) { + tb_printf(0, msg_y - skip, 0, 0, "%s [%s]: %s", messages[msg_y].timestamp, messages[msg_y].author, messages[msg_y].text); + } +} + +u8 messages_add(Message *msg) +{ + if (nmessages == messages_size) { + return -1; + } + + memcpy(messages[nmessages].author, msg->author, MESSAGE_AUTHOR_LEN); + memcpy(messages[nmessages].timestamp, msg->timestamp, MESSAGE_TIMESTAMP_LEN); + messages[nmessages].text_len = msg->text_len; + messages[nmessages].text = msg->text; + + nmessages++; + msg_y++; + + return 0; +} + +int main(void) +{ + // current event + struct tb_event ev; + // time for a new entered message + time_t now; + // localtime of new sent message + struct tm *ltime; + char buf[MESSAGE_MAX]; + input.text = buf; + + int serverfd, ttyfd, resizefd; + Message msg_recv = {0}; + const struct sockaddr_in address = { + AF_INET, + htons(PORT), + {0}, + }; + + tb_init(); + bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); + + screen_welcome(); + tb_present(); + + tb_get_fds(&ttyfd, &resizefd); + serverfd = socket(AF_INET, SOCK_STREAM, 0); + + struct pollfd fds[FD_MAX] = { + {serverfd, POLLIN, 0}, // FD_SERVER + { ttyfd, POLLIN, 0}, // FD_TTY + {resizefd, POLLIN, 0}, // FD_RESIZE + }; + + if (connect(serverfd, (struct sockaddr *)&address, sizeof(address))) + err_exit("Error while connecting."); + + for (;;) { + if (poll(fds, FD_MAX, 50000) == -1) { + // check if it was a resize event that interrupted the system call + if (errno == EINTR) { + tb_peek_event(&ev, 80); + if (ev.type != TB_EVENT_RESIZE) + err_exit("Error while polling."); + else { + tb_clear(); + screen_welcome(); + } + } + } + + if (fds[FD_TTY].revents & POLLIN) { + tb_poll_event(&ev); + switch (ev.key) { + // exit + case TB_KEY_CTRL_C: + case TB_KEY_CTRL_D: + case TB_KEY_ESC: + goto exit_loop; + // remove line till cursor + case TB_KEY_CTRL_U: + while (global.cursor_x > curs_offs_x) { + global.cursor_x--; + tb_print(global.cursor_x, global.cursor_y, 0, 0, " "); + } + tb_set_cursor(curs_offs_x, global.cursor_y); + input.text_len = 0; + break; + // send message + case TB_KEY_CTRL_M: + if (input.text_len <= 0) + break; + while (global.cursor_x > curs_offs_x) { + global.cursor_x--; + tb_print(global.cursor_x, global.cursor_y, 0, 0, " "); + } + tb_set_cursor(curs_offs_x, global.cursor_y); + + // zero terminate + input.text[input.text_len] = 0; + + // print new message + time(&now); + ltime = localtime(&now); + strftime((char*)input.timestamp, sizeof(input.timestamp), "%H:%M:%S", ltime); + + messages_add(&input); + + if (message_send(&input, serverfd) == -1) + err_exit("Error while sending message."); + + // reset buffer + input.text_len = 0; + + // update the screen + // NOTE: kind of wasteful cause we should only display new message + tb_clear(); + screen_welcome(); + + break; + // remove word + case TB_KEY_CTRL_W: + // Delete consecutive space + while (input.text[input.text_len - 1] == ' ' && global.cursor_x > curs_offs_x) { + global.cursor_x--; + input.text_len--; + tb_print(global.cursor_x, global.cursor_y, 0, 0, " "); + } + // Delete until next non-space + while (input.text[input.text_len - 1] != ' ' && global.cursor_x > curs_offs_x) { + global.cursor_x--; + input.text_len--; + tb_print(global.cursor_x, global.cursor_y, 0, 0, " "); + } + input.text[input.text_len] = 0; + break; + } + + // append pressed character to input.text + // TODO: wrap instead, allocate more ram for the message instead + if (ev.ch > 0 && input.text_len < MESSAGE_MAX && input.text_len < global.width - 3 - 1) { + tb_printf(global.cursor_x, global.cursor_y, 0, 0, "%c", ev.ch); + global.cursor_x++; + + input.text[input.text_len++] = ev.ch; + } + + } else if (fds[FD_SERVER].revents & POLLIN) { + int nrecv = message_receive(&msg_recv, serverfd); + + if (nrecv == 0) { + // Server closed + // TODO: error message like (disconnected) + break; + } else if (nrecv == -1) { + err_exit("Error while receiveiving from server."); + } + messages_add(&msg_recv); + tb_clear(); + screen_welcome(); + + } else if (fds[FD_RESIZE].revents & POLLIN) { + tb_poll_event(&ev); + if (ev.type == TB_EVENT_RESIZE) { + tb_clear(); + screen_welcome(); + } + } + + tb_present(); + } +exit_loop:; + + cleanup(); + return 0; +} diff --git a/v1/common.h b/v1/common.h new file mode 100644 index 0000000..c7bb0dd --- /dev/null +++ b/v1/common.h @@ -0,0 +1,181 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9983 +// max buffer size sent over network +// TODO: choose a better size +#define BUF_MAX 256 +// max size for a message sent +#define MESSAGE_MAX 256 +// max length of author field +#define MESSAGE_AUTHOR_LEN 13 +// max length of timestamp field +#define MESSAGE_TIMESTAMP_LEN 9 +// current user's name +#define USERNAME "Jef Koek" + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; + +// To serialize the text that could be arbitrary length the lenght is encoded after the author +// string and before the text. +struct Message { + u8 author[MESSAGE_AUTHOR_LEN]; + u8 timestamp[MESSAGE_TIMESTAMP_LEN]; // HH:MM:SS + u16 text_len; // length of the text including null terminator + char *text; +} typedef Message; + +// printf without buffering using write syscall, works when using sockets +void writef(char *format, ...); + +u16 str_len(char *str); + +// save the message msg to file in binary format, returns zero on success, returns 1 if the msg.text +// was empty which should not be allowed. +u8 message_fsave(Message *msg, FILE *f); +// load the message msg from file f, returns zero on success, returns 1 if the msg.text +// was empty which should not be allowed. +u8 message_fload(Message *msg, FILE *f); + +// Encode msg and send it to fd +// return -1 if send() returns -1. Otherwise returns number of bytes sent. +// NOTE: this function should not alter the content stored in msg. +u32 message_send(Message *msg, u32 fd); +// Decode data from fd and populate msg with it +// if recv() returns 0 or -1 it will return early and return 0 or -1 accordingly. +// Otherwise returns the number of bytes received +u32 message_receive(Message *msg, u32 fd); + +void writef(char *format, ...) +{ + char buf[255 + 1]; + va_list args; + va_start(args, format); + + vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + int n = 0; + while (*(buf + n) != 0) + n++; + write(0, buf, n); +} + +// Returns the length of the string plus the null terminator +u16 str_len(char *str) +{ + if (*str == 0) + return 0; + + u16 i = 0; + while (str[i]) + i++; + + return i + 1; +} + +void str_cpy(char *to, char *from) +{ + while ((*to++ = *from++)) + ; +} + +// Save msg to file f +// Returns 0 on success, returns 1 if msg->text is NULL, returns 2 if mfg->len is 0 +u8 message_fsave(Message *msg, FILE *f) +{ + if (msg->text == NULL) { + return 1; + } else if (msg->text_len == 0) + return 2; + + fwrite(&msg->timestamp, sizeof(*msg->timestamp) * MESSAGE_TIMESTAMP_LEN, 1, f); + fwrite(&msg->author, sizeof(*msg->author) * MESSAGE_AUTHOR_LEN, 1, f); + fwrite(&msg->text_len, sizeof(msg->text_len), 1, f); + fwrite(&msg->text, msg->text_len, 1, f); + + return 0; +} + +u8 message_fload(Message *msg, FILE *f) +{ + fread(msg, sizeof(*msg->timestamp) * MESSAGE_TIMESTAMP_LEN + sizeof(*msg->author) * MESSAGE_AUTHOR_LEN, 1, f); + u16 len; + fread(&len, sizeof(len), 1, f); + if (len == 0) { + // TODO: Error: empty message should not be allowed + // empty message + msg->text = NULL; + return 1; + } + char txt[len]; + fgets(txt, len, f); + memcpy(msg->text, txt, len); + + return 0; +} + +u32 message_send(Message *msg, u32 serverfd) +{ + // for protocol see README.md + u32 buf_len = sizeof(buf_len) + MESSAGE_AUTHOR_LEN + MESSAGE_TIMESTAMP_LEN + msg->text_len; + char buf[buf_len]; + u32 offset; + + memcpy(buf, &buf_len, sizeof(buf_len)); + offset = sizeof(buf_len); + memcpy(buf + offset, msg->author, MESSAGE_AUTHOR_LEN); + offset += MESSAGE_AUTHOR_LEN; + memcpy(buf + offset, msg->timestamp, MESSAGE_TIMESTAMP_LEN); + offset += MESSAGE_TIMESTAMP_LEN; + memcpy(buf + offset, msg->text, msg->text_len); + + u32 n = send(serverfd, &buf, buf_len, 0); + if (n == -1) + return n; + + return n; +} + +u32 message_receive(Message *msg, u32 clientfd) +{ + // for protocol see README.md + // must all be of the s + u32 nrecv = 0, buf_len = 0; + // limit on what can be received with recv() + u32 buf_size = 20; + // temporary buffer to receive message data over a stream + char recv_buf[BUF_MAX] = {0}; + + nrecv = recv(clientfd, recv_buf, buf_size, 0); + if (nrecv == 0 || nrecv == -1) + return nrecv; + + memcpy(&buf_len, recv_buf, sizeof(buf_len)); + + u32 i = 0; + while (nrecv < buf_len) { + // advance the copying by the amounts of bytes received each time + i = recv(clientfd, recv_buf + nrecv, buf_size, 0); + if (i == 0 || i == -1) + return nrecv; + nrecv += i; + } + + memcpy(msg, recv_buf + sizeof(buf_len), MESSAGE_AUTHOR_LEN + MESSAGE_TIMESTAMP_LEN); + msg->text = recv_buf + sizeof(buf_len) + MESSAGE_AUTHOR_LEN + MESSAGE_TIMESTAMP_LEN; + msg->text_len = buf_len - sizeof(buf_len) - MESSAGE_AUTHOR_LEN - MESSAGE_TIMESTAMP_LEN; + + return nrecv; +} diff --git a/v1/compile_flags.txt b/v1/compile_flags.txt new file mode 100644 index 0000000..1a62790 --- /dev/null +++ b/v1/compile_flags.txt @@ -0,0 +1,5 @@ +-Wall +-Werror +-pedantic +-std=c99 +-O3 diff --git a/v1/recv.c b/v1/recv.c new file mode 100644 index 0000000..042d8a9 --- /dev/null +++ b/v1/recv.c @@ -0,0 +1,59 @@ +// Minimal server implementation for probing out things + +#include "common.h" +#include +#include +#include +#include +#include + +int main(void) +{ + u32 serverfd, clientfd; + u8 on = 1; + + const struct sockaddr_in address = { + AF_INET, + htons(PORT), + {0}, + }; + + serverfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)); + u32 err = bind(serverfd, (struct sockaddr *)&address, sizeof(address)); + assert(err == 0); + + err = listen(serverfd, 256); + assert(err == 0); + + clientfd = accept(serverfd, 0, 0); + assert(clientfd != -1); + + struct pollfd fds[1] = { + {clientfd, POLLIN, 0}, + }; + + for (;;) { + int ret = poll(fds, 1, 50000); + assert(ret != -1); + + if (fds[0].revents & POLLIN) { + u8 recv_buf[BUF_MAX]; + u32 nrecv = recv(clientfd, recv_buf, sizeof(recv_buf), 0); + assert(nrecv >= 0); + + writef("client(%d): %d bytes received.\n", clientfd, nrecv); + if (nrecv == 0) { + writef("client(%d): disconnected.\n", clientfd); + fds[0].fd = -1; + fds[0].revents = 0; + err = close(clientfd); + assert(err == 0); + + return 0; + } + } + } + + return 0; +} diff --git a/v1/send.c b/v1/send.c new file mode 100644 index 0000000..27e9793 --- /dev/null +++ b/v1/send.c @@ -0,0 +1,61 @@ +// minimal client implementation +#include "common.h" +#include +#include +#include +#include +#include + +u32 serverfd; + +// NOTE: Errno could be unset and contain an error for a previous command +void debug_panic(const char *msg) +{ + writef("%s errno: %d\n", msg, errno); + raise(SIGINT); +} + +// get current time in timestamp string +void timestamp(u8 timestamp[MESSAGE_TIMESTAMP_LEN]) +{ + time_t now; + struct tm *ltime; + time(&now); + ltime = localtime(&now); + strftime((char*)timestamp, MESSAGE_TIMESTAMP_LEN, "%H:%M:%S", ltime); +} + +int main(int argc, char **argv) +{ + + if (argc < 2) { + printf("usage: send \n"); + return 1; + } + + Message input = { + .author = "Friendship", + }; + input.text = argv[1]; + input.text_len = str_len(input.text); + + serverfd = socket(AF_INET, SOCK_STREAM, 0); + if (serverfd == -1) + debug_panic("Error while getting socket."); + + const struct sockaddr_in address = { + AF_INET, + htons(PORT), + {0}, + }; + + if (connect(serverfd, (struct sockaddr *)&address, sizeof(address))) + debug_panic("Error while connecting."); + + printf("input.len: %d\n", input.text_len); + timestamp(input.timestamp); + + message_send(&input, serverfd); + + return 0; +} diff --git a/v1/server.c b/v1/server.c new file mode 100644 index 0000000..7edf558 --- /dev/null +++ b/v1/server.c @@ -0,0 +1,148 @@ +// Server for chatty +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_CONNECTIONS 5 +#define FD_MAX MAX_CONNECTIONS + 1 + +// static const u8 *filename = "history.dat"; + +enum { FD_SERVER = 0 }; +u32 serverfd; + +void err_exit(const char *msg) +{ + if (serverfd) + if (close(serverfd)) + writef("Error while closing server socket. errno: %d\n", errno); + fprintf(stderr, "%s errno: %d\n", msg, errno); + _exit(1); +} + +int main(void) +{ + u32 clientfd; + u16 nclient = 0; + u32 on = 1; + Message msg_recv = {0}; + + serverfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP); + if (setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on))) + err_exit("Error while setting socket option."); + + const struct sockaddr_in address = { + AF_INET, + htons(PORT), + {0}, + }; + + if (bind(serverfd, (struct sockaddr *)&address, sizeof(address))) + err_exit("Error while binding."); + + if (listen(serverfd, BUF_MAX)) + err_exit("Error while listening"); + + writef("Listening on localhost:%d\n", PORT); + + struct pollfd fds[MAX_CONNECTIONS + 1] = { + {serverfd, POLLIN, 0}, // FD_SERVER + { -1, POLLIN, 0}, + { -1, POLLIN, 0}, + { -1, POLLIN, 0}, + { -1, POLLIN, 0}, + { -1, POLLIN, 0}, + }; + + for (;;) { + u32 ret = poll(fds, FD_MAX, 50000); + if (ret == -1) + err_exit("Error while polling"); + else if (ret == 0) { + writef("polling timed out.\n"); + continue; + } + + // New client tries to connect to serverfd + if (fds[FD_SERVER].revents & POLLIN) { + clientfd = accept(serverfd, NULL, NULL); + + // When MAX_CONNECTIONS is reached, close new clients trying to connect. + if (nclient == MAX_CONNECTIONS) { + writef("Max connections reached.\n"); + if (send(clientfd, 0, 0, 0) == -1) + err_exit("Error while sending EOF to client socket."); + if (shutdown(clientfd, SHUT_RDWR)) + err_exit("Error while shutting down client socket."); + if (close(clientfd)) + err_exit("Error while closing client socket."); + } else if (clientfd != -1) { + nclient++; + + // get a new available spot in the fds array + u32 i; + for (i = 0; i < MAX_CONNECTIONS; i++) + if (fds[i].fd == -1) + break; + fds[i].fd = clientfd; + writef("New client: %d, %d\n", i, clientfd); + + } else { + writef("Could not accept client errno: %d\n", errno); + } + } + + // Check for events on connected clients + for (u32 i = 1; i <= nclient; i++) { + if (!(fds[i].revents & POLLIN)) + continue; + + u32 nrecv; + + clientfd = fds[i].fd; + + nrecv = message_receive(&msg_recv, clientfd); + if (nrecv == 0) { + printf("client %d disconnected.\n", i); + fds[i].fd = -1; + fds[i].revents = 0; + if (shutdown(clientfd, SHUT_RDWR)) + err_exit("Error while shutting down client %d socket."); + if (close(clientfd)) + err_exit("Error while cloing client socket."); + nclient--; + break; + } else if (nrecv == -1) { + // TODO: this can happen when connect is reset by pear + err_exit("Error while receiving from client socket."); + } + + writef("Received %d bytes from client(%d): %s [%s] (%d)%s\n", nrecv, clientfd - serverfd, msg_recv.timestamp, msg_recv.author, msg_recv.text_len, msg_recv.text); + + // TODO: + for (u32 j = 1; j <= nclient; j++) { + // skip the client that sent the message + if (j == i) + continue; + if (message_send(&msg_recv, fds[j].fd) == -1) + printf("Error while sendig message to client %d. errno: %d\n", j, errno); + else + printf("Retransmitted message to client %d.\n", j); + } + + // // TODO: Serialize received message + // FILE *f = fopen(filename, "wb"); + // save_message(&msg_recv, f); + // fclose(f); + // // return 0; + } + } + + return 0; +} diff --git a/v1/termbox2.h b/v1/termbox2.h new file mode 100644 index 0000000..265cdab --- /dev/null +++ b/v1/termbox2.h @@ -0,0 +1,3517 @@ +/* +MIT License + +Copyright (c) 2010-2020 nsf + 2015-2024 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_INCL +#define TERMBOX_H_INCL + +#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 +#include + +#ifdef PATH_MAX +#define TB_PATH_MAX PATH_MAX +#else +#define TB_PATH_MAX 4096 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// __ffi_start + +#define TB_VERSION_STR "2.5.0-dev" + +/* The following compile-time options are supported: + * + * TB_OPT_ATTR_W: Integer width of fg and bg attributes. Valid values + * (assuming system support) are 16, 32, and 64. (See + * uintattr_t). 32 or 64 enables output mode + * TB_OUTPUT_TRUECOLOR. 64 enables additional style + * attributes. (See tb_set_output_mode.) Larger values + * consume more memory in exchange for more features. + * Defaults to 16. + * + * TB_OPT_EGC: If set, enable extended grapheme cluster support + * (tb_extend_cell, tb_set_cell_ex). Consumes more memory. + * Defaults off. + * + * TB_OPT_PRINTF_BUF: Write buffer size for printf operations. Represents the + * largest string that can be sent in one call to tb_print* + * and tb_send* functions. Defaults to 4096. + * + * TB_OPT_READ_BUF: Read buffer size for tty reads. Defaults to 64. + * + * TB_OPT_TRUECOLOR: Deprecated. Sets TB_OPT_ATTR_W to 32 if not already set. + */ + +#if defined(TB_LIB_OPTS) || 0 // __tb_lib_opts +/* Ensure consistent compile-time options when using as a shared library */ +#undef TB_OPT_ATTR_W +#undef TB_OPT_EGC +#undef TB_OPT_PRINTF_BUF +#undef TB_OPT_READ_BUF +#define TB_OPT_ATTR_W 64 +#define TB_OPT_EGC +#endif + +/* Ensure sane `TB_OPT_ATTR_W` (16, 32, or 64) */ +#if defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 16 +#elif defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 32 +#elif defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 64 +#else +#undef TB_OPT_ATTR_W +#if defined TB_OPT_TRUECOLOR // Deprecated. Back-compat for old flag. +#define TB_OPT_ATTR_W 32 +#else +#define TB_OPT_ATTR_W 16 +#endif +#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 caps */ +/* BEGIN codegen h */ +/* Produced by ./codegen.sh on Tue, 03 Sep 2024 04:17:47 +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_DIM 36 +#define TB_CAP_INVISIBLE 37 +#define TB_CAP__COUNT 38 +/* 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" +#define TB_HARDCAP_STRIKEOUT "\x1b[9m" +#define TB_HARDCAP_UNDERLINE_2 "\x1b[21m" +#define TB_HARDCAP_OVERLINE "\x1b[53m" + +/* 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 + +#if TB_OPT_ATTR_W == 16 +#define TB_BOLD 0x0100 +#define TB_UNDERLINE 0x0200 +#define TB_REVERSE 0x0400 +#define TB_ITALIC 0x0800 +#define TB_BLINK 0x1000 +#define TB_HI_BLACK 0x2000 +#define TB_BRIGHT 0x4000 +#define TB_DIM 0x8000 +#define TB_256_BLACK TB_HI_BLACK // `TB_256_BLACK` is deprecated +#else +// `TB_OPT_ATTR_W` is 32 or 64 +#define TB_BOLD 0x01000000 +#define TB_UNDERLINE 0x02000000 +#define TB_REVERSE 0x04000000 +#define TB_ITALIC 0x08000000 +#define TB_BLINK 0x10000000 +#define TB_HI_BLACK 0x20000000 +#define TB_BRIGHT 0x40000000 +#define TB_DIM 0x80000000 +#define TB_TRUECOLOR_BOLD TB_BOLD // `TB_TRUECOLOR_*` is deprecated +#define TB_TRUECOLOR_UNDERLINE TB_UNDERLINE +#define TB_TRUECOLOR_REVERSE TB_REVERSE +#define TB_TRUECOLOR_ITALIC TB_ITALIC +#define TB_TRUECOLOR_BLINK TB_BLINK +#define TB_TRUECOLOR_BLACK TB_HI_BLACK +#endif + +#if TB_OPT_ATTR_W == 64 +#define TB_STRIKEOUT 0x0000000100000000 +#define TB_UNDERLINE_2 0x0000000200000000 +#define TB_OVERLINE 0x0000000400000000 +#define TB_INVISIBLE 0x0000000800000000 +#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 +#if TB_OPT_ATTR_W >= 32 +#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 + +/* Deprecated. 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 + +#if TB_OPT_ATTR_W == 64 +typedef uint64_t uintattr_t; +#elif TB_OPT_ATTR_W == 32 +typedef uint32_t uintattr_t; +#else // 16 +typedef uint16_t uintattr_t; +#endif + +/* A cell in a 2d grid representing the terminal screen. + * + * The terminal screen is represented as 2d array of cells. The structure is + * optimized for dealing with single-width (`wcwidth==1`) Unicode codepoints, + * however some support for grapheme clusters (e.g., combining diacritical + * marks) and wide codepoints (e.g., Hiragana) is provided through `ech`, + * `nech`, and `cech` via `tb_set_cell_ex`. `ech` is only valid when `nech>0`, + * otherwise `ch` is used. + * + * For non-single-width codepoints, 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. Callers may use + * `tb_set_cell_ex` or `tb_print*` to render `N==0` combining + * characters. + * + * 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` codepoint, 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 codepoint + uintattr_t fg; // bitwise foreground attributes + uintattr_t bg; // bitwise background attributes +#ifdef TB_OPT_EGC + uint32_t *ech; // a grapheme cluster of Unicode codepoints, 0-terminated + size_t nech; // num elements in ech, 0 means use ch instead of ech + size_t cech; // num elements allocated for 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) and `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` and `h` + * + * when `TB_EVENT_MOUSE`: `key` (`TB_KEY_MOUSE_*`), `x`, and `y` + */ +struct tb_event { + uint8_t type; // one of `TB_EVENT_*` constants + uint8_t mod; // bitwise `TB_MOD_*` constants + uint16_t key; // one of `TB_KEY_*` constants + uint32_t ch; // a Unicode codepoint + int32_t w; // resize width + int32_t h; // resize height + int32_t x; // mouse x + int32_t y; // mouse y +}; + +/* Initialize 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 `tb_shutdown`. + */ +int tb_init(void); +int tb_init_file(const char *path); +int tb_init_fd(int ttyfd); +int tb_init_rwfd(int rfd, int wfd); +int tb_shutdown(void); + +/* Return 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` calls. Both dimensions have an unspecified + * negative value when called before `tb_init` or after `tb_shutdown`. + */ +int tb_width(void); +int tb_height(void); + +/* Clear the internal back buffer using `TB_DEFAULT` or the attributes set by + * `tb_set_clear_attrs`. + */ +int tb_clear(void); +int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg); + +/* Synchronize the internal back buffer with the terminal by writing to tty. */ +int tb_present(void); + +/* Clear the internal front buffer effectively forcing a complete re-render of + * the back buffer to the tty. It is not necessary to call this under normal + * circumstances. */ +int tb_invalidate(void); + +/* Set the position of the cursor. Upper-left cell is (0, 0). */ +int tb_set_cursor(int cx, int cy); +int tb_hide_cursor(void); + +/* 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). + * + * Calling `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 codepoint to `tb_cell.ech`. + * + * Non-printable (`iswprint(3)`) codepoints are replaced with `U+FFFD` at render + * time. + */ +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); + +/* Set 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` is used. If for some reason you've decided to use + * `TB_INPUT_ESC | TB_INPUT_ALT`, it will behave as if only `TB_INPUT_ESC` was + * selected. + * + * If mode is `TB_INPUT_CURRENT`, return the current input mode. + * + * The default input mode is `TB_INPUT_ESC`. + */ +int tb_set_input_mode(int mode); + +/* Set the output mode. Termbox has multiple output modes: + * + * 1. `TB_OUTPUT_NORMAL` => [0..8] + * + * This mode provides 8 different colors: + * `TB_BLACK`, `TB_RED`, `TB_GREEN`, `TB_YELLOW`, + * `TB_BLUE`, `TB_MAGENTA`, `TB_CYAN`, `TB_WHITE` + * + * Plus `TB_DEFAULT` which skips sending a color code (i.e., uses the + * terminal's default color). + * + * Colors (including `TB_DEFAULT`) may be bitwise OR'd with attributes: + * `TB_BOLD`, `TB_UNDERLINE`, `TB_REVERSE`, `TB_ITALIC`, `TB_BLINK`, + * `TB_BRIGHT`, `TB_DIM` + * + * The following style attributes are also available if compiled with + * `TB_OPT_ATTR_W` set to 64: + * `TB_STRIKEOUT`, `TB_UNDERLINE_2`, `TB_OVERLINE`, `TB_INVISIBLE` + * + * As in all modes, the value 0 is interpreted as `TB_DEFAULT` for + * convenience. + * + * Some notes: `TB_REVERSE` and `TB_BRIGHT` can be applied as either `fg` or + * `bg` attributes for the same effect. The rest of the attributes apply to + * `fg` 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] + `TB_HI_BLACK` + * + * In this mode you get 256 distinct colors (plus default): + * 0x00 (1): `TB_DEFAULT` + * `TB_HI_BLACK` (1): `TB_BLACK` in `TB_OUTPUT_NORMAL` + * 0x01..0x07 (7): the next 7 colors as in `TB_OUTPUT_NORMAL` + * 0x08..0x0f (8): bright versions of the above + * 0x10..0xe7 (216): 216 different colors + * 0xe8..0xff (24): 24 different shades of gray + * + * All `TB_*` style attributes except `TB_BRIGHT` may be bitwise OR'd as in + * `TB_OUTPUT_NORMAL`. + * + * Note `TB_HI_BLACK` must be used for black, as 0x00 represents default. + * + * 3. `TB_OUTPUT_216` => [0..216] + * + * This mode supports the 216-color range of `TB_OUTPUT_256` only, but you + * don't need to provide an offset: + * 0x00 (1): `TB_DEFAULT` + * 0x01..0xd8 (216): 216 different colors + * + * 4. `TB_OUTPUT_GRAYSCALE` => [0..24] + * + * This mode supports the 24-color range of `TB_OUTPUT_256` only, but you + * don't need to provide an offset: + * 0x00 (1): `TB_DEFAULT` + * 0x01..0x18 (24): 24 different shades of gray + * + * 5. `TB_OUTPUT_TRUECOLOR` => [0x000000..0xffffff] + `TB_HI_BLACK` + * + * This mode provides 24-bit color on supported terminals. The format is + * 0xRRGGBB. + * + * All `TB_*` style attributes except `TB_BRIGHT` may be bitwise OR'd as in + * `TB_OUTPUT_NORMAL`. + * + * Note `TB_HI_BLACK` must be used for black, as 0x000000 represents default. + * + * To use the terminal default color (i.e., to not send an escape code), pass + * `TB_DEFAULT`. For convenience, the value 0 is interpreted as `TB_DEFAULT` in + * all modes. + * + * Note, cell attributes persist after switching output modes. Any translation + * between, for example, `TB_OUTPUT_NORMAL`'s `TB_RED` and + * `TB_OUTPUT_TRUECOLOR`'s 0xff0000 must be performed by the caller. Also note + * that cells previously rendered in one mode may persist unchanged until the + * front buffer is cleared (such as after a resize event) at which point it will + * be re-interpreted and flushed according to the current mode. Callers may + * invoke `tb_invalidate` if it is desirable to immediately re-interpret and + * flush the entire screen according to the current mode. + * + * 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. The same + * advice applies to style attributes. + * + * If mode is `TB_OUTPUT_CURRENT`, return the current output mode. + * + * The default output mode is `TB_OUTPUT_NORMAL`. + */ +int tb_set_output_mode(int mode); + +/* Wait for an event up to `timeout_ms` milliseconds and populate `event` 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 may elect to 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(2)`, `select(2)`, etc. + * externally. Callers must invoke `tb_poll_event` or `tb_peek_event` if + * fds become readable. */ +int tb_get_fds(int *ttyfd, int *resizefd); + +/* Print and printf functions. Specify param `out_w` to determine width of + * printed string. Strings are interpreted as UTF-8. + * + * Non-printable characters (`iswprint(3)`) and truncated UTF-8 byte sequences + * are replaced with U+FFFD. + * + * Newlines (`\n`) are supported with the caveat that `out_w` will return the + * width of the string as if it were on a single line. + * + * If the starting coordinate is out of bounds, `TB_ERR_OUT_OF_BOUNDS` is + * returned. If the starting coordinate is in bounds, but goes out of bounds, + * then the out-of-bounds portions of the string are ignored. + * + * For finer control, use `tb_set_cell`. + */ +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, ...); + +/* Deprecated. Set custom callbacks. `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 *)); + +/* Return byte length of codepoint given first byte of UTF-8 sequence (1-6). */ +int tb_utf8_char_length(char c); + +/* Convert UTF-8 null-terminated byte sequence to UTF-32 codepoint. + * + * If `c` is an empty C string, return 0. `out` is left unchanged. + * + * If a null byte is encountered in the middle of the codepoint, return a + * negative number indicating how many bytes were processed. `out` is left + * unchanged. + * + * Otherwise, return byte length of codepoint (1-6). + */ +int tb_utf8_char_to_unicode(uint32_t *out, const char *c); + +/* Convert UTF-32 codepoint to UTF-8 null-terminated byte sequence. + * + * `out` must be char[7] or greater. Return byte length of codepoint (1-6). + */ +int tb_utf8_unicode_to_char(char *out, uint32_t c); + +/* Library utility functions */ +int tb_last_errno(void); +const char *tb_strerror(int err); +struct tb_cell *tb_cell_buffer(void); // Deprecated +int tb_has_truecolor(void); +int tb_has_egc(void); +int tb_attr_width(void); +const char *tb_version(void); + +/* Deprecation notice! + * + * The following will be removed in version 3.x (ABI version 3): + * + * TB_256_BLACK (use TB_HI_BLACK) + * TB_OPT_TRUECOLOR (use TB_OPT_ATTR_W) + * TB_TRUECOLOR_BOLD (use TB_BOLD) + * TB_TRUECOLOR_UNDERLINE (use TB_UNDERLINE) + * TB_TRUECOLOR_REVERSE (use TB_REVERSE) + * TB_TRUECOLOR_ITALIC (use TB_ITALICe) + * TB_TRUECOLOR_BLINK (use TB_BLINK) + * TB_TRUECOLOR_BLACK (use TB_HI_BLACK) + * tb_cell_buffer + * tb_set_func + * TB_FUNC_EXTRACT_PRE + * TB_FUNC_EXTRACT_POST + */ + +#ifdef __cplusplus +} +#endif + +#endif // TERMBOX_H_INCL + +#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 *); + char errbuf[1024]; +}; + +static struct tb_global_t global = {0}; + +/* BEGIN codegen c */ +/* Produced by ./codegen.sh on Tue, 03 Sep 2024 04:17:48 +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) + 30, // dim (TB_CAP_DIM) + 32, // invis (TB_CAP_INVISIBLE) +}; + +// 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) + "\033[2m", // dim (TB_CAP_DIM) + "\033[8m", // invis (TB_CAP_INVISIBLE) +}; + +// 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\011", // 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) + "\033[2m", // dim (TB_CAP_DIM) + "", // invis (TB_CAP_INVISIBLE) +}; + +// 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) + "\033[2m", // dim (TB_CAP_DIM) + "", // invis (TB_CAP_INVISIBLE) +}; + +// 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) + "", // dim (TB_CAP_DIM) + "", // invis (TB_CAP_INVISIBLE) +}; + +// 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) + "", // dim (TB_CAP_DIM) + "", // invis (TB_CAP_INVISIBLE) +}; + +// 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) + "", // dim (TB_CAP_DIM) + "", // invis (TB_CAP_INVISIBLE) +}; + +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(void); +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(void); +static int init_term_caps(void); +static int init_cap_trie(void); +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(void); +static int send_init_escape_codes(void); +static int send_clear(void); +static int update_term_size(void); +static int update_term_size_via_esc(void); +static int init_cellbuf(void); +static int tb_deinit(void); +static int load_terminfo(void); +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(void); +static int load_builtin_caps(void); +static const char *get_terminfo_string(int16_t str_offsets_pos, + int16_t str_offsets_len, 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(void); +static void handle_resize(int sig); +static int send_attr(uintattr_t fg, uintattr_t bg); +static int send_sgr(uint32_t fg, uint32_t bg, int fg_is_default, + int bg_is_default); +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_in_bounds(struct cellbuf_t *c, int x, int y); +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(void) { + 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(void) { + if_not_init_return(); + tb_deinit(); + return TB_OK; +} + +int tb_width(void) { + if_not_init_return(); + return global.width; +} + +int tb_height(void) { + if_not_init_return(); + return global.height; +} + +int tb_clear(void) { + 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(void) { + 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 + // wcwidth simply returns -1 on overflow of wchar_t + w = wcwidth((wchar_t)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)) { + // Not enough room for wide char, send spaces + 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); + } + + // When wcwidth>1, we need to advance the cursor by more + // than 1, thereby skipping some cells. Set these skipped + // cells to an invalid codepoint in the front buffer, so + // that if this cell is later replaced by a wcwidth==1 char, + // we'll get a cell_cmp diff for the skipped cells and + // properly re-render. + for (i = 1; i < w; i++) { + struct tb_cell *front_wide; + uint32_t invalid = -1; + if_err_return(rv, + cellbuf_get(&global.front, x + i, y, &front_wide)); + if_err_return(rv, + cell_set(front_wide, &invalid, 1, -1, -1)); + } + } + } + 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_invalidate(void) { + int rv; + if_not_init_return(); + if_err_return(rv, resize_cellbufs()); + 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(void) { + 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) { + 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: +#if TB_OPT_ATTR_W >= 32 + case TB_OUTPUT_TRUECOLOR: +#endif + global.last_fg = ~global.fg; + global.last_bg = ~global.bg; + 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, w, ix; + uint32_t uni; + + if_not_init_return(); + + if (!cellbuf_in_bounds(&global.back, x, y)) { + return TB_ERR_OUT_OF_BOUNDS; + } + + ix = x; + if (out_w) *out_w = 0; + + while (*str) { + rv = tb_utf8_char_to_unicode(&uni, str); + + if (rv < 0) { + uni = 0xfffd; // replace invalid UTF-8 char with U+FFFD + str += rv * -1; + } else if (rv > 0) { + str += rv; + } else { + break; // shouldn't get here + } + + if (uni == '\n') { // TODO: \r, \t, \v, \f, etc? + x = ix; + y += 1; + continue; + } else if (!iswprint((wint_t)uni)) { + uni = 0xfffd; // replace non-printable with U+FFFD + } + + w = wcwidth((wchar_t)uni); + if (w < 0) { + return TB_ERR; // shouldn't happen if iswprint + } else if (w == 0) { // combining character + if (cellbuf_in_bounds(&global.back, x - 1, y)) { + if_err_return(rv, tb_extend_cell(x - 1, y, uni)); + } + } else { + if (cellbuf_in_bounds(&global.back, x, y)) { + 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(void) { + 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 0; + + 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 && c[i] != '\0'; ++i) { + result <<= 6; + result |= c[i] & 0x3f; + } + + if (i != len) return i * -1; + + *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; + out[len] = '\0'; + + return len; +} + +int tb_last_errno(void) { + 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: + strerror_r(global.last_errno, global.errbuf, sizeof(global.errbuf)); + return (const char *)global.errbuf; + } +} + +int tb_has_truecolor(void) { +#if TB_OPT_ATTR_W >= 32 + return 1; +#else + return 0; +#endif +} + +int tb_has_egc(void) { +#ifdef TB_OPT_EGC + return 1; +#else + return 0; +#endif +} + +int tb_attr_width(void) { + return TB_OPT_ATTR_W; +} + +const char *tb_version(void) { + return TB_VERSION_STR; +} + +static int tb_reset(void) { + 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(void) { + 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(void) { + if (load_terminfo() == TB_OK) { + return parse_terminfo_caps(); + } + return load_builtin_caps(); +} + +static int init_cap_trie(void) { + int rv, i; + + // Add caps from terminfo or built-in + // + // Collisions are expected as some terminfo entries have dupes. (For + // example, att605-pc collides on TB_CAP_F4 and TB_CAP_DELETE.) First cap + // in TB_CAP_* index order will win. + // + // TODO: Reorder TB_CAP_* so more critical caps come first. + for (i = 0; i < TB_CAP__COUNT_KEYS; i++) { + rv = cap_trie_add(global.caps[i], tb_key_i(i), 0); + if (rv != TB_OK && rv != TB_ERR_CAP_COLLISION) return rv; + } + + // Add built-in mod caps + // + // Collisions are OK here as well. This can happen if global.caps collides + // with builtin_mod_caps. It is desirable to give precedence to global.caps + // here. + 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); + 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; + + if (!cap || strlen(cap) <= 0) return TB_OK; // Nothing to do for empty caps + + 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 = (struct cap_trie_t *)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(void) { + 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(void) { + 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(void) { + 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(void) { + 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; + } + } + + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + sigaction(SIGWINCH, &sa, 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(void) { + int rv; + char tmp[TB_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[TB_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 = (char *)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(void) { + // 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, header[4], + 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(void) { + 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_offsets_len, int16_t str_table_pos, int16_t str_table_len, + int16_t str_index) { + const int str_byte_index = (int)str_index * (int)sizeof(int16_t); + if (str_byte_index >= (int)str_offsets_len * (int)sizeof(int16_t)) { + // An offset beyond the table indicates absent + // See `convert_strings` in tinfo `read_entry.c` + return ""; + } + const int16_t *str_offset = + (int16_t *)(global.terminfo + (int)str_offsets_pos + str_byte_index); + if ((char *)str_offset >= global.terminfo + global.nterminfo) { + // str_offset points beyond end of entry + // Truncated/corrupt terminfo entry? + return NULL; + } + if (*str_offset < 0 || *str_offset >= str_table_len) { + // A negative offset indicates absent + // An offset beyond the table indicates absent + // See `convert_strings` in tinfo `read_entry.c` + return ""; + } + if (((size_t)((int)str_table_pos + (int)*str_offset)) >= global.nterminfo) { + // string points beyond end of entry + // Truncated/corrupt terminfo entry? + 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_VT200 = 0, TYPE_1006, TYPE_1015, TYPE_MAX }; + + const 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["}; + + int 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); + + unsigned n1 = strtoul(&in->buf[start], NULL, 10); + unsigned n2 = + strtoul(&in->buf[indices[FIRST_SEMICOLON] + 1], NULL, 10); + unsigned 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(void) { + 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])); + + uint32_t cfg, cbg; + switch (global.output_mode) { + default: + case TB_OUTPUT_NORMAL: + // The minus 1 below is because our colors are 1-indexed starting + // from black. Black is represented by a 30, 40, 90, or 100 for fg, + // bg, bright fg, or bright bg respectively. Red is 31, 41, 91, + // 101, etc. + cfg = (fg & TB_BRIGHT ? 90 : 30) + (fg & 0x0f) - 1; + cbg = (bg & TB_BRIGHT ? 100 : 40) + (bg & 0x0f) - 1; + break; + + case TB_OUTPUT_256: + cfg = fg & 0xff; + cbg = bg & 0xff; + if (fg & TB_HI_BLACK) cfg = 0; + if (bg & TB_HI_BLACK) cbg = 0; + break; + + case TB_OUTPUT_216: + cfg = fg & 0xff; + cbg = bg & 0xff; + if (cfg > 216) cfg = 216; + if (cbg > 216) cbg = 216; + cfg += 0x0f; + cbg += 0x0f; + break; + + case TB_OUTPUT_GRAYSCALE: + cfg = fg & 0xff; + cbg = bg & 0xff; + if (cfg > 24) cfg = 24; + if (cbg > 24) cbg = 24; + cfg += 0xe7; + cbg += 0xe7; + break; + +#if TB_OPT_ATTR_W >= 32 + case TB_OUTPUT_TRUECOLOR: + cfg = fg & 0xffffff; + cbg = bg & 0xffffff; + if (fg & TB_HI_BLACK) cfg = 0; + if (bg & TB_HI_BLACK) cbg = 0; + break; +#endif + } + + if (fg & TB_BOLD) + if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BOLD])); + + if (fg & TB_BLINK) + if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BLINK])); + + if (fg & TB_UNDERLINE) + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_UNDERLINE])); + + if (fg & TB_ITALIC) + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_ITALIC])); + + if (fg & TB_DIM) + if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_DIM])); + +#if TB_OPT_ATTR_W == 64 + if (fg & TB_STRIKEOUT) + if_err_return(rv, bytebuf_puts(&global.out, TB_HARDCAP_STRIKEOUT)); + + if (fg & TB_UNDERLINE_2) + if_err_return(rv, bytebuf_puts(&global.out, TB_HARDCAP_UNDERLINE_2)); + + if (fg & TB_OVERLINE) + if_err_return(rv, bytebuf_puts(&global.out, TB_HARDCAP_OVERLINE)); + + if (fg & TB_INVISIBLE) + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_INVISIBLE])); +#endif + + if ((fg & TB_REVERSE) || (bg & TB_REVERSE)) + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_REVERSE])); + + int fg_is_default = (fg & 0xff) == 0; + int bg_is_default = (bg & 0xff) == 0; + if (global.output_mode == TB_OUTPUT_256) { + if (fg & TB_HI_BLACK) fg_is_default = 0; + if (bg & TB_HI_BLACK) bg_is_default = 0; + } +#if TB_OPT_ATTR_W >= 32 + if (global.output_mode == TB_OUTPUT_TRUECOLOR) { + fg_is_default = ((fg & 0xffffff) == 0) && ((fg & TB_HI_BLACK) == 0); + bg_is_default = ((bg & 0xffffff) == 0) && ((bg & TB_HI_BLACK) == 0); + } +#endif + + if_err_return(rv, send_sgr(cfg, cbg, fg_is_default, bg_is_default)); + + global.last_fg = fg; + global.last_bg = bg; + + return TB_OK; +} + +static int send_sgr(uint32_t cfg, uint32_t cbg, int fg_is_default, + int bg_is_default) { + int rv; + char nbuf[32]; + + if (fg_is_default && bg_is_default) { + return TB_OK; + } + + switch (global.output_mode) { + default: + case TB_OUTPUT_NORMAL: + send_literal(rv, "\x1b["); + if (!fg_is_default) { + send_num(rv, nbuf, cfg); + if (!bg_is_default) { + send_literal(rv, ";"); + } + } + if (!bg_is_default) { + send_num(rv, nbuf, cbg); + } + send_literal(rv, "m"); + break; + + case TB_OUTPUT_256: + case TB_OUTPUT_216: + case TB_OUTPUT_GRAYSCALE: + send_literal(rv, "\x1b["); + if (!fg_is_default) { + send_literal(rv, "38;5;"); + send_num(rv, nbuf, cfg); + if (!bg_is_default) { + send_literal(rv, ";"); + } + } + if (!bg_is_default) { + send_literal(rv, "48;5;"); + send_num(rv, nbuf, cbg); + } + send_literal(rv, "m"); + break; + +#if TB_OPT_ATTR_W >= 32 + case TB_OUTPUT_TRUECOLOR: + send_literal(rv, "\x1b["); + if (!fg_is_default) { + send_literal(rv, "38;2;"); + send_num(rv, nbuf, (cfg >> 16) & 0xff); + send_literal(rv, ";"); + send_num(rv, nbuf, (cfg >> 8) & 0xff); + send_literal(rv, ";"); + send_num(rv, nbuf, cfg & 0xff); + if (!bg_is_default) { + send_literal(rv, ";"); + } + } + if (!bg_is_default) { + send_literal(rv, "48;2;"); + send_num(rv, nbuf, (cbg >> 16) & 0xff); + send_literal(rv, ";"); + send_num(rv, nbuf, (cbg >> 8) & 0xff); + send_literal(rv, ";"); + send_num(rv, nbuf, cbg & 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 chu8[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 ch32 = *(ch + i); + if (!iswprint((wint_t)ch32)) { + ch32 = 0xfffd; // replace non-printable codepoints with U+FFFD + } + int chu8_len = tb_utf8_unicode_to_char(chu8, ch32); + if_err_return(rv, bytebuf_nputs(&global.out, chu8, (size_t)chu8_len)); + } + + return TB_OK; +} + +static int convert_num(uint32_t num, char *buf) { + int i, l = 0; + char ch; + do { + buf[l++] = (char)('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, sizeof(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 = (struct tb_cell *)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 (!cellbuf_in_bounds(c, x, y)) { + *out = NULL; + return TB_ERR_OUT_OF_BOUNDS; + } + *out = &c->cells[(y * c->width) + x]; + return TB_OK; +} + +static int cellbuf_in_bounds(struct cellbuf_t *c, int x, int y) { + if (x < 0 || x >= c->width || y < 0 || y >= c->height) { + return 0; + } + return 1; +} + +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) { + if (!str || strlen(str) <= 0) return TB_OK; // Nothing to do for empty caps + 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 = (char *)tb_realloc(b->buf, newcap); + } else { + newbuf = (char *)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 -- cgit v1.2.3