From f87f7b4f0aaccc65d03ccee5bb11915ead6fb0e1 Mon Sep 17 00:00:00 2001 From: Raymaekers Luca Date: Sun, 27 Apr 2025 12:52:06 +0200 Subject: First pass at preparing for Github --- .gitignore | 10 +- LICENSE | 7 + Makefile | 11 - README.md | 120 +- archived.md | 31 - archived/array.h | 70 - archived/input_box.c | 70 - archived/network_compression.c | 154 -- archived/ui_checkmark.c | 87 - archived/ui_meter.c | 108 - archived/ui_selection.c | 71 - archived/utf8toASCII.c | 163 -- archived/v1/.gitignore | 7 - archived/v1/README.md | 58 - archived/v1/arena.h | 116 -- archived/v1/build.sh | 6 - archived/v1/client.c | 259 --- archived/v1/common.h | 181 -- archived/v1/compile_flags.txt | 5 - archived/v1/recv.c | 59 - archived/v1/send.c | 61 - archived/v1/server.c | 148 -- archived/v1/termbox2.h | 3517 -------------------------------- archived/wrap.c | 52 - arena.h | 53 - build.sh | 7 - build/chatty | Bin 0 -> 139016 bytes build/input_box | Bin 0 -> 117432 bytes build/server | Bin 0 -> 44688 bytes chatty.c | 831 -------- chatty.h | 70 - chatty2.c | 90 - compile_flags.txt | 5 - external/keyboard.c | 777 -------- external/termbox2.h | 3517 -------------------------------- gdb_scripts | 12 - insert.md | 68 - notes/archived.md | 31 + notes/chatty.md | 76 + notes/insert.md | 68 + notes/scroll.md | 63 + protocol.h | 375 ---- scroll.md | 63 - scrollandwrapped.c | 225 --- send.c | 80 - server.c | 581 ------ source/archived/array.h | 70 + source/archived/input_box.c | 81 + source/archived/network_compression.c | 154 ++ source/archived/scrollandwrapped.c | 225 +++ source/archived/send.c | 80 + source/archived/ui_checkmark.c | 87 + source/archived/ui_meter | Bin 0 -> 101936 bytes source/archived/ui_meter.c | 108 + source/archived/ui_selection.c | 71 + source/archived/ui_wrapped.c | 82 + source/archived/utf8toASCII.c | 163 ++ source/archived/v1/README.md | 58 + source/archived/v1/arena.h | 116 ++ source/archived/v1/build.sh | 6 + source/archived/v1/client.c | 259 +++ source/archived/v1/common.h | 181 ++ source/archived/v1/compile_flags.txt | 5 + source/archived/v1/recv.c | 59 + source/archived/v1/send.c | 61 + source/archived/v1/server.c | 148 ++ source/archived/v1/termbox2.h | 3517 ++++++++++++++++++++++++++++++++ source/archived/wrap.c | 52 + source/arena.h | 63 + source/build.sh | 14 + source/chatty.c | 834 ++++++++ source/chatty.h | 78 + source/protocol.h | 375 ++++ source/server.c | 581 ++++++ source/termbox2.h | 3518 +++++++++++++++++++++++++++++++++ source/ui.h | 840 ++++++++ test.h | 217 -- tests.c | 37 - tests/a.out | Bin 0 -> 78728 bytes tests/build.sh | 12 + tests/test.h | 217 ++ tests/tests | Bin 0 -> 107832 bytes tests/tests.c | 34 + types.h | 15 - ui.h | 819 -------- ui_box.h | 472 ----- ui_wrapped.c | 82 - wrap.c | 52 - 88 files changed, 12435 insertions(+), 13771 deletions(-) create mode 100644 LICENSE delete mode 100644 Makefile delete mode 100644 archived.md delete mode 100644 archived/array.h delete mode 100644 archived/input_box.c delete mode 100644 archived/network_compression.c delete mode 100644 archived/ui_checkmark.c delete mode 100644 archived/ui_meter.c delete mode 100644 archived/ui_selection.c delete mode 100644 archived/utf8toASCII.c delete mode 100644 archived/v1/.gitignore delete mode 100644 archived/v1/README.md delete mode 100644 archived/v1/arena.h delete mode 100755 archived/v1/build.sh delete mode 100644 archived/v1/client.c delete mode 100644 archived/v1/common.h delete mode 100644 archived/v1/compile_flags.txt delete mode 100644 archived/v1/recv.c delete mode 100644 archived/v1/send.c delete mode 100644 archived/v1/server.c delete mode 100644 archived/v1/termbox2.h delete mode 100644 archived/wrap.c delete mode 100644 arena.h delete mode 100755 build.sh create mode 100755 build/chatty create mode 100755 build/input_box create mode 100755 build/server delete mode 100644 chatty.c delete mode 100644 chatty.h delete mode 100644 chatty2.c delete mode 100644 compile_flags.txt delete mode 100644 external/keyboard.c delete mode 100644 external/termbox2.h delete mode 100644 gdb_scripts delete mode 100644 insert.md create mode 100644 notes/archived.md create mode 100644 notes/chatty.md create mode 100644 notes/insert.md create mode 100644 notes/scroll.md delete mode 100644 protocol.h delete mode 100644 scroll.md delete mode 100644 scrollandwrapped.c delete mode 100644 send.c delete mode 100644 server.c create mode 100644 source/archived/array.h create mode 100644 source/archived/input_box.c create mode 100644 source/archived/network_compression.c create mode 100644 source/archived/scrollandwrapped.c create mode 100644 source/archived/send.c create mode 100644 source/archived/ui_checkmark.c create mode 100755 source/archived/ui_meter create mode 100644 source/archived/ui_meter.c create mode 100644 source/archived/ui_selection.c create mode 100644 source/archived/ui_wrapped.c create mode 100644 source/archived/utf8toASCII.c create mode 100644 source/archived/v1/README.md create mode 100644 source/archived/v1/arena.h create mode 100755 source/archived/v1/build.sh create mode 100644 source/archived/v1/client.c create mode 100644 source/archived/v1/common.h create mode 100644 source/archived/v1/compile_flags.txt create mode 100644 source/archived/v1/recv.c create mode 100644 source/archived/v1/send.c create mode 100644 source/archived/v1/server.c create mode 100644 source/archived/v1/termbox2.h create mode 100644 source/archived/wrap.c create mode 100644 source/arena.h create mode 100755 source/build.sh create mode 100644 source/chatty.c create mode 100644 source/chatty.h create mode 100644 source/protocol.h create mode 100644 source/server.c create mode 100644 source/termbox2.h create mode 100644 source/ui.h delete mode 100644 test.h delete mode 100644 tests.c create mode 100755 tests/a.out create mode 100755 tests/build.sh create mode 100644 tests/test.h create mode 100755 tests/tests create mode 100644 tests/tests.c delete mode 100644 types.h delete mode 100644 ui.h delete mode 100644 ui_box.h delete mode 100644 ui_wrapped.c delete mode 100644 wrap.c diff --git a/.gitignore b/.gitignore index dbd414c..f2e627a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,2 @@ -build/* -external/keyboard - -_id -_clients -*.log - -tags +.gitignore +chatty.log diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..666de47 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2025 Luca Raymaekers + +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. \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index aa3eea1..0000000 --- a/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -all: chatty server send - -clean: - rm -f server chatty send tags *.log _* - -chatty: - gcc -ggdb -Wall -pedantic -std=c99 -o chatty chatty.c -server: - gcc -ggdb -Wall -pedantic -std=c99 -o server server.c -send: - gcc -ggdb -Wall -pedantic -std=c99 -o send send.c diff --git a/README.md b/README.md index 565fc2e..c69eb45 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,53 @@ -# Chatty -The idea is the following: -- tcp server that you can send messages to -- history upon connecting -- date of messages sent -- client for reading the messages and sending them at the same time -- rooms -- encryption -- authentication - -## client -- [ ] BUG: text is not appearing after typing -- [ ] BUG: when connecting two clients of the same account -- [ ] BUG: wrapping does not work and displays nothing if there is no screen space -- [ ] BUG: reconnect does not work when server does not know id -- [ ] TODO: Convert tabs to spaces -- [ ] BUG: when using lots of markup characters -- [ ] TODO: Newline support - - [ ] resizable box - -## server -- [ ] check that fds arena does not overflow - - free clients which disconnected and use free list to give them space -- [ ] check if when sending and the client is offline (due to connection loss) what happens -- [ ] timeout on recv? -- [ ] use threads to handle clients/ timeout when receiving because a client could theoretically - stall the entire server. -- [ ] do not crash on errors from clients - - implement error message? - - timeout on recv with setsockopt -- [ ] theoretically two clients can connect at the same time. The uni/bi connections should be - negotiated. - -## common -- [ ] use IP address / domain -- [ ] chat history -- [ ] rooms -- [ ] compression - -## Protocol -- see `protocol.h` for more info - -- The null terminator must be sent with the string. -- The text can be arbitrary length - -## Keybinds +# chatty: The terminal chat application + +## Overview +`chatty` is a terminal chat application. +Included is also a server. + +### Client features +- users are saved +- you can send messages +- you can pause and resume with `Ctrl-Z` and the `fg` command +- messages can have basic markdown formatting +- basic shortcuts for editing the message +- reconnecting on +#### Shortcuts - `Ctrl+C` | `Ctrl+D`: quits - `Ctrl+U`: Erase input line - `Ctrl+W`: Erase word behind cursor - `Ctrl+Y`: Paste clipboard into input field -## Resources I used for building this +### Server features +- multiple users +- recovering on invalid messages +- send "connected"/"disconnected" messages to other clients + +## Build +Run the build script. +```sh +./source/build.sh +``` + +## Try it out +Run the server with +```sh +./build/server +``` +> You can stop it with `Ctrl-D` + +In another prompt, start a client with +```sh +./build/chatty Poulbi +``` + +# Resources +- terminal library: [Termbox2](https://github.com/termbox/termbox2) - source code I looked at: - https://github.com/git-bruh/matrix-tui - https://github.com/NikitaIvanovV/ictree - - https://github.com/termbox/termbox2 - *mmap & gdb*: [Tsoding - "Why linux has this syscall?" ](https://youtu.be/sFYFuBzu9Ow?si=CX32IzFVA8OPDZvS) - *pthreads*: [C for dummies](https://c-for-dummies.com/blog/?p=5365) - *unicode and wide characters*: [C for dummies](https://c-for-dummies.com/blog/?p=2578) - *sockets*: [Nir Lichtman - Making Minimalist Chat Server in C on Linux](https://www.youtube.com/watch?v=gGfTjKwLQxY) - syscall manpages `man` - UTF8 Comprssion: [Casey Muratori - Simple RLE Compressor](https://www.youtube.com/watch?v=kikLEdc3C1c&t=6312s) - -### To Read -#### C Programming -- https://www.youtube.com/watch?v=wvtFGa6XJDU -- https://nullprogram.com/blog/2023/02/11/ -- https://nullprogram.com/blog/2023/02/13/ -- https://nullprogram.com/blog/2023/10/08/ -#### Encryption w/ Compression -- https://en.wikipedia.org/wiki/BREACH -- https://en.wikipedia.org/wiki/CRIME -- https://crypto.stackexchange.com/questions/2283/crypto-compression-algorithms -- openpgp https://www.rfc-editor.org/rfc/rfc4880 -- https://security.stackexchange.com/questions/19911/crime-how-to-beat-the-beast-successor -- https://blog.qualys.com/product-tech/2012/09/14/crime-information-leakage-attack-against-ssltls -- Algorithms: - *Symmetric* - - AESI - - Blowfish - - Twofish - - Rivest Cipher (RC4) - *Assymetric* - - Data Encryption Standard (DES) - - ECDSA - - RSA - - Diffie-Hellman - - PGP - _Hash_ - - Deflate - - Huffman Coding - - LZ77 - Other - - ChaCha20-Poly1305 - - AES(-GCM) diff --git a/archived.md b/archived.md deleted file mode 100644 index 62def2d..0000000 --- a/archived.md +++ /dev/null @@ -1,31 +0,0 @@ -## Client -- [x] prompt -- [x] sending message -- [x] bug: do not allow sending empty message -- [x] wrapping messages -- [x] bug: when sending message after diconnect (serverfd?) -- [x] Handle disconnection thiin a thread, the best way would be -- [x] Add limit_y to printf_wrap -- [x] id2string on clients -- [x] ctrl+z to suspend -- [x] bug: when reconnecting nrecv != -1 -- [x] bug: when disconnecting -- [x] use error type success to say that authentication succeeded -- [x] markup for messages -- [x] clipboard shortcut -- [x] tab as spaces support -- [x] fixed empty messages with markup characters - -## Server -- [x] import clients - -## Common -- [x] handle messages that are too large -- [x] refactor i&self into conn -- [x] logging -- [x] Req|Inf connection per client -- [x] connect/disconnect messages -- [x] bug: blocking after `Added pollfd`, after importing a client and then connecting with the - id/or without? After reconnection fails chatty blocks (remove sleep) -- [x] connect/disconnections messages -- [x] asserting, logging if fail / halt execution diff --git a/archived/array.h b/archived/array.h deleted file mode 100644 index 04d2f38..0000000 --- a/archived/array.h +++ /dev/null @@ -1,70 +0,0 @@ -#include "chatty.h" - -u64 ArrayLength(u8 *Array); -void ArrayInsert(u8 *Array, u64 Position, u8 Element); -void ArrayCopy(u8 *To, u8 *From); -void ArrayDelete(u8* Array, u64 Position); -u8* ArrayCreate(u8* Container, u64 Capacity); - -// EXAMPLE: CREATE -// -// u64 Capacity = 15; -// u8 ArrayContainer[Capacity + sizeof(Capacity)]; -// u8* Array = ArrayCreate(ArrayContainer, Capacity); -// -// EXAMPLE: API -// -// ArrayCopy(Array, (u8*)"Hello, world!"); -// -// ArrayInsert(Array, 3, 'f'); -// ArrayInsert(Array, 3, 'e'); -// Array[14] = 'd'; -// ArrayDelete(Array, 3); - -#ifdef ARRAY_IMPL - -#include -#include - -u64 -ArrayLength(u8 *Array) -{ - return *((u64*)(Array - sizeof(u64))); -} - -void -ArrayInsert(u8 *Array, u64 Position, u8 Element) -{ - memmove(Array + Position + 1, Array + Position, ArrayLength(Array) - Position - 1); - Array[Position] = Element; -} - -// Copy null terminated string without copying over the null terminator -void -ArrayCopy(u8 *To, u8 *From) -{ - u32 i = 0; - while (From[i]) - { - To[i] = From[i]; - i++; - } -} - -void -ArrayDelete(u8* Array, u64 Position) -{ - memmove(Array + Position, Array + Position + 1, ArrayLength(Array) - Position - 1); - Array[ArrayLength(Array) - 1] = 0; -} - -u8* -ArrayCreate(u8* Container, u64 Capacity) -{ - *(u64*)Container = Capacity; - u8 *Array = Container + sizeof(Capacity); - bzero(Array, Capacity); - return Array; -} - -#endif diff --git a/archived/input_box.c b/archived/input_box.c deleted file mode 100644 index 466c03d..0000000 --- a/archived/input_box.c +++ /dev/null @@ -1,70 +0,0 @@ -#define Assert(expr) if (!(expr)) { \ - tb_shutdown(); \ - raise(SIGTRAP); \ -} - -#define TB_IMPL -#include "termbox2.h" -#undef TB_IMPL - -#define TEXTBOX_MAX_INPUT 255 - -#define UI_IMPL -#include "ui.h" - -#define ARENA_IMPL -#include "arena.h" - -#include - -int -main(void) -{ - struct tb_event ev = {0}; - - u32 InputLen = 0; - wchar_t Input[TEXTBOX_MAX_INPUT] = {0}; - rect TextBox = {0, 0, 32, 5}; - rect TextR = { - 2, 1, - TextBox.W - 2*TEXTBOX_PADDING_X - 2*TEXTBOX_BORDER_WIDTH, - TextBox.H - 2*TEXTBOX_BORDER_WIDTH - }; - // Used for scrolling the text. Text before TextOffset will not be printed. - u32 TextOffset = 0; - // Position in input based on cursor position. - u32 InputPos = 0; - - Assert(setlocale(LC_ALL, "")); - tb_init(); - bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); - global.cursor_x = TextR.X; - global.cursor_y = TextR.Y; - - DrawBox(TextBox, 0); - - while (ev.key != TB_KEY_CTRL_C) - { - DrawTextBoxWrapped(TextR, Input + TextOffset, InputLen - TextOffset); - - InputPos = TextOffset + (global.cursor_x - TextR.X) + (global.cursor_y - TextR.Y) * TextR.W; - Assert(InputPos <= InputLen); - - tb_present(); - tb_poll_event(&ev); - - u32 Ret = TextBoxKeypress(ev, TextR, Input, &InputLen, InputPos, &TextOffset); - - u32 ShouldInsert = (!Ret) && (ev.ch && InputLen < TEXTBOX_MAX_INPUT); - // Insert new character in Input at InputPos - if (ShouldInsert) - { - TextBoxInsert(Input, InputPos, InputLen++, ev.ch); - TextBoxScrollRight(TextR, &TextOffset); - } - } - - - tb_shutdown(); - return 0; -} diff --git a/archived/network_compression.c b/archived/network_compression.c deleted file mode 100644 index eef9e3c..0000000 --- a/archived/network_compression.c +++ /dev/null @@ -1,154 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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; - -size_t -GetMaximumCompressedOutputSize(size_t FileSize) -{ - // TODO: figure out equation - return FileSize*2 + 256; -} - -u32 -RLECompress(size_t InSize, u8* In, size_t MaxOutSize, u8* OutBase) -{ - u8* Out = OutBase; - -#define MAX_LITERAL_COUNT 255 -#define MAX_RUN_COUNT 255 - u32 LiteralCount = 0; - u8 Literals[MAX_LITERAL_COUNT] = {0}; - - u8 *InEnd = In + InSize; - while(In < InEnd) - { - u8 StartingValue = In[0]; - size_t Run = 1; // first one is the character itself - while((Run < (InEnd - In)) && - (Run < MAX_RUN_COUNT) && - (In[Run] == StartingValue)) - { - ++Run; - } - - if ((Run > 1) || - (LiteralCount == MAX_LITERAL_COUNT)) // stop doing runs when there is no - // space left in the buffer. - { - // Encode a literal/run pair - u8 LiteralCount8 = (u8)LiteralCount; - assert(LiteralCount8 == LiteralCount); - *Out++ = LiteralCount8; - - for(u32 LiteralIndex = 0; - LiteralIndex < LiteralCount; - ++LiteralIndex) - { - *Out++ = Literals[LiteralIndex]; - } - LiteralCount = 0; - - u8 Run8 = (u8)Run; - assert(Run8 == Run); - *Out++ = Run8; - - *Out++ = StartingValue; - - In += Run; - } - else - { - // Buffer literals, you have to because we are encoding them in pairs - Literals[LiteralCount++] = StartingValue; - ++In; - } - - } -#undef MAX_LITERAL_COUNT -#undef MAX_RUN_COUNT - - assert(In == InEnd); - - size_t OutSize = Out - OutBase; - assert(OutSize <= MaxOutSize); - - return OutSize; -} - -void -RLEDecompress(size_t InSize, u8* In, size_t OutSize, u8* Out) -{ - u8 *InEnd = In + InSize; - while(In < InEnd) - { - // TODO: I think Casey made a mistake and this should be an u8 - u8 LiteralCount = *In++; - while(LiteralCount--) - { - *Out++ = *In++; - } - - // Alternate to Run - u8 RepCount = *In++; - u8 RepValue = *In++; - while(RepCount--) - { - *Out++ = RepValue; - } - } - - assert(In == InEnd); -} - -int main(int Argc, char* Argv[]) { - - if (Argc < 2) - { - fprintf(stderr, "Usage: %s [compress|decompress]\n", Argv[0]); - return 1; - } - - char* Command = Argv[1]; - - if (!strcmp(Command, "compress")) - { - u32* Message = (u32*)L"abcabcbbbbbbb"; - size_t MessageSize = wcslen((const wchar_t*)Message) * 4; - - size_t OutBufferSize = GetMaximumCompressedOutputSize(MessageSize); - u8 OutBuffer[OutBufferSize + 8]; - bzero(OutBuffer, OutBufferSize + 8); - *(u32*)OutBuffer = MessageSize; - - size_t CompressedSize = RLECompress(MessageSize, (u8*)Message, OutBufferSize, OutBuffer + 8); - - s32 OutFile = open("test.compressed", O_WRONLY | O_CREAT, 0600); - - write(OutFile, OutBuffer, CompressedSize); - - fprintf(stdout, "%lu -> %lu bytes\n", MessageSize, CompressedSize); - } - else if (!strcmp(Command, "decompress")) - { - fprintf(stderr, "Not implemented yet.\n"); - } - else - fprintf(stderr, "Unknown command: '%s'\n", Command); - - return 0; -} diff --git a/archived/ui_checkmark.c b/archived/ui_checkmark.c deleted file mode 100644 index df7e507..0000000 --- a/archived/ui_checkmark.c +++ /dev/null @@ -1,87 +0,0 @@ -#define TB_IMPL -#include "../chatty/external/termbox2.h" - -int -main(void) -{ - struct tb_event ev = {0}; - - typedef struct { - int X, Y; - int Checked; - } Checkmark; - -#define NUM_CHECKMARKS 4 - int Y = 0; - Checkmark Marks[NUM_CHECKMARKS] = { - {0, Y++, 0}, - {0, Y++, 0}, - {0, Y++, 1}, - {0, Y++, 0} - }; - Y++; - - int Selected = 0; - - tb_init(); - - int Quit = 0; - while (!Quit) - { - tb_clear(); - - for (int CheckmarkIndex = 0; - CheckmarkIndex < NUM_CHECKMARKS; - CheckmarkIndex++) - { - Checkmark Mark = Marks[CheckmarkIndex]; - if (Mark.Checked) - { - tb_printf(Mark.X, Mark.Y, 0, 0, "[x]"); - } - else - { - tb_printf(Mark.X, Mark.Y, 0, 0, "[ ]"); - } - } - Checkmark Mark = Marks[Selected]; - if (Mark.Checked) - { - tb_set_cell(Mark.X + 1, Mark.Y, L'x', TB_UNDERLINE, 0); - } - else - { - tb_set_cell(Mark.X + 1, Mark.Y, L' ', TB_UNDERLINE, 0); - } - - int BaseY = Y; - tb_printf(0, BaseY, TB_BOLD, 0, "j"); tb_printf(2, BaseY++, 0, 0, "next"); - tb_printf(0, BaseY, TB_BOLD, 0, "k"); tb_printf(2, BaseY++, 0, 0, "previous"); - tb_printf(0, BaseY, TB_BOLD, 0, "c"); tb_printf(2, BaseY++, 0, 0, "toggle"); - tb_printf(0, BaseY, TB_BOLD, 0, "q"); tb_printf(2, BaseY++, 0, 0, "quit"); - - tb_present(); - - tb_poll_event(&ev); - if (ev.ch == 'q') - { - Quit = 1; - } - else if (ev.ch == 'j') - { - if (Selected == NUM_CHECKMARKS - 1) Selected = 0; - else Selected++; - } - else if (ev.ch == 'k') - { - if (Selected) Selected--; - else Selected = NUM_CHECKMARKS - 1; - } - else if (ev.ch == 'c') - { - Marks[Selected].Checked = !Marks[Selected].Checked; - } - } - - tb_shutdown(); -} diff --git a/archived/ui_meter.c b/archived/ui_meter.c deleted file mode 100644 index 8636ba4..0000000 --- a/archived/ui_meter.c +++ /dev/null @@ -1,108 +0,0 @@ -#define TB_IMPL -#include "../chatty/external/termbox2.h" - -#include -#include -#include -#include - -#define Assert(expr) \ - if (!(expr)) \ - { \ - tb_shutdown(); \ - raise(SIGTRAP); \ - } - -#define METER_WIDTH 4 - -static char ***EnvironmentPath = 0; - -void -draw_meter(int X, int Y, int Height, int MaxLevel, int Level) -{ - int FillColor = 0; - - Height -= 2; // substract top and bottom border - - tb_printf(X, Y, 0, 0, "%ls", L"┌──┐"); - tb_printf(X, Y + Height + 1, 0, 0, "%ls", L"└ ┘"); - - if (Level == MaxLevel) - { - FillColor = TB_GREEN; - tb_printf(X + 1, Y + Height + 1, 0, 0, "00"); - } - else - { - FillColor = TB_CYAN; - tb_printf(X + 1, Y + Height + 1, 0, 0, "%02d", Level); - } - - int FilledLevel = (Level * Height)/MaxLevel; - - for (int LevelIndex = 0; LevelIndex < FilledLevel; LevelIndex++) - { - int YLevel = Y + (Height - LevelIndex); - tb_set_cell(X + 1, YLevel, L'▒', FillColor, FillColor); - tb_set_cell(X + 2, YLevel, L'▒', FillColor, FillColor); - } - - for (int YOffset = 0; YOffset < Height; YOffset++) - { - tb_set_cell(X + 0, Y + YOffset + 1, L'│', 0, 0); - tb_set_cell(X + 3, Y + YOffset + 1, L'│', 0, 0); - } - -} - -int -main(int Argc, char *Args[], char *Envp[]) -{ - EnvironmentPath = &Envp; - - struct tb_event ev = {0}; - Assert(setlocale(LC_ALL, "")); - int Quit = 0; - int Level = 0; - int LevelStep = 10; - int MeterMaxLevel = 100; - int MeterHeight = 10 + 2; - - tb_init(); - tb_set_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE); - - while (!Quit) - { - tb_clear(); - - int Y = 0; - draw_meter(1, Y, MeterHeight, MeterMaxLevel, Level); - Y += MeterHeight + 1;; - - tb_print(0, Y, TB_BOLD, 0, "j"); tb_print(2, Y++, 0, 0, "decrease"); - tb_print(0, Y, TB_BOLD, 0, "k"); tb_print(2, Y++, 0, 0, "increase"); - tb_print(0, Y, TB_BOLD, 0, "q"); tb_print(2, Y++, 0, 0, "to quit"); - - tb_present(); - - tb_poll_event(&ev); - - if (ev.ch == 'q' || ev.key == TB_KEY_CTRL_C) Quit = 1; - else if ((ev.ch == 'j' || ev.key == TB_KEY_MOUSE_WHEEL_DOWN) && - Level > 0) - { - if (LevelStep > Level) Level = 0; - else Level -= LevelStep; - } - else if ((ev.ch == 'k' || ev.key == TB_KEY_MOUSE_WHEEL_UP) && - Level < MeterMaxLevel) - { - if (Level + LevelStep > MeterMaxLevel) Level = MeterMaxLevel; - else Level += LevelStep; - } - - } - - tb_shutdown(); - return 0; -} diff --git a/archived/ui_selection.c b/archived/ui_selection.c deleted file mode 100644 index 3e45ee2..0000000 --- a/archived/ui_selection.c +++ /dev/null @@ -1,71 +0,0 @@ -#define TB_IMPL -#include "../chatty/external/termbox2.h" - -#include - -#define Assert(expr) if (!(expr)) raise(SIGTRAP) - -typedef struct { - int X, Y; -} Position; - -int -main(void) -{ - int Selected = 0; - int NumChoices = 3; - int Y = 0; - - Position Positions[NumChoices]; - Positions[0] = (Position){1, Y}; - Positions[1] = (Position){5, Y}; - Positions[2] = (Position){9, Y}; - Y += 2; - - struct tb_event ev = {0}; - int Color = TB_GREEN; - - tb_init(); - - int Quit = 0; - while (!Quit) - { - tb_clear(); - - int BaseY = Y; - tb_printf(0, BaseY, TB_BOLD, 0, "j"); tb_printf(2, BaseY++, 0, 0, "select next"); - tb_printf(0, BaseY, TB_BOLD, 0, "k"); tb_printf(2, BaseY++, 0, 0, "select previous"); - tb_printf(0, BaseY, TB_BOLD, 0, "s"); tb_printf(2, BaseY++, 0, 0, "change color"); - tb_printf(0, BaseY, TB_BOLD, 0, "q"); tb_printf(2, BaseY++, 0, 0, "quit"); - - // Draw a box at position - for (int PositionsIndex = 0; PositionsIndex < NumChoices; PositionsIndex++) - { - Assert(Positions[PositionsIndex].X > 0); - tb_printf(Positions[PositionsIndex].X - 1, Positions[PositionsIndex].Y, Color, 0, "[ ]"); - } - // Draw mark in selected box - tb_printf(Positions[Selected].X, Positions[Selected].Y, Color, 0, "x"); - - tb_present(); - tb_poll_event(&ev); - - if (ev.ch == 'q') Quit = 1; - else if (ev.ch == 'j' && Selected < NumChoices - 1) Selected++; - else if (ev.ch == 'k' && Selected) Selected--; - else if (ev.ch == 's') - { - switch (Selected) - { - case 0: Color = TB_GREEN; break; - case 1: Color = TB_BLUE; break; - case 2: Color = TB_RED; break; - default: Assert(0); break; - } - } - } - - tb_shutdown(); - - return 0; -} diff --git a/archived/utf8toASCII.c b/archived/utf8toASCII.c deleted file mode 100644 index 988a69b..0000000 --- a/archived/utf8toASCII.c +++ /dev/null @@ -1,163 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -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; - -static size_t -UTF8Compress(size_t InSize, wchar_t* In, size_t OutSize, u8* OutBase) -{ - wchar_t* InEnd = (wchar_t*)((u8*)In + InSize); - u8* Out = OutBase; - -#define MAX_LITERAL_COUNT 255 - u8 ASCIILiterals[MAX_LITERAL_COUNT] = {0}; - u8 ASCIILiteralsCount = 0; - wchar_t UTF8Literals[MAX_LITERAL_COUNT] = {0}; - u8 UTF8LiteralsCount = 0; - - while (In < InEnd) - { - wchar_t CurrentChar = In[0]; - - // Check consecutive ascii characters - while(CurrentChar == (u8)CurrentChar && - ASCIILiteralsCount < MAX_LITERAL_COUNT) - { - ASCIILiterals[ASCIILiteralsCount++] = (u8)CurrentChar; - CurrentChar = *++In; - } - - while(CurrentChar != (u8)CurrentChar && - UTF8LiteralsCount < MAX_LITERAL_COUNT) - { - UTF8Literals[UTF8LiteralsCount++] = CurrentChar; - CurrentChar = *++In; - } - - // Encode ASCII/UTF8 pair - *Out++ = ASCIILiteralsCount; - for (u8 ch = 0; - ch < ASCIILiteralsCount; - ch++) - { - *Out = ASCIILiterals[ch]; - Out += sizeof(ASCIILiterals[ch]); - } - ASCIILiteralsCount = 0; - - *Out++ = UTF8LiteralsCount; - for (u8 ch = 0; - ch < UTF8LiteralsCount; - ch++) - { - *(wchar_t*)Out = UTF8Literals[ch]; - Out += sizeof(UTF8Literals[ch]); - } - UTF8LiteralsCount = 0; - - } -#undef MAX_LITERAL_COUNT - assert(In == InEnd); - - return Out - OutBase; -} - -static void -PrintCompressedUTF8(u8* In, size_t InSize) -{ - u8* InEnd = In + InSize; - - while (In < InEnd) - { - u8 ASCIICount = *In++; - wprintf(L"%dA(\"", ASCIICount); - while(ASCIICount--) - { - wprintf(L"%c", *In); - In += sizeof(u8); - } - wprintf(L"\") "); - - u8 UTF8Count = *In++; - wprintf(L"%dU(\"", UTF8Count); - while(UTF8Count--) - { - wprintf(L"%lc", *(wchar_t*)In); - In += sizeof(wchar_t); - } - wprintf(L"\") "); - } - wprintf(L"\n"); - - assert(In == InEnd); -} - -static void -UTF8Decompress(size_t InSize, u8* In, size_t OutSize, wchar_t* Out) -{ - u8* InEnd = In + InSize; - - while (In < InEnd) - { - u8 ASCIICount = *In++; - while(ASCIICount--) - { - *Out++ = *In++; - } - - u8 UTF8Count = *In++; - while(UTF8Count--) - { - *Out++ = *(wchar_t*)In; - In += sizeof(wchar_t); - } - } - assert(In == InEnd); -} - -// Size is the size of the UTF8 string in bytes. "aaa" would be 12. -size_t -UTF8GetMaximumCompressedSize(size_t Size) -{ - // The largest would be if there was only one unicode point in which case we store 0 for ascii 1 - // for unicode and the raw codepoint. 1 + 1 + 4 * CodepointNum - return Size + 2; -} - -int -main(int Argc, char* Argv[]) { - assert(setlocale(LC_ALL, "") != 0); - - wchar_t* InBuf = L"text│tt│"; - size_t InSize = wcslen(InBuf) * 4; - - size_t OutSize = UTF8GetMaximumCompressedSize(InSize); - u8 OutBuf[OutSize]; - - size_t CompressedSize = UTF8Compress(InSize, InBuf, OutSize, OutBuf); - - fwprintf(stderr, L"Raw string: \"%ls\"\n", InBuf); - fwprintf(stderr, L"Compressed %lu bytes -> %lu bytes.\n", InSize, CompressedSize); - - size_t DecompressedSize = InSize; - wchar_t *DecompressedBuffer = malloc(DecompressedSize); - - UTF8Decompress(CompressedSize, OutBuf, DecompressedSize, DecompressedBuffer); - fwprintf(stderr, L"Decompressed: \"%ls\"\n", DecompressedBuffer); - - PrintCompressedUTF8(OutBuf, CompressedSize); - - return 0; -} diff --git a/archived/v1/.gitignore b/archived/v1/.gitignore deleted file mode 100644 index a9ac961..0000000 --- a/archived/v1/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -chatty -server -recv -send -client - -tags diff --git a/archived/v1/README.md b/archived/v1/README.md deleted file mode 100644 index 2cf0aee..0000000 --- a/archived/v1/README.md +++ /dev/null @@ -1,58 +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 -- [ ] 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/archived/v1/arena.h b/archived/v1/arena.h deleted file mode 100644 index 6f371f4..0000000 --- a/archived/v1/arena.h +++ /dev/null @@ -1,116 +0,0 @@ -#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/archived/v1/build.sh b/archived/v1/build.sh deleted file mode 100755 index a80b193..0000000 --- a/archived/v1/build.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/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/archived/v1/client.c b/archived/v1/client.c deleted file mode 100644 index 878b678..0000000 --- a/archived/v1/client.c +++ /dev/null @@ -1,259 +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 -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/archived/v1/common.h b/archived/v1/common.h deleted file mode 100644 index c7bb0dd..0000000 --- a/archived/v1/common.h +++ /dev/null @@ -1,181 +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 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/archived/v1/compile_flags.txt b/archived/v1/compile_flags.txt deleted file mode 100644 index 1a62790..0000000 --- a/archived/v1/compile_flags.txt +++ /dev/null @@ -1,5 +0,0 @@ --Wall --Werror --pedantic --std=c99 --O3 diff --git a/archived/v1/recv.c b/archived/v1/recv.c deleted file mode 100644 index 042d8a9..0000000 --- a/archived/v1/recv.c +++ /dev/null @@ -1,59 +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)); - 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/archived/v1/send.c b/archived/v1/send.c deleted file mode 100644 index 27e9793..0000000 --- a/archived/v1/send.c +++ /dev/null @@ -1,61 +0,0 @@ -// 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/archived/v1/server.c b/archived/v1/server.c deleted file mode 100644 index 7edf558..0000000 --- a/archived/v1/server.c +++ /dev/null @@ -1,148 +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 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/archived/v1/termbox2.h b/archived/v1/termbox2.h deleted file mode 100644 index 265cdab..0000000 --- a/archived/v1/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/archived/wrap.c b/archived/wrap.c deleted file mode 100644 index 2317c6e..0000000 --- a/archived/wrap.c +++ /dev/null @@ -1,52 +0,0 @@ -// 1. Search backwards for whitespace -// - found? -// y) wrap -// n) break at limit -// - end? -// y) terminate -// n) goto 1. with offset += limit -void -wrap(u8* Text, u32 Len, u32 XLimit, u32 YLimit) -{ - u32 SearchingOffset = XLimit; - u32 X = SearchingOffset; - u32 Y = 0; - u8 t; - u32 PrevX = 0; - - while (X < Len) - { - // Search for whitespace to break on - while (1) - { - if (is_whitespace(Text[X])) break; - - X--; - - // if we got back to the previous position break on Text[SearchingOffset] - if (X == PrevX) - { - X = XLimit; - break; - } - } - - // break - t = Text[X]; - Text[X] = '\0'; - tb_printf(0, Y, 0, 0, "%s", Text + PrevX); - Text[X] = t; - Y++; - if (Y >= YLimit) break; - - // consume leading whitespace - while (is_whitespace(Text[X])) X++; - - PrevX = X; - X += XLimit; - } - - tb_printf(0, Y, 0, 0, "%s", Text + PrevX); - - return; -} diff --git a/arena.h b/arena.h deleted file mode 100644 index f3d21d7..0000000 --- a/arena.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef ARENA_H -#define ARENA_H - -#include -#include -#include "types.h" - -// Arena Allocator -typedef struct { - void* addr; - u64 size; - u64 pos; -} Arena; -#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) - -void ArenaAlloc(Arena* arena, u64 size); -void ArenaRelease(Arena* arena); -void* ArenaPush(Arena* arena, u64 size); - -#endif // ARENA_H - -#ifdef ARENA_IMPL - -// Returns arena in case of success, or 0 if it failed to alllocate the memory -void -ArenaAlloc(Arena* arena, u64 size) -{ - arena->addr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - Assert(arena->addr != MAP_FAILED); - arena->pos = 0; - arena->size = size; -} - -void -ArenaRelease(Arena* arena) -{ - munmap(arena->addr, arena->size); -} - -void* -ArenaPush(Arena* arena, u64 size) -{ - u8* mem; - mem = (u8*)arena->addr + arena->pos; - arena->pos += size; - Assert(arena->pos <= arena->size); - return mem; -} - -#endif // ARENA_IMPL diff --git a/build.sh b/build.sh deleted file mode 100755 index 50572d0..0000000 --- a/build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -set -x -# gcc external/keyboard.c -gcc -DDEBUG -ggdb -Wall -pedantic -std=c11 -I external -o build/chatty chatty.c -gcc -DDEBUG -ggdb -Wall -pedantic -std=c99 -o build/server server.c -# gcc -DDEBUG -ggdb -Wall -pedantic -std=c99 -o build/send send.c -gcc -DDEBUG -ggdb -Wall -pedantic -std=c11 -I external -I . -o build/input_box archived/input_box.c diff --git a/build/chatty b/build/chatty new file mode 100755 index 0000000..06f4379 Binary files /dev/null and b/build/chatty differ diff --git a/build/input_box b/build/input_box new file mode 100755 index 0000000..466a38c Binary files /dev/null and b/build/input_box differ diff --git a/build/server b/build/server new file mode 100755 index 0000000..90d2d33 Binary files /dev/null and b/build/server differ diff --git a/chatty.c b/chatty.c deleted file mode 100644 index 3300ce0..0000000 --- a/chatty.c +++ /dev/null @@ -1,831 +0,0 @@ -#define TB_IMPL -#include "external/termbox2.h" -#undef TB_IMPL - -#include -#include -#include -#include -#include -#include - -#define TIMEOUT_POLL 60 * 1000 -// time to reconnect in seconds -#define TIMEOUT_RECONNECT 1 -#define MAX_INPUT_LEN 512 -// Filepath where user ID is stored -#define ID_FILE "_id" -// Filepath where logged -#define LOGFILE "chatty.log" -// enable logging -#define LOGGING -// Number of spaces inserted when pressing Tab/Ctrl+I -#define TAB_WIDTH 4 - -#ifndef Assert -#ifdef DEBUG -#define Assert(expr) if (!(expr)) \ - { \ - tb_shutdown(); \ - raise(SIGTRAP); \ - } -#else -#define Assert(expr) ; -#endif // DEBUG -#endif // Assert - -#define CHATTY_IMPL -#include "chatty.h" -#include "protocol.h" - -#define TEXTBOX_MAX_INPUT MAX_INPUT_LEN -#include "ui.h" - -#define ARENA_IMPL -#include "arena.h" - -enum { FDS_BI = 0, // for one-way communication with the server (eg. TextMessage) - FDS_UNI, // For two-way communication with the server (eg. IDMessage) - FDS_TTY, - FDS_RESIZE, - FDS_MAX }; - -typedef struct { - u8 Author[AUTHOR_LEN]; - ID ID; -} User; -#define USER_FMT "[%s](%lu)" -#define USER_ARG(client) client.Author, client.ID - -typedef struct { - s32 NumRead; - u32 Error; -} command_output; - -// User used by chatty -global_variable User user = {0}; -// Address of chatty server -global_variable struct sockaddr_in address; - -// fill str array with char -void -fillstr(u32* Str, u32 ch, u32 Len) -{ - for (u32 i = 0; i < Len; i++) - Str[i] = ch; -} - -// Centered popup displaying message in the appropriate cololrs -void -popup(u32 fg, u32 bg, u8* text) -{ - u32 len = strlen((char*)text); - Assert(len > 0); - tb_print(global.width / 2 - len / 2, global.height / 2, fg, bg, (char*)text); -} - -// Returns client in clientsArena matching id -// Returns user if the id was the user's ID -// Returns 0 if nothing was found -User* -get_user_by_id(Arena* clientsArena, ID id) -{ - // User is not in the clientsArena - if (id == user.ID) return &user; - - User* clients = clientsArena->addr; - for (u64 i = 0; i < (clientsArena->pos / sizeof(*clients)); i++) - { - if (clients[i].ID == id) - return clients + i; - } - return 0; -} - -// Request information of client from fd byd id and add it to clientsArena -// Returns pointer to added client -User* -add_user_info(Arena* clientsArena, s32 fd, u64 id) -{ - // Request information about ID - HeaderMessage header = HEADER_INIT(HEADER_TYPE_ID); - header.id = user.ID; - IDMessage message = {id}; - s32 nsend = sendAnyMessage(fd, header, &message); - Assert(nsend != -1); - - // Wait for response - IntroductionMessage introduction_message; - recvAnyMessageType(fd, &header, &introduction_message, HEADER_TYPE_INTRODUCTION); - - // Add the information - User* client = ArenaPush(clientsArena, sizeof(*client)); - memcpy(client->Author, introduction_message.author, AUTHOR_LEN); - client->ID = id; - - LoggingF("Got " USER_FMT "\n", USER_ARG((*client))); - return client; -} - -// Tries to connect to address and populates resulting file descriptors in ConnectionResult. -s32 -get_connection(struct sockaddr_in* address) -{ - s32 fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd == -1) return -1; - - s32 err = connect(fd, (struct sockaddr*)address, sizeof(*address)); - if (err) return -1; - - return fd; -} - -// Authenticates a file descriptor with either the user's id if non-zero or -// it's information if id is zero. -// Returns 0 if an error occurred. Non-zero on success. -u32 -authenticate(User* user, s32 fd) -{ - /* Scenario 1: Already have an ID */ - if (user->ID) - { - HeaderMessage header = HEADER_INIT(HEADER_TYPE_ID); - IDMessage message = {user->ID}; - s32 nsend = sendAnyMessage(fd, header, &message); - Assert(nsend != -1); - - ErrorMessage error_message; - s32 nrecv = recvAnyMessageType(fd, &header, &error_message, HEADER_TYPE_ERROR); - Assert(nrecv != -1); - // TODO: handle not found - if (nrecv == 0) - return 0; - - if (error_message.type == ERROR_TYPE_SUCCESS) - return 1; - else - return 0; - } - /* Scenario 2: No ID, request one from server */ - else - { - HeaderMessage header = HEADER_INIT(HEADER_TYPE_INTRODUCTION); - IntroductionMessage message; - memcpy(message.author, user->Author, AUTHOR_LEN); - s32 nsend = sendAnyMessage(fd, header, &message); - Assert(nsend != -1); - - IDMessage id_message; - s32 nrecv = recvAnyMessageType(fd, &header, &id_message, HEADER_TYPE_ID); - Assert(nrecv != -1); - user->ID = id_message.id; - return 1; - } -} - -// Connect to *address_ptr of type `struct sockaddr_in*`. If it failed wait for TIMEOUT_RECONNECT -// seconds. -// This function is meant to be run by a thread. -// An offline server means fds[FDS_SERVER] is set to -1. When online -// it is set to with the appropriate file descriptor. -// Returns 0. -#define Miliseconds(s) (s*1000*1000) -void* -thread_reconnect(void* fds_ptr) -{ - s32 unifd, bifd; - struct pollfd* fds = fds_ptr; - struct timespec t = { 0, Miliseconds(300) }; // 300 miliseconds - LoggingF("Trying to reconnect\n"); - while (1) - { - // timeout - nanosleep(&t, &t); - - bifd = get_connection(&address); - if (bifd == -1) - { - LoggingF("errno: %d\n", errno); - continue; - } - unifd = get_connection(&address); - if (unifd == -1) - { - LoggingF("errno: %d\n", errno); - close(bifd); - continue; - } - - LoggingF("Reconnect succeeded (%d, %d), authenticating\n", unifd, bifd); - - if (authenticate(&user, bifd) && - authenticate(&user, unifd)) - { - break; - } - - close(bifd); - close(unifd); - - LoggingF("Failed, retrying...\n"); - } - - fds[FDS_BI].fd = bifd; - fds[FDS_UNI].fd = unifd; - - // Redraw screen - raise(SIGWINCH); - - return 0; -} - -command_output -run_command_get_output(char *Command, char *Argv[], u8 *OutputBuffer, int Len) -{ - command_output Result = {0}; - - int CommandPipe[2]; - int Error = pipe(CommandPipe); - Assert(Error != -1); - - int Pid = fork(); - Assert(Pid != -1); - - // Run command in child - if (!Pid) - { - dup2(CommandPipe[1], STDOUT_FILENO); //redirect stdout to Pipe - close(CommandPipe[0]); - close(CommandPipe[1]); - - int fd = open("/dev/null", O_WRONLY); - dup2(fd, STDERR_FILENO); - - execvp(Command, Argv); - } - - // Wait for child - int statval; - waitpid(Pid, &statval, 0); - - if(WIFEXITED(statval)) - { - int ExitCode = WEXITSTATUS(statval); - if (ExitCode) - { - Result.Error = ExitCode; - } - } - else - { - Result.Error = 1; - return Result; - } - - close(CommandPipe[1]); - - Result.NumRead = read(CommandPipe[0], OutputBuffer, Len); - Assert(Result.NumRead != -1); - - return Result; -} - -// home screen, the first screen the user sees -// it displays a prompt with the user input of input_len wide characters -// and the received messages from msgsArena -void -DisplayChat(Arena* ScratchArena, - Arena* MessagesArena, u32 MessagesNum, - Arena* ClientsArena, struct pollfd* fds, - wchar_t Input[], u32 InputLen) -{ - rect TextBox = { - 1, 0, global.width - 2, 3, - }; - u32 FreeHeight = global.height - TextBox.H; - TextBox.Y = FreeHeight; - -#define MIN_TEXT_WIDTH_FOR_WRAPPING 20 - s32 MinBoxWidth = TEXTBOX_MIN_WIDTH; - s32 InputBoxTextWidth = TextBox.W - MinBoxWidth + 2; - bool ShouldIncreaseSize = ( - (s32)InputLen >= InputBoxTextWidth && - InputBoxTextWidth > MIN_TEXT_WIDTH_FOR_WRAPPING - ); - if (ShouldIncreaseSize) - { - TextBox.H++; - } -#undef MIN_TEXT_WIDTH_FOR_WRAPPING - - rect TextR = { - TextBox.X + 2, TextBox.Y + 1, - TextBox.W - 2*TEXTBOX_PADDING_X - 2*TEXTBOX_BORDER_WIDTH, - TextBox.H - 2*TEXTBOX_BORDER_WIDTH - }; - - if (global.height < TextBox.H || global.width < TextBox.W) - { - tb_hide_cursor(); - return; - } - - bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); - global.cursor_x = TextR.X; - global.cursor_y = TextR.Y; - DrawBox(TextBox, 0); - - // InputBox(TextBox, Input, InputLen, True); - - // Print vertical bar - s32 VerticalBarOffset = TIMESTAMP_LEN + AUTHOR_LEN + 2; - for (u32 Y = 0; Y < FreeHeight; Y++) - tb_print(VerticalBarOffset, Y, 0, 0, "│"); - - // show error popup if server disconnected - if (fds[FDS_UNI].fd == -1 || fds[FDS_BI].fd == -1) - { - popup(TB_RED, TB_BLACK, (u8*)"Server disconnected."); - } - - // Print messages in msgsArena, if there are too many to display, start printing from an offset. - // Looks like this: - // 03:24:29 [1234567890ab] hello homes how are - // you doing? - // 03:24:33 [TlasT] │ I am fine - // 03:24:33 [Fin] │ I am too - { - - // If there is not enough space to draw, do not draw - if (FreeHeight <= 0) return; - - // Used to go to the next message in MessagesArena by incrementing with the messages' size. - u8* MessageAddress = MessagesArena->addr; - Assert(MessageAddress != 0); - - // Skip messages if there is not enough space to display them all - u32 MessagesOffset = (MessagesNum > FreeHeight) ? MessagesNum - FreeHeight : 0; - for (u32 MessageIndex = 0; MessageIndex < MessagesOffset; MessageIndex++) - { - HeaderMessage* header = (HeaderMessage*)MessageAddress; - MessageAddress += sizeof(*header); - - switch (header->type) - { - case HEADER_TYPE_TEXT: - { - TextMessage* message = (TextMessage*)MessageAddress; - MessageAddress += TEXTMESSAGE_SIZE; - MessageAddress += message->len * sizeof(*message->text); - break; - } - case HEADER_TYPE_PRESENCE: - MessageAddress += sizeof(PresenceMessage); - break; - case HEADER_TYPE_HISTORY: - MessageAddress += sizeof(HistoryMessage); - break; - default: - // unhandled message type - Assert(0); - } - } - - u32 MessageY = 0; - - for (u32 i = MessagesOffset; - i < MessagesNum; - i++) - { - if (MessageY >= FreeHeight) break; - - HeaderMessage* header = (HeaderMessage*)MessageAddress; - MessageAddress += sizeof(*header); - - User* client = get_user_by_id(ClientsArena, header->id); - if (!client) - { - LoggingF("User not known, requesting from server\n"); - client = add_user_info(ClientsArena, fds[FDS_BI].fd, header->id); - } - Assert(client); - - switch (header->type) - { - case HEADER_TYPE_TEXT: - { - TextMessage* message = (TextMessage*)MessageAddress; - - - // Color own messages - u32 fg = 0; - if (user.ID == header->id) - { - fg = TB_CYAN; - } - else - { - fg = TB_MAGENTA; - } - - // prefix is of format "HH:MM:SS [] ", create it - u8 timestamp[TIMESTAMP_LEN]; - formatTimestamp(timestamp, message->timestamp); - - tb_printf(0, MessageY, TB_WHITE, 0, "%s", timestamp); - tb_printf(TIMESTAMP_LEN, MessageY, fg, 0, "[%s]", client->Author); - - // Only display when there is enough space - if (global.width > VerticalBarOffset + 2) - { - raw_result RawText = markdown_to_raw(ScratchArena, (wchar_t*)&message->text, message->len); - markdown_formatoptions MDFormat = preprocess_markdown(ScratchArena, - (wchar_t*)&message->text, - message->len); - - u32 timesWrapped = tb_print_wrapped_with_markdown(VerticalBarOffset + 2, MessageY, fg, 0, - RawText.Text, RawText.Len, - global.width, global.height, MDFormat); - - // Free the memory - ScratchArena->pos = 0; - - MessageY += timesWrapped; - } - else - { - // We still displayed the timestamp so we need to increment the Y. - MessageY++; - } - - u32 message_size = TEXTMESSAGE_SIZE + message->len * sizeof(*message->text); - MessageAddress += message_size; - } break; - case HEADER_TYPE_PRESENCE: - { - PresenceMessage* message = (PresenceMessage*)MessageAddress; - tb_printf(TIMESTAMP_LEN, MessageY, TB_MAGENTA, 0, "[%s]", client->Author); - - // Wrap Text in '*' - u8 *Text = presenceTypeString(message->type); - u32 Len = 0; - while(Text[Len]) Len++; - u32 FormattedText[Len+2]; - FormattedText[0] = '*'; - FormattedText[Len+1] = '*'; - for (u32 i = 1; i < Len + 1; i++) FormattedText[i] = Text[i-1]; - - tb_print_markdown(VerticalBarOffset + 2, MessageY, 0, 0, FormattedText, Len + 2); - - MessageY++; - MessageAddress += sizeof(*message); - } break; - case HEADER_TYPE_HISTORY: - { - HistoryMessage* message = (HistoryMessage*)MessageAddress; - MessageAddress += sizeof(*message); - // TODO: implement - } break; - default: - tb_printf(0, MessageY, 0, 0, "%s", headerTypeString(header->type)); - MessageY++; - break; - } - } - - } -} - -int -main(int argc, char** argv) -{ - if (argc < 2) - { - fprintf(stderr, "usage: chatty \n"); - return 1; - } - - u32 arg_len = strlen(argv[1]); - Assert(arg_len <= AUTHOR_LEN - 1); - memcpy(user.Author, argv[1], arg_len); - user.Author[arg_len] = '\0'; - - s32 err = 0; // error code for functions - - u32 MessagesNum = 0; // Number of messages in msgsArena - s32 nrecv = 0; // number of bytes received - - wchar_t Input[MAX_INPUT_LEN] = {0}; // input buffer - u32 InputIndex = 0; // number of characters in input - - Arena ScratchArena; - Arena MessagesArena; - Arena ClientsArena; - ArenaAlloc(&MessagesArena, Megabytes(64)); // Messages received & sent - ArenaAlloc(&ClientsArena, Megabytes(1)); // Arena for storing clients - ArenaAlloc(&ScratchArena, Megabytes(1)); // Arena for storing clients - - struct tb_event ev; // event fork keypress & resize - u8 quit = 0; // boolean to indicate if we want to quit the main loop - u8* quitmsg = 0; // this string will be printed before returning from main - - pthread_t thr_rec; // thread for reconnecting to server when disconnected - -#ifdef LOGGING - LogFD = open(LOGFILE, O_RDWR | O_CREAT | O_TRUNC, 0600); - Assert(LogFD != -1); -#else - logfd = 2; // stderr -#endif - - // poopoo C cannot infer type - struct pollfd fds[FDS_MAX] = { - {-1, POLLIN, 0}, // FDS_BI - {-1, POLLIN, 0}, // FDS_UNI - {-1, POLLIN, 0}, // FDS_TTY - {-1, POLLIN, 0}, // FDS_RESIZE - }; - - address = (struct sockaddr_in){ - AF_INET, - htons(PORT), - {0}, - {0}, - }; - -#ifdef IMPORT_ID - // File for storing the user's ID. - u32 idfile = open(ID_FILE, O_RDWR | O_CREAT, 0600); - s32 nread = read(idfile, &user.id, sizeof(user.id)); - Assert(nread != -1); -#endif - /* Authentication */ - { - s32 unifd, bifd; - bifd = get_connection(&address); - if (bifd == -1) - { - LoggingF("errno: %d\n", errno); - return 1; - } - unifd = get_connection(&address); - if (unifd == -1) - { - LoggingF("errno: %d\n", errno); - return 1; - } - LoggingF("(%d,%d)\n", bifd, unifd); - if (!authenticate(&user, bifd) || - !authenticate(&user, unifd)) - { - LoggingF("errno: %d\n", errno); - return 1; - } - else - { - LoggingF("Authenticated (%d,%d)\n", bifd, unifd); - } - fds[FDS_BI].fd = bifd; - fds[FDS_UNI].fd = unifd; - } - -#ifdef IMPORT_ID - // Save id - write(idfile, &user.id, sizeof(user.id)); -#endif - - LoggingF("Got ID: %lu\n", user.ID); - - // for wide character printing - Assert(setlocale(LC_ALL, "")); - - // init - tb_init(); - tb_get_fds(&fds[FDS_TTY].fd, &fds[FDS_RESIZE].fd); - - DisplayChat(&ScratchArena, - &MessagesArena, MessagesNum, - &ClientsArena, fds, - Input, InputIndex); - tb_present(); - - // main loop - while (!quit) - { - err = poll(fds, FDS_MAX, TIMEOUT_POLL); - // ignore resize events and use them to redraw the screen - Assert(err != -1 || errno == EINTR); - - tb_clear(); - - if (fds[FDS_UNI].revents & POLLIN) - { - // got data from server - HeaderMessage header; - nrecv = recv(fds[FDS_UNI].fd, &header, sizeof(header), 0); - Assert(nrecv != -1); - - // Server disconnects - if (nrecv == 0) - { - // close diconnected server's socket - err = close(fds[FDS_UNI].fd); - Assert(err == 0); - fds[FDS_UNI].fd = -1; // ignore - // start trying to reconnect in a thread - err = pthread_create(&thr_rec, 0, &thread_reconnect, (void*)fds); - Assert(err == 0); - } - else - { - if (header.version != PROTOCOL_VERSION) - { - LoggingF("Header received does not match version\n"); - continue; - } - - void* addr = ArenaPush(&MessagesArena, sizeof(header)); - memcpy(addr, &header, sizeof(header)); - - // Messages handled from server - switch (header.type) - { - case HEADER_TYPE_TEXT: - recvTextMessage(&MessagesArena, fds[FDS_UNI].fd); - MessagesNum++; - break; - case HEADER_TYPE_PRESENCE:; - PresenceMessage* message = ArenaPush(&MessagesArena, sizeof(*message)); - nrecv = recv(fds[FDS_UNI].fd, message, sizeof(*message), 0); - Assert(nrecv != -1); - Assert(nrecv == sizeof(*message)); - MessagesNum++; - break; - default: - LoggingF("Got unhandled message: %s\n", headerTypeString(header.type)); - break; - } - } - } - - if (fds[FDS_TTY].revents & POLLIN) - { - // got a key event - tb_poll_event(&ev); - - switch (ev.key) - { - case TB_KEY_CTRL_W: - // delete consecutive whitespace - while (InputIndex) - { - if (Input[InputIndex - 1] == L' ') - { - Input[InputIndex - 1] = 0; - InputIndex--; - continue; - } - break; - } - // delete until whitespace - while (InputIndex) - { - if (Input[InputIndex - 1] == L' ') - break; - // erase - Input[InputIndex - 1] = 0; - InputIndex--; - } - break; - case TB_KEY_CTRL_Z: - { - pid_t pid = getpid(); - tb_shutdown(); - kill(pid, SIGSTOP); - tb_init(); - } break; - case TB_KEY_CTRL_Y: // Paste clipboard contents to input - { - u32 OutputBufferLen = MAX_INPUT_LEN - InputIndex; - if (OutputBufferLen <= 0) break; - - u8 OutputBuffer[OutputBufferLen]; - - char *PathName = "xclip"; - char *Argv[] = {PathName, "-o", "-sel", "c", 0}; - - command_output Output = run_command_get_output(PathName, Argv, OutputBuffer, OutputBufferLen - 1); - if (Output.Error) break; - - // Remove trailing whitespace - int BufferIndex = Output.NumRead - 1; - while (BufferIndex > 0 && - (OutputBuffer[BufferIndex] == '\n' || - OutputBuffer[BufferIndex] == '\t')) - { - OutputBuffer[BufferIndex] = 0; - BufferIndex--; - } - - // Append to output - for (s32 BufferIndex = 0; BufferIndex < Output.NumRead; BufferIndex++) - { - // convert u8 to u32 - u32 ch = OutputBuffer[BufferIndex]; - Input[InputIndex] = ch; - InputIndex++; - } - - } break; - case TB_KEY_CTRL_I: - { - for (u32 i = 0; - i < TAB_WIDTH && InputIndex < MAX_INPUT_LEN - 1; - i++) - { - Input[InputIndex] = L' '; - InputIndex++; - } - } break; - case TB_KEY_BACKSPACE2: - if (InputIndex) InputIndex--; - Input[InputIndex] = 0; - break; - case TB_KEY_CTRL_D: - case TB_KEY_CTRL_C: - quit = 1; - break; - case TB_KEY_CTRL_M: // send message - { - raw_result RawText = markdown_to_raw(0, Input, InputIndex); - - if (RawText.Len == 0) - // do not send empty message - break; - if (fds[FDS_UNI].fd == -1) - // do not send message to disconnected server - break; - - // null terminate - Input[InputIndex] = 0; - InputIndex++; - - // Save header - HeaderMessage* header = ArenaPush(&MessagesArena, sizeof(*header)); - header->version = PROTOCOL_VERSION; - header->type = HEADER_TYPE_TEXT; - header->id = user.ID; - - // Save message - TextMessage* sendmsg = ArenaPush(&MessagesArena, TEXTMESSAGE_SIZE); - sendmsg->timestamp = time(0); - sendmsg->len = InputIndex; - - u32 text_size = InputIndex * sizeof(*Input); - ArenaPush(&MessagesArena, text_size); - memcpy(&sendmsg->text, Input, text_size); - - sendAnyMessage(fds[FDS_UNI].fd, *header, sendmsg); - - MessagesNum++; - // also clear input - } // fallthrough - case TB_KEY_CTRL_U: // clear input - bzero(Input, InputIndex * sizeof(*Input)); - InputIndex = 0; - break; - default: - if (ev.ch == 0) - break; - - // TODO: show error - if (InputIndex == MAX_INPUT_LEN - 1) // last byte reserved for \0 - break; - - // append key to input buffer - Input[InputIndex] = ev.ch; - InputIndex++; - } - if (quit) - break; - } - - // These are used to redraw the screen from threads - if (fds[FDS_RESIZE].revents & POLLIN) - { - // ignore - tb_poll_event(&ev); - } - - DisplayChat(&ScratchArena, &MessagesArena, MessagesNum, &ClientsArena, fds, Input, InputIndex); - - tb_present(); - } - - tb_shutdown(); - - if (quitmsg != 0) - printf("%s\n", quitmsg); - - return 0; -} diff --git a/chatty.h b/chatty.h deleted file mode 100644 index 5891b94..0000000 --- a/chatty.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef CHATTY_H -#define CHATTY_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// port for chatty -#define PORT 9983 -// max number of bytes that can be logged at once -#define LOGMESSAGE_MAX 2048 -#define LOG_FMT "%H:%M:%S " -#define LOG_LEN 10 -// Enable/Disable saving clients permanently to file -// #define IMPORT_ID - -#define Kilobytes(Value) ((Value) * 1024) -#define Megabytes(Value) (Kilobytes(Value) * 1024) -#define Gigabytes(Value) (Megabytes((u64)Value) * 1024) -#define Terabytes(Value) (Gigabytes((u64)Value) * 1024) -#define PAGESIZE 4096 -#define local_persist static -#define global_variable -#define internal static - -#include "types.h" - -void Loggingf(char* format, ...); - -#endif // CHATTY_H - -#ifdef CHATTY_IMPL - -global_variable s32 LogFD; - -void -LoggingF(char* format, ...) -{ - char buf[LOGMESSAGE_MAX]; - va_list args; - va_start(args, format); - - vsnprintf(buf, sizeof(buf), format, args); - va_end(args); - - int n = 0; - while (*(buf + n) != 0) n++; - - u64 t = time(0); - u8 timestamp[LOG_LEN]; - struct tm* ltime = localtime((time_t*)&t); - strftime((char*)timestamp, LOG_LEN, LOG_FMT, ltime); - write(LogFD, timestamp, LOG_LEN - 1); - - write(LogFD, buf, n); -} - - -#endif // CHATTY_IMPL diff --git a/chatty2.c b/chatty2.c deleted file mode 100644 index 5a00fee..0000000 --- a/chatty2.c +++ /dev/null @@ -1,90 +0,0 @@ -/* Macro's */ - -#define TB_IMPL -#include "external/termbox2.h" -#undef TB_IMPL - -#define DEBUG -#define MAX_INPUT_LEN 255 - -#define CHATTY_IMPL -#include "ui.h" -#undef CHATTY_IMPL -#include "protocol.h" - -#include - -#ifdef DEBUG -#define Assert(expr) \ - if (!(expr)) \ - { \ - tb_shutdown(); \ - raise(SIGTRAP); \ - } -#else -#define Assert(expr) ; -#endif - -int -main(int Argc, char *Argv[]) -{ - struct tb_event ev; - rect TextBox = {0, 0, 24, 4}; - rect TextR = { - TextBox.X + TEXTBOX_BORDER_WIDTH + TEXTBOX_PADDING_X, - TextBox.Y + TEXTBOX_BORDER_WIDTH, - TextBox.W - TEXTBOX_BORDER_WIDTH * 2 - TEXTBOX_PADDING_X * 2, - TextBox.H - TEXTBOX_BORDER_WIDTH * 2 - }; - wchar_t Input[MAX_INPUT_LEN] = {0}; - u32 InputLen = 0; - u32 InputOffset = 0; - u32 InputPos = 0; - u32 DidParseKey = 0; - - Assert(setlocale(LC_ALL, "")); - tb_init(); - global.cursor_x = TextR.X; - global.cursor_y = TextR.Y; - bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); - - while (ev.key != TB_KEY_CTRL_C) - { - tb_clear(); - - DrawBox(TextBox, 0); - TextBoxDraw(TextR, Input + InputOffset, InputLen); - - InputPos = InputOffset + (global.cursor_x - TextR.X) + (global.cursor_y - TextR.Y) * TextR.W; - Assert(InputPos <= InputLen); - - tb_present(); - - tb_poll_event(&ev); - - // TODO: Handle resize event - - // Intercept keys - if (ev.key == TB_KEY_CTRL_M) - { - tb_printf(26, 0, 0, 0, "sent."); - continue; - } - else - { - DidParseKey = TextBoxKeypress(ev, TextR, - Input, &InputLen, InputPos, &InputOffset); - } - - u32 ShouldInsert = (!DidParseKey) && (ev.ch && InputLen < MAX_INPUT_LEN); - if (ShouldInsert) - { - TextBoxInsert(Input, InputPos, InputLen++, ev.ch); - ScrollRight(TextR, &InputOffset); - } - - // tb_clear(); - } - - tb_shutdown(); -} diff --git a/compile_flags.txt b/compile_flags.txt deleted file mode 100644 index eb526c6..0000000 --- a/compile_flags.txt +++ /dev/null @@ -1,5 +0,0 @@ --Wall --Wextra --pedantic --std=c99 --O3 diff --git a/external/keyboard.c b/external/keyboard.c deleted file mode 100644 index e53fac5..0000000 --- a/external/keyboard.c +++ /dev/null @@ -1,777 +0,0 @@ -#define TB_IMPL -#include "termbox2.h" - -#include -#include -#include -#include -#include - -struct key { - unsigned char x; - unsigned char y; - uint32_t ch; -}; - -#define STOP {0,0,0} -struct key K_ESC[] = {{1,1,'E'},{2,1,'S'},{3,1,'C'},STOP}; -struct key K_F1[] = {{6,1,'F'},{7,1,'1'},STOP}; -struct key K_F2[] = {{9,1,'F'},{10,1,'2'},STOP}; -struct key K_F3[] = {{12,1,'F'},{13,1,'3'},STOP}; -struct key K_F4[] = {{15,1,'F'},{16,1,'4'},STOP}; -struct key K_F5[] = {{19,1,'F'},{20,1,'5'},STOP}; -struct key K_F6[] = {{22,1,'F'},{23,1,'6'},STOP}; -struct key K_F7[] = {{25,1,'F'},{26,1,'7'},STOP}; -struct key K_F8[] = {{28,1,'F'},{29,1,'8'},STOP}; -struct key K_F9[] = {{33,1,'F'},{34,1,'9'},STOP}; -struct key K_F10[] = {{36,1,'F'},{37,1,'1'},{38,1,'0'},STOP}; -struct key K_F11[] = {{40,1,'F'},{41,1,'1'},{42,1,'1'},STOP}; -struct key K_F12[] = {{44,1,'F'},{45,1,'1'},{46,1,'2'},STOP}; -struct key K_PRN[] = {{50,1,'P'},{51,1,'R'},{52,1,'N'},STOP}; -struct key K_SCR[] = {{54,1,'S'},{55,1,'C'},{56,1,'R'},STOP}; -struct key K_BRK[] = {{58,1,'B'},{59,1,'R'},{60,1,'K'},STOP}; -struct key K_LED1[] = {{66,1,'-'},STOP}; -struct key K_LED2[] = {{70,1,'-'},STOP}; -struct key K_LED3[] = {{74,1,'-'},STOP}; - -struct key K_TILDE[] = {{1,4,'`'},STOP}; -struct key K_TILDE_SHIFT[] = {{1,4,'~'},STOP}; -struct key K_1[] = {{4,4,'1'},STOP}; -struct key K_1_SHIFT[] = {{4,4,'!'},STOP}; -struct key K_2[] = {{7,4,'2'},STOP}; -struct key K_2_SHIFT[] = {{7,4,'@'},STOP}; -struct key K_3[] = {{10,4,'3'},STOP}; -struct key K_3_SHIFT[] = {{10,4,'#'},STOP}; -struct key K_4[] = {{13,4,'4'},STOP}; -struct key K_4_SHIFT[] = {{13,4,'$'},STOP}; -struct key K_5[] = {{16,4,'5'},STOP}; -struct key K_5_SHIFT[] = {{16,4,'%'},STOP}; -struct key K_6[] = {{19,4,'6'},STOP}; -struct key K_6_SHIFT[] = {{19,4,'^'},STOP}; -struct key K_7[] = {{22,4,'7'},STOP}; -struct key K_7_SHIFT[] = {{22,4,'&'},STOP}; -struct key K_8[] = {{25,4,'8'},STOP}; -struct key K_8_SHIFT[] = {{25,4,'*'},STOP}; -struct key K_9[] = {{28,4,'9'},STOP}; -struct key K_9_SHIFT[] = {{28,4,'('},STOP}; -struct key K_0[] = {{31,4,'0'},STOP}; -struct key K_0_SHIFT[] = {{31,4,')'},STOP}; -struct key K_MINUS[] = {{34,4,'-'},STOP}; -struct key K_MINUS_SHIFT[] = {{34,4,'_'},STOP}; -struct key K_EQUALS[] = {{37,4,'='},STOP}; -struct key K_EQUALS_SHIFT[] = {{37,4,'+'},STOP}; -struct key K_BACKSLASH[] = {{40,4,'\\'},STOP}; -struct key K_BACKSLASH_SHIFT[] = {{40,4,'|'},STOP}; -struct key K_BACKSPACE[] = {{44,4,0x2190},{45,4,0x2500},{46,4,0x2500},STOP}; -struct key K_INS[] = {{50,4,'I'},{51,4,'N'},{52,4,'S'},STOP}; -struct key K_HOM[] = {{54,4,'H'},{55,4,'O'},{56,4,'M'},STOP}; -struct key K_PGU[] = {{58,4,'P'},{59,4,'G'},{60,4,'U'},STOP}; -struct key K_K_NUMLOCK[] = {{65,4,'N'},STOP}; -struct key K_K_SLASH[] = {{68,4,'/'},STOP}; -struct key K_K_STAR[] = {{71,4,'*'},STOP}; -struct key K_K_MINUS[] = {{74,4,'-'},STOP}; - -struct key K_TAB[] = {{1,6,'T'},{2,6,'A'},{3,6,'B'},STOP}; -struct key K_q[] = {{6,6,'q'},STOP}; -struct key K_Q[] = {{6,6,'Q'},STOP}; -struct key K_w[] = {{9,6,'w'},STOP}; -struct key K_W[] = {{9,6,'W'},STOP}; -struct key K_e[] = {{12,6,'e'},STOP}; -struct key K_E[] = {{12,6,'E'},STOP}; -struct key K_r[] = {{15,6,'r'},STOP}; -struct key K_R[] = {{15,6,'R'},STOP}; -struct key K_t[] = {{18,6,'t'},STOP}; -struct key K_T[] = {{18,6,'T'},STOP}; -struct key K_y[] = {{21,6,'y'},STOP}; -struct key K_Y[] = {{21,6,'Y'},STOP}; -struct key K_u[] = {{24,6,'u'},STOP}; -struct key K_U[] = {{24,6,'U'},STOP}; -struct key K_i[] = {{27,6,'i'},STOP}; -struct key K_I[] = {{27,6,'I'},STOP}; -struct key K_o[] = {{30,6,'o'},STOP}; -struct key K_O[] = {{30,6,'O'},STOP}; -struct key K_p[] = {{33,6,'p'},STOP}; -struct key K_P[] = {{33,6,'P'},STOP}; -struct key K_LSQB[] = {{36,6,'['},STOP}; -struct key K_LCUB[] = {{36,6,'{'},STOP}; -struct key K_RSQB[] = {{39,6,']'},STOP}; -struct key K_RCUB[] = {{39,6,'}'},STOP}; -struct key K_ENTER[] = { - {43,6,0x2591},{44,6,0x2591},{45,6,0x2591},{46,6,0x2591}, - {43,7,0x2591},{44,7,0x2591},{45,7,0x21B5},{46,7,0x2591}, - {41,8,0x2591},{42,8,0x2591},{43,8,0x2591},{44,8,0x2591}, - {45,8,0x2591},{46,8,0x2591},STOP -}; -struct key K_DEL[] = {{50,6,'D'},{51,6,'E'},{52,6,'L'},STOP}; -struct key K_END[] = {{54,6,'E'},{55,6,'N'},{56,6,'D'},STOP}; -struct key K_PGD[] = {{58,6,'P'},{59,6,'G'},{60,6,'D'},STOP}; -struct key K_K_7[] = {{65,6,'7'},STOP}; -struct key K_K_8[] = {{68,6,'8'},STOP}; -struct key K_K_9[] = {{71,6,'9'},STOP}; -struct key K_K_PLUS[] = {{74,6,' '},{74,7,'+'},{74,8,' '},STOP}; - -struct key K_CAPS[] = {{1,8,'C'},{2,8,'A'},{3,8,'P'},{4,8,'S'},STOP}; -struct key K_a[] = {{7,8,'a'},STOP}; -struct key K_A[] = {{7,8,'A'},STOP}; -struct key K_s[] = {{10,8,'s'},STOP}; -struct key K_S[] = {{10,8,'S'},STOP}; -struct key K_d[] = {{13,8,'d'},STOP}; -struct key K_D[] = {{13,8,'D'},STOP}; -struct key K_f[] = {{16,8,'f'},STOP}; -struct key K_F[] = {{16,8,'F'},STOP}; -struct key K_g[] = {{19,8,'g'},STOP}; -struct key K_G[] = {{19,8,'G'},STOP}; -struct key K_h[] = {{22,8,'h'},STOP}; -struct key K_H[] = {{22,8,'H'},STOP}; -struct key K_j[] = {{25,8,'j'},STOP}; -struct key K_J[] = {{25,8,'J'},STOP}; -struct key K_k[] = {{28,8,'k'},STOP}; -struct key K_K[] = {{28,8,'K'},STOP}; -struct key K_l[] = {{31,8,'l'},STOP}; -struct key K_L[] = {{31,8,'L'},STOP}; -struct key K_SEMICOLON[] = {{34,8,';'},STOP}; -struct key K_PARENTHESIS[] = {{34,8,':'},STOP}; -struct key K_QUOTE[] = {{37,8,'\''},STOP}; -struct key K_DOUBLEQUOTE[] = {{37,8,'"'},STOP}; -struct key K_K_4[] = {{65,8,'4'},STOP}; -struct key K_K_5[] = {{68,8,'5'},STOP}; -struct key K_K_6[] = {{71,8,'6'},STOP}; - -struct key K_LSHIFT[] = {{1,10,'S'},{2,10,'H'},{3,10,'I'},{4,10,'F'},{5,10,'T'},STOP}; -struct key K_z[] = {{9,10,'z'},STOP}; -struct key K_Z[] = {{9,10,'Z'},STOP}; -struct key K_x[] = {{12,10,'x'},STOP}; -struct key K_X[] = {{12,10,'X'},STOP}; -struct key K_c[] = {{15,10,'c'},STOP}; -struct key K_C[] = {{15,10,'C'},STOP}; -struct key K_v[] = {{18,10,'v'},STOP}; -struct key K_V[] = {{18,10,'V'},STOP}; -struct key K_b[] = {{21,10,'b'},STOP}; -struct key K_B[] = {{21,10,'B'},STOP}; -struct key K_n[] = {{24,10,'n'},STOP}; -struct key K_N[] = {{24,10,'N'},STOP}; -struct key K_m[] = {{27,10,'m'},STOP}; -struct key K_M[] = {{27,10,'M'},STOP}; -struct key K_COMMA[] = {{30,10,','},STOP}; -struct key K_LANB[] = {{30,10,'<'},STOP}; -struct key K_PERIOD[] = {{33,10,'.'},STOP}; -struct key K_RANB[] = {{33,10,'>'},STOP}; -struct key K_SLASH[] = {{36,10,'/'},STOP}; -struct key K_QUESTION[] = {{36,10,'?'},STOP}; -struct key K_RSHIFT[] = {{42,10,'S'},{43,10,'H'},{44,10,'I'},{45,10,'F'},{46,10,'T'},STOP}; -struct key K_ARROW_UP[] = {{54,10,'('},{55,10,0x2191},{56,10,')'},STOP}; -struct key K_K_1[] = {{65,10,'1'},STOP}; -struct key K_K_2[] = {{68,10,'2'},STOP}; -struct key K_K_3[] = {{71,10,'3'},STOP}; -struct key K_K_ENTER[] = {{74,10,0x2591},{74,11,0x2591},{74,12,0x2591},STOP}; - -struct key K_LCTRL[] = {{1,12,'C'},{2,12,'T'},{3,12,'R'},{4,12,'L'},STOP}; -struct key K_LWIN[] = {{6,12,'W'},{7,12,'I'},{8,12,'N'},STOP}; -struct key K_LALT[] = {{10,12,'A'},{11,12,'L'},{12,12,'T'},STOP}; -struct key K_SPACE[] = { - {14,12,' '},{15,12,' '},{16,12,' '},{17,12,' '},{18,12,' '}, - {19,12,'S'},{20,12,'P'},{21,12,'A'},{22,12,'C'},{23,12,'E'}, - {24,12,' '},{25,12,' '},{26,12,' '},{27,12,' '},{28,12,' '}, - STOP -}; -struct key K_RALT[] = {{30,12,'A'},{31,12,'L'},{32,12,'T'},STOP}; -struct key K_RWIN[] = {{34,12,'W'},{35,12,'I'},{36,12,'N'},STOP}; -struct key K_RPROP[] = {{38,12,'P'},{39,12,'R'},{40,12,'O'},{41,12,'P'},STOP}; -struct key K_RCTRL[] = {{43,12,'C'},{44,12,'T'},{45,12,'R'},{46,12,'L'},STOP}; -struct key K_ARROW_LEFT[] = {{50,12,'('},{51,12,0x2190},{52,12,')'},STOP}; -struct key K_ARROW_DOWN[] = {{54,12,'('},{55,12,0x2193},{56,12,')'},STOP}; -struct key K_ARROW_RIGHT[] = {{58,12,'('},{59,12,0x2192},{60,12,')'},STOP}; -struct key K_K_0[] = {{65,12,' '},{66,12,'0'},{67,12,' '},{68,12,' '},STOP}; -struct key K_K_PERIOD[] = {{71,12,'.'},STOP}; - -struct combo { - struct key *keys[6]; -}; - -struct combo combos[] = { - {{K_TILDE, K_2, K_LCTRL, K_RCTRL, 0}}, - {{K_A, K_LCTRL, K_RCTRL, 0}}, - {{K_B, K_LCTRL, K_RCTRL, 0}}, - {{K_C, K_LCTRL, K_RCTRL, 0}}, - {{K_D, K_LCTRL, K_RCTRL, 0}}, - {{K_E, K_LCTRL, K_RCTRL, 0}}, - {{K_F, K_LCTRL, K_RCTRL, 0}}, - {{K_G, K_LCTRL, K_RCTRL, 0}}, - {{K_H, K_BACKSPACE, K_LCTRL, K_RCTRL, 0}}, - {{K_I, K_TAB, K_LCTRL, K_RCTRL, 0}}, - {{K_J, K_LCTRL, K_RCTRL, 0}}, - {{K_K, K_LCTRL, K_RCTRL, 0}}, - {{K_L, K_LCTRL, K_RCTRL, 0}}, - {{K_M, K_ENTER, K_K_ENTER, K_LCTRL, K_RCTRL, 0}}, - {{K_N, K_LCTRL, K_RCTRL, 0}}, - {{K_O, K_LCTRL, K_RCTRL, 0}}, - {{K_P, K_LCTRL, K_RCTRL, 0}}, - {{K_Q, K_LCTRL, K_RCTRL, 0}}, - {{K_R, K_LCTRL, K_RCTRL, 0}}, - {{K_S, K_LCTRL, K_RCTRL, 0}}, - {{K_T, K_LCTRL, K_RCTRL, 0}}, - {{K_U, K_LCTRL, K_RCTRL, 0}}, - {{K_V, K_LCTRL, K_RCTRL, 0}}, - {{K_W, K_LCTRL, K_RCTRL, 0}}, - {{K_X, K_LCTRL, K_RCTRL, 0}}, - {{K_Y, K_LCTRL, K_RCTRL, 0}}, - {{K_Z, K_LCTRL, K_RCTRL, 0}}, - {{K_LSQB, K_ESC, K_3, K_LCTRL, K_RCTRL, 0}}, - {{K_4, K_BACKSLASH, K_LCTRL, K_RCTRL, 0}}, - {{K_RSQB, K_5, K_LCTRL, K_RCTRL, 0}}, - {{K_6, K_LCTRL, K_RCTRL, 0}}, - {{K_7, K_SLASH, K_MINUS_SHIFT, K_LCTRL, K_RCTRL, 0}}, - {{K_SPACE,0}}, - {{K_1_SHIFT,K_LSHIFT,K_RSHIFT,0}}, - {{K_DOUBLEQUOTE,K_LSHIFT,K_RSHIFT,0}}, - {{K_3_SHIFT,K_LSHIFT,K_RSHIFT,0}}, - {{K_4_SHIFT,K_LSHIFT,K_RSHIFT,0}}, - {{K_5_SHIFT,K_LSHIFT,K_RSHIFT,0}}, - {{K_7_SHIFT,K_LSHIFT,K_RSHIFT,0}}, - {{K_QUOTE,0}}, - {{K_9_SHIFT,K_LSHIFT,K_RSHIFT,0}}, - {{K_0_SHIFT,K_LSHIFT,K_RSHIFT,0}}, - {{K_8_SHIFT,K_K_STAR,K_LSHIFT,K_RSHIFT,0}}, - {{K_EQUALS_SHIFT,K_K_PLUS,K_LSHIFT,K_RSHIFT,0}}, - {{K_COMMA,0}}, - {{K_MINUS,K_K_MINUS,0}}, - {{K_PERIOD,K_K_PERIOD,0}}, - {{K_SLASH,K_K_SLASH,0}}, - {{K_0,K_K_0,0}}, - {{K_1,K_K_1,0}}, - {{K_2,K_K_2,0}}, - {{K_3,K_K_3,0}}, - {{K_4,K_K_4,0}}, - {{K_5,K_K_5,0}}, - {{K_6,K_K_6,0}}, - {{K_7,K_K_7,0}}, - {{K_8,K_K_8,0}}, - {{K_9,K_K_9,0}}, - {{K_PARENTHESIS,K_LSHIFT,K_RSHIFT,0}}, - {{K_SEMICOLON,0}}, - {{K_LANB,K_LSHIFT,K_RSHIFT,0}}, - {{K_EQUALS,0}}, - {{K_RANB,K_LSHIFT,K_RSHIFT,0}}, - {{K_QUESTION,K_LSHIFT,K_RSHIFT,0}}, - {{K_2_SHIFT,K_LSHIFT,K_RSHIFT,0}}, - {{K_A,K_LSHIFT,K_RSHIFT,0}}, - {{K_B,K_LSHIFT,K_RSHIFT,0}}, - {{K_C,K_LSHIFT,K_RSHIFT,0}}, - {{K_D,K_LSHIFT,K_RSHIFT,0}}, - {{K_E,K_LSHIFT,K_RSHIFT,0}}, - {{K_F,K_LSHIFT,K_RSHIFT,0}}, - {{K_G,K_LSHIFT,K_RSHIFT,0}}, - {{K_H,K_LSHIFT,K_RSHIFT,0}}, - {{K_I,K_LSHIFT,K_RSHIFT,0}}, - {{K_J,K_LSHIFT,K_RSHIFT,0}}, - {{K_K,K_LSHIFT,K_RSHIFT,0}}, - {{K_L,K_LSHIFT,K_RSHIFT,0}}, - {{K_M,K_LSHIFT,K_RSHIFT,0}}, - {{K_N,K_LSHIFT,K_RSHIFT,0}}, - {{K_O,K_LSHIFT,K_RSHIFT,0}}, - {{K_P,K_LSHIFT,K_RSHIFT,0}}, - {{K_Q,K_LSHIFT,K_RSHIFT,0}}, - {{K_R,K_LSHIFT,K_RSHIFT,0}}, - {{K_S,K_LSHIFT,K_RSHIFT,0}}, - {{K_T,K_LSHIFT,K_RSHIFT,0}}, - {{K_U,K_LSHIFT,K_RSHIFT,0}}, - {{K_V,K_LSHIFT,K_RSHIFT,0}}, - {{K_W,K_LSHIFT,K_RSHIFT,0}}, - {{K_X,K_LSHIFT,K_RSHIFT,0}}, - {{K_Y,K_LSHIFT,K_RSHIFT,0}}, - {{K_Z,K_LSHIFT,K_RSHIFT,0}}, - {{K_LSQB,0}}, - {{K_BACKSLASH,0}}, - {{K_RSQB,0}}, - {{K_6_SHIFT,K_LSHIFT,K_RSHIFT,0}}, - {{K_MINUS_SHIFT,K_LSHIFT,K_RSHIFT,0}}, - {{K_TILDE,0}}, - {{K_a,0}}, - {{K_b,0}}, - {{K_c,0}}, - {{K_d,0}}, - {{K_e,0}}, - {{K_f,0}}, - {{K_g,0}}, - {{K_h,0}}, - {{K_i,0}}, - {{K_j,0}}, - {{K_k,0}}, - {{K_l,0}}, - {{K_m,0}}, - {{K_n,0}}, - {{K_o,0}}, - {{K_p,0}}, - {{K_q,0}}, - {{K_r,0}}, - {{K_s,0}}, - {{K_t,0}}, - {{K_u,0}}, - {{K_v,0}}, - {{K_w,0}}, - {{K_x,0}}, - {{K_y,0}}, - {{K_z,0}}, - {{K_LCUB,K_LSHIFT,K_RSHIFT,0}}, - {{K_BACKSLASH_SHIFT,K_LSHIFT,K_RSHIFT,0}}, - {{K_RCUB,K_LSHIFT,K_RSHIFT,0}}, - {{K_TILDE_SHIFT,K_LSHIFT,K_RSHIFT,0}}, - {{K_8, K_BACKSPACE, K_LCTRL, K_RCTRL, 0}} -}; - -struct combo func_combos[] = { - {{K_F1,0}}, - {{K_F2,0}}, - {{K_F3,0}}, - {{K_F4,0}}, - {{K_F5,0}}, - {{K_F6,0}}, - {{K_F7,0}}, - {{K_F8,0}}, - {{K_F9,0}}, - {{K_F10,0}}, - {{K_F11,0}}, - {{K_F12,0}}, - {{K_INS,0}}, - {{K_DEL,0}}, - {{K_HOM,0}}, - {{K_END,0}}, - {{K_PGU,0}}, - {{K_PGD,0}}, - {{K_ARROW_UP,0}}, - {{K_ARROW_DOWN,0}}, - {{K_ARROW_LEFT,0}}, - {{K_ARROW_RIGHT,0}} -}; - -void print_tb(const char *str, int x, int y, uint16_t fg, uint16_t bg) -{ - while (*str) { - uint32_t uni; - str += tb_utf8_char_to_unicode(&uni, str); - tb_set_cell(x, y, uni, fg, bg); - x++; - } -} - -void printf_tb(int x, int y, uint16_t fg, uint16_t bg, const char *fmt, ...) -{ - char buf[4096]; - va_list vl; - va_start(vl, fmt); - vsnprintf(buf, sizeof(buf), fmt, vl); - va_end(vl); - print_tb(buf, x, y, fg, bg); -} - -void draw_key(struct key *k, uint16_t fg, uint16_t bg) -{ - while (k->x) { - tb_set_cell(k->x+2, k->y+4, k->ch, fg, bg); - k++; - } -} - -void draw_keyboard(void) -{ - int i; - tb_set_cell(0, 0, 0x250C, TB_WHITE, TB_DEFAULT); - tb_set_cell(79, 0, 0x2510, TB_WHITE, TB_DEFAULT); - tb_set_cell(0, 23, 0x2514, TB_WHITE, TB_DEFAULT); - tb_set_cell(79, 23, 0x2518, TB_WHITE, TB_DEFAULT); - - for (i = 1; i < 79; ++i) { - tb_set_cell(i, 0, 0x2500, TB_WHITE, TB_DEFAULT); - tb_set_cell(i, 23, 0x2500, TB_WHITE, TB_DEFAULT); - tb_set_cell(i, 17, 0x2500, TB_WHITE, TB_DEFAULT); - tb_set_cell(i, 4, 0x2500, TB_WHITE, TB_DEFAULT); - } - for (i = 1; i < 23; ++i) { - tb_set_cell(0, i, 0x2502, TB_WHITE, TB_DEFAULT); - tb_set_cell(79, i, 0x2502, TB_WHITE, TB_DEFAULT); - } - tb_set_cell(0, 17, 0x251C, TB_WHITE, TB_DEFAULT); - tb_set_cell(79, 17, 0x2524, TB_WHITE, TB_DEFAULT); - tb_set_cell(0, 4, 0x251C, TB_WHITE, TB_DEFAULT); - tb_set_cell(79, 4, 0x2524, TB_WHITE, TB_DEFAULT); - for (i = 5; i < 17; ++i) { - tb_set_cell(1, i, 0x2588, TB_YELLOW, TB_YELLOW); - tb_set_cell(78, i, 0x2588, TB_YELLOW, TB_YELLOW); - } - - draw_key(K_ESC, TB_WHITE, TB_BLUE); - draw_key(K_F1, TB_WHITE, TB_BLUE); - draw_key(K_F2, TB_WHITE, TB_BLUE); - draw_key(K_F3, TB_WHITE, TB_BLUE); - draw_key(K_F4, TB_WHITE, TB_BLUE); - draw_key(K_F5, TB_WHITE, TB_BLUE); - draw_key(K_F6, TB_WHITE, TB_BLUE); - draw_key(K_F7, TB_WHITE, TB_BLUE); - draw_key(K_F8, TB_WHITE, TB_BLUE); - draw_key(K_F9, TB_WHITE, TB_BLUE); - draw_key(K_F10, TB_WHITE, TB_BLUE); - draw_key(K_F11, TB_WHITE, TB_BLUE); - draw_key(K_F12, TB_WHITE, TB_BLUE); - draw_key(K_PRN, TB_WHITE, TB_BLUE); - draw_key(K_SCR, TB_WHITE, TB_BLUE); - draw_key(K_BRK, TB_WHITE, TB_BLUE); - draw_key(K_LED1, TB_WHITE, TB_BLUE); - draw_key(K_LED2, TB_WHITE, TB_BLUE); - draw_key(K_LED3, TB_WHITE, TB_BLUE); - - draw_key(K_TILDE, TB_WHITE, TB_BLUE); - draw_key(K_1, TB_WHITE, TB_BLUE); - draw_key(K_2, TB_WHITE, TB_BLUE); - draw_key(K_3, TB_WHITE, TB_BLUE); - draw_key(K_4, TB_WHITE, TB_BLUE); - draw_key(K_5, TB_WHITE, TB_BLUE); - draw_key(K_6, TB_WHITE, TB_BLUE); - draw_key(K_7, TB_WHITE, TB_BLUE); - draw_key(K_8, TB_WHITE, TB_BLUE); - draw_key(K_9, TB_WHITE, TB_BLUE); - draw_key(K_0, TB_WHITE, TB_BLUE); - draw_key(K_MINUS, TB_WHITE, TB_BLUE); - draw_key(K_EQUALS, TB_WHITE, TB_BLUE); - draw_key(K_BACKSLASH, TB_WHITE, TB_BLUE); - draw_key(K_BACKSPACE, TB_WHITE, TB_BLUE); - draw_key(K_INS, TB_WHITE, TB_BLUE); - draw_key(K_HOM, TB_WHITE, TB_BLUE); - draw_key(K_PGU, TB_WHITE, TB_BLUE); - draw_key(K_K_NUMLOCK, TB_WHITE, TB_BLUE); - draw_key(K_K_SLASH, TB_WHITE, TB_BLUE); - draw_key(K_K_STAR, TB_WHITE, TB_BLUE); - draw_key(K_K_MINUS, TB_WHITE, TB_BLUE); - - draw_key(K_TAB, TB_WHITE, TB_BLUE); - draw_key(K_q, TB_WHITE, TB_BLUE); - draw_key(K_w, TB_WHITE, TB_BLUE); - draw_key(K_e, TB_WHITE, TB_BLUE); - draw_key(K_r, TB_WHITE, TB_BLUE); - draw_key(K_t, TB_WHITE, TB_BLUE); - draw_key(K_y, TB_WHITE, TB_BLUE); - draw_key(K_u, TB_WHITE, TB_BLUE); - draw_key(K_i, TB_WHITE, TB_BLUE); - draw_key(K_o, TB_WHITE, TB_BLUE); - draw_key(K_p, TB_WHITE, TB_BLUE); - draw_key(K_LSQB, TB_WHITE, TB_BLUE); - draw_key(K_RSQB, TB_WHITE, TB_BLUE); - draw_key(K_ENTER, TB_WHITE, TB_BLUE); - draw_key(K_DEL, TB_WHITE, TB_BLUE); - draw_key(K_END, TB_WHITE, TB_BLUE); - draw_key(K_PGD, TB_WHITE, TB_BLUE); - draw_key(K_K_7, TB_WHITE, TB_BLUE); - draw_key(K_K_8, TB_WHITE, TB_BLUE); - draw_key(K_K_9, TB_WHITE, TB_BLUE); - draw_key(K_K_PLUS, TB_WHITE, TB_BLUE); - - draw_key(K_CAPS, TB_WHITE, TB_BLUE); - draw_key(K_a, TB_WHITE, TB_BLUE); - draw_key(K_s, TB_WHITE, TB_BLUE); - draw_key(K_d, TB_WHITE, TB_BLUE); - draw_key(K_f, TB_WHITE, TB_BLUE); - draw_key(K_g, TB_WHITE, TB_BLUE); - draw_key(K_h, TB_WHITE, TB_BLUE); - draw_key(K_j, TB_WHITE, TB_BLUE); - draw_key(K_k, TB_WHITE, TB_BLUE); - draw_key(K_l, TB_WHITE, TB_BLUE); - draw_key(K_SEMICOLON, TB_WHITE, TB_BLUE); - draw_key(K_QUOTE, TB_WHITE, TB_BLUE); - draw_key(K_K_4, TB_WHITE, TB_BLUE); - draw_key(K_K_5, TB_WHITE, TB_BLUE); - draw_key(K_K_6, TB_WHITE, TB_BLUE); - - draw_key(K_LSHIFT, TB_WHITE, TB_BLUE); - draw_key(K_z, TB_WHITE, TB_BLUE); - draw_key(K_x, TB_WHITE, TB_BLUE); - draw_key(K_c, TB_WHITE, TB_BLUE); - draw_key(K_v, TB_WHITE, TB_BLUE); - draw_key(K_b, TB_WHITE, TB_BLUE); - draw_key(K_n, TB_WHITE, TB_BLUE); - draw_key(K_m, TB_WHITE, TB_BLUE); - draw_key(K_COMMA, TB_WHITE, TB_BLUE); - draw_key(K_PERIOD, TB_WHITE, TB_BLUE); - draw_key(K_SLASH, TB_WHITE, TB_BLUE); - draw_key(K_RSHIFT, TB_WHITE, TB_BLUE); - draw_key(K_ARROW_UP, TB_WHITE, TB_BLUE); - draw_key(K_K_1, TB_WHITE, TB_BLUE); - draw_key(K_K_2, TB_WHITE, TB_BLUE); - draw_key(K_K_3, TB_WHITE, TB_BLUE); - draw_key(K_K_ENTER, TB_WHITE, TB_BLUE); - - draw_key(K_LCTRL, TB_WHITE, TB_BLUE); - draw_key(K_LWIN, TB_WHITE, TB_BLUE); - draw_key(K_LALT, TB_WHITE, TB_BLUE); - draw_key(K_SPACE, TB_WHITE, TB_BLUE); - draw_key(K_RCTRL, TB_WHITE, TB_BLUE); - draw_key(K_RPROP, TB_WHITE, TB_BLUE); - draw_key(K_RWIN, TB_WHITE, TB_BLUE); - draw_key(K_RALT, TB_WHITE, TB_BLUE); - draw_key(K_ARROW_LEFT, TB_WHITE, TB_BLUE); - draw_key(K_ARROW_DOWN, TB_WHITE, TB_BLUE); - draw_key(K_ARROW_RIGHT, TB_WHITE, TB_BLUE); - draw_key(K_K_0, TB_WHITE, TB_BLUE); - draw_key(K_K_PERIOD, TB_WHITE, TB_BLUE); - - printf_tb(33, 1, (uint16_t)(TB_MAGENTA | TB_BOLD), TB_DEFAULT, "Keyboard demo!"); - printf_tb(21, 2, TB_MAGENTA, TB_DEFAULT, "(press CTRL+X and then CTRL+Q to exit)"); - printf_tb(15, 3, TB_MAGENTA, TB_DEFAULT, "(press CTRL+X and then CTRL+C to change input mode)"); - - int inputmode = tb_set_input_mode(0); - char inputmode_str[64]; - - if (inputmode & TB_INPUT_ESC) - sprintf(inputmode_str, "TB_INPUT_ESC"); - if (inputmode & TB_INPUT_ALT) - sprintf(inputmode_str, "TB_INPUT_ALT"); - if (inputmode & TB_INPUT_MOUSE) - sprintf(inputmode_str + 12, " | TB_INPUT_MOUSE"); - - printf_tb(3, 18, TB_WHITE, TB_DEFAULT, "Input mode: %s", inputmode_str); -} - -const char *funckeymap(int k) -{ - static const char *fcmap[] = { - "CTRL+2, CTRL+~", - "CTRL+A", - "CTRL+B", - "CTRL+C", - "CTRL+D", - "CTRL+E", - "CTRL+F", - "CTRL+G", - "CTRL+H, BACKSPACE", - "CTRL+I, TAB", - "CTRL+J", - "CTRL+K", - "CTRL+L", - "CTRL+M, ENTER", - "CTRL+N", - "CTRL+O", - "CTRL+P", - "CTRL+Q", - "CTRL+R", - "CTRL+S", - "CTRL+T", - "CTRL+U", - "CTRL+V", - "CTRL+W", - "CTRL+X", - "CTRL+Y", - "CTRL+Z", - "CTRL+3, ESC, CTRL+[", - "CTRL+4, CTRL+\\", - "CTRL+5, CTRL+]", - "CTRL+6", - "CTRL+7, CTRL+/, CTRL+_", - "SPACE" - }; - static const char *fkmap[] = { - "F1", - "F2", - "F3", - "F4", - "F5", - "F6", - "F7", - "F8", - "F9", - "F10", - "F11", - "F12", - "INSERT", - "DELETE", - "HOME", - "END", - "PGUP", - "PGDN", - "ARROW UP", - "ARROW DOWN", - "ARROW LEFT", - "ARROW RIGHT" - }; - - if (k == TB_KEY_CTRL_8) - return "CTRL+8, BACKSPACE 2"; /* 0x7F */ - else if (k >= TB_KEY_ARROW_RIGHT && k <= 0xFFFF) - return fkmap[0xFFFF-k]; - else if (k <= TB_KEY_SPACE) - return fcmap[k]; - return "UNKNOWN"; -} - -void pretty_print_press(struct tb_event *ev) -{ - char buf[7]; - buf[tb_utf8_unicode_to_char(buf, ev->ch)] = '\0'; - printf_tb(3, 19, TB_WHITE , TB_DEFAULT, "Key: "); - printf_tb(8, 19, TB_YELLOW, TB_DEFAULT, "decimal: %d", ev->key); - printf_tb(8, 20, TB_GREEN , TB_DEFAULT, "hex: 0x%X", ev->key); - printf_tb(8, 21, TB_CYAN , TB_DEFAULT, "octal: 0%o", ev->key); - printf_tb(8, 22, TB_RED , TB_DEFAULT, "string: %s", funckeymap(ev->key)); - - printf_tb(54, 19, TB_WHITE , TB_DEFAULT, "Char: "); - printf_tb(60, 19, TB_YELLOW, TB_DEFAULT, "decimal: %d", ev->ch); - printf_tb(60, 20, TB_GREEN , TB_DEFAULT, "hex: 0x%X", ev->ch); - printf_tb(60, 21, TB_CYAN , TB_DEFAULT, "octal: 0%o", ev->ch); - printf_tb(60, 22, TB_RED , TB_DEFAULT, "string: %s", buf); - - printf_tb(54, 18, TB_WHITE, TB_DEFAULT, "Modifier: %c%c%c%c", - (ev->mod & TB_MOD_CTRL) ? 'C' : ' ', - (ev->mod & TB_MOD_ALT) ? 'A' : ' ', - (ev->mod & TB_MOD_SHIFT) ? 'S' : ' ', - (ev->mod & TB_MOD_MOTION) ? 'M' : ' '); - -} - -void pretty_print_resize(struct tb_event *ev) -{ - printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Resize event: %d x %d", ev->w, ev->h); -} - -int counter = 0; - -void pretty_print_mouse(struct tb_event *ev) { - printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Mouse event: %d x %d %c", ev->x, ev->y, (ev->mod & TB_MOD_MOTION) ? '*' : ' '); - char *btn = ""; - switch (ev->key) { - case TB_KEY_MOUSE_LEFT: - btn = "MouseLeft: %d"; - break; - case TB_KEY_MOUSE_MIDDLE: - btn = "MouseMiddle: %d"; - break; - case TB_KEY_MOUSE_RIGHT: - btn = "MouseRight: %d"; - break; - case TB_KEY_MOUSE_WHEEL_UP: - btn = "MouseWheelUp: %d"; - break; - case TB_KEY_MOUSE_WHEEL_DOWN: - btn = "MouseWheelDown: %d"; - break; - case TB_KEY_MOUSE_RELEASE: - btn = "MouseRelease: %d"; - } - counter++; - printf_tb(43, 19, TB_WHITE, TB_DEFAULT, "Key: "); - printf_tb(48, 19, TB_YELLOW, TB_DEFAULT, btn, counter); -} - -void dispatch_press(struct tb_event *ev) -{ - if (ev->mod & TB_MOD_ALT) { - draw_key(K_LALT, TB_WHITE, TB_RED); - draw_key(K_RALT, TB_WHITE, TB_RED); - } - if (ev->mod & TB_MOD_CTRL) { - draw_key(K_LCTRL, TB_WHITE, TB_RED); - draw_key(K_RCTRL, TB_WHITE, TB_RED); - } - if (ev->mod & TB_MOD_SHIFT) { - draw_key(K_LSHIFT, TB_WHITE, TB_RED); - draw_key(K_RSHIFT, TB_WHITE, TB_RED); - } - - struct combo *k = 0; - if (ev->key >= TB_KEY_ARROW_RIGHT) - k = &func_combos[0xFFFF-ev->key]; - else if (ev->ch < 128) { - if (ev->ch == 0 && ev->key < 128) - k = &combos[ev->key]; - else - k = &combos[ev->ch]; - } - if (!k) - return; - - struct key **keys = k->keys; - while (*keys) { - draw_key(*keys, TB_WHITE, TB_RED); - keys++; - } -} - -int main(int argc, char **argv) -{ - (void) argc; (void) argv; - int ret; - - setlocale(LC_ALL, ""); - - ret = tb_init(); - if (ret) { - fprintf(stderr, "tb_init() failed with error code %d\n", ret); - return 1; - } - - tb_set_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE); - struct tb_event ev; - - tb_clear(); - draw_keyboard(); - tb_present(); - int inputmode = 0; - int ctrlxpressed = 0; - - while (1) { - ret = tb_poll_event(&ev); - - if (ret != TB_OK) { - if (ret == TB_ERR_POLL && tb_last_errno() == EINTR) { - /* poll was interrupted, maybe by a SIGWINCH; try again */ - continue; - } - /* some other error occurred; bail */ - break; - } - - switch (ev.type) { - case TB_EVENT_KEY: - if (ev.key == TB_KEY_CTRL_Q && ctrlxpressed) { - tb_shutdown(); - return 0; - } - if (ev.key == TB_KEY_CTRL_C && ctrlxpressed) { - static int chmap[] = { - TB_INPUT_ESC | TB_INPUT_MOUSE, /* 101 */ - TB_INPUT_ALT | TB_INPUT_MOUSE, /* 110 */ - TB_INPUT_ESC, /* 001 */ - TB_INPUT_ALT, /* 010 */ - }; - inputmode++; - if (inputmode >= 4) { - inputmode = 0; - } - tb_set_input_mode(chmap[inputmode]); - } - if (ev.key == TB_KEY_CTRL_X) - ctrlxpressed = 1; - else - ctrlxpressed = 0; - - tb_clear(); - draw_keyboard(); - dispatch_press(&ev); - pretty_print_press(&ev); - tb_present(); - break; - case TB_EVENT_RESIZE: - tb_clear(); - draw_keyboard(); - pretty_print_resize(&ev); - tb_present(); - break; - case TB_EVENT_MOUSE: - tb_clear(); - draw_keyboard(); - pretty_print_mouse(&ev); - tb_present(); - break; - default: - break; - } - } - tb_shutdown(); - return 0; -} diff --git a/external/termbox2.h b/external/termbox2.h deleted file mode 100644 index 265cdab..0000000 --- a/external/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/gdb_scripts b/gdb_scripts deleted file mode 100644 index 97acf23..0000000 --- a/gdb_scripts +++ /dev/null @@ -1,12 +0,0 @@ -define tbx - tui ena - b 126 - continue - disp SearchIndex - disp PrevIndex - disp Text[TextOffset + SearchIndex] - disp Text + TextOffset + PrevIndex - disp BreakOffset - disp YOffset -end - diff --git a/insert.md b/insert.md deleted file mode 100644 index cb00d1c..0000000 --- a/insert.md +++ /dev/null @@ -1,68 +0,0 @@ -# 1. InputBox - -InputBox(u32 BoxX, u32 BoxY, u32 BoxWidth, u32 BoxHeight, - wchar_t *Text, u32 TextLen, u32 TextIndex, - Bool Focused) - -TextIndex is where the edit cursor is in the text, so where the text should be displayed? - -It is most user friendly when the text moves as least as possible, so giving an offset will make it -scroll each time the user moves the cursor. No it should only move when the user moves the cursor -out of bounds. - - -There should be cursor logic and text logic - -# 2. Redraw text based on cursor - -```c -void -Redraw(u32 DrawnStart, u32 DrawnEnd) -{ - -} - -Box(BoxX, BoxY, Width, Height); -BoxedText(X+1, Y+1, Width-2, Height-2, - TextLen, Text); - -if (ev.key == TB_KEY_ARROW_LEFT) -{ - CursorX--; - TextIndex--; -} -else if (ev.key == TB_KEY_ARROW_RIGHT) -{ - CursorX++; - TextIndex++; -} -else if (ev.key == TB_KEY_ARROW_UP) -{ - // Scroll Up, Update TextIndex, Redraw -} -else if (ev.key == TB_KEY_ARROW_DOWN) -{ - // Scroll Down, Update TextIndex, Redraw -} -else if (ev.ch) -{ - Text[Textlen++] = ev.ch; - TextIndex++; - CursorX++; -} - -if (CursorX == BoxX + Width) -{ - // Scroll Down || Next Line (if empty) - // Update TextIndex, Redraw -} -else if (CursorX == BoxX) -{ - // Scroll up, Update TextIndex, Redraw -} -```` -It can be deterministic how we draw the text, and the cursor position is only needed to know where -to insert text. - -1. Do text insertion -2. Redrawing diff --git a/notes/archived.md b/notes/archived.md new file mode 100644 index 0000000..62def2d --- /dev/null +++ b/notes/archived.md @@ -0,0 +1,31 @@ +## Client +- [x] prompt +- [x] sending message +- [x] bug: do not allow sending empty message +- [x] wrapping messages +- [x] bug: when sending message after diconnect (serverfd?) +- [x] Handle disconnection thiin a thread, the best way would be +- [x] Add limit_y to printf_wrap +- [x] id2string on clients +- [x] ctrl+z to suspend +- [x] bug: when reconnecting nrecv != -1 +- [x] bug: when disconnecting +- [x] use error type success to say that authentication succeeded +- [x] markup for messages +- [x] clipboard shortcut +- [x] tab as spaces support +- [x] fixed empty messages with markup characters + +## Server +- [x] import clients + +## Common +- [x] handle messages that are too large +- [x] refactor i&self into conn +- [x] logging +- [x] Req|Inf connection per client +- [x] connect/disconnect messages +- [x] bug: blocking after `Added pollfd`, after importing a client and then connecting with the + id/or without? After reconnection fails chatty blocks (remove sleep) +- [x] connect/disconnections messages +- [x] asserting, logging if fail / halt execution diff --git a/notes/chatty.md b/notes/chatty.md new file mode 100644 index 0000000..77a3886 --- /dev/null +++ b/notes/chatty.md @@ -0,0 +1,76 @@ +# Chatty +The idea is the following: +- tcp server that you can send messages to +- history upon connecting +- date of messages sent +- client for reading the messages and sending them at the same time +- rooms +- encryption +- authentication + +## client +- [ ] BUG: text is not appearing after typing +- [ ] BUG: when connecting two clients of the same account +- [ ] BUG: wrapping does not work and displays nothing if there is no screen space +- [ ] BUG: reconnect does not work when server does not know id +- [ ] TODO: Convert tabs to spaces +- [ ] BUG: when using lots of markup characters +- [ ] TODO: Newline support + - [ ] resizable box + +## server +- [ ] check that fds arena does not overflow + - free clients which disconnected and use free list to give them space +- [ ] check if when sending and the client is offline (due to connection loss) what happens +- [ ] timeout on recv? +- [ ] use threads to handle clients/ timeout when receiving because a client could theoretically + stall the entire server. +- [ ] do not crash on errors from clients + - implement error message? + - timeout on recv with setsockopt +- [ ] theoretically two clients can connect at the same time. The uni/bi connections should be + negotiated. + +## common +- [ ] use IP address / domain +- [ ] chat history +- [ ] rooms +- [ ] compression + +## Protocol +- see `protocol.h` for more info +- The null terminator must be sent with the string. +- The text can be arbitrary length + +### To Read +#### C Programming +- https://www.youtube.com/watch?v=wvtFGa6XJDU +- https://nullprogram.com/blog/2023/02/11/ +- https://nullprogram.com/blog/2023/02/13/ +- https://nullprogram.com/blog/2023/10/08/ +#### Encryption w/ Compression +- https://en.wikipedia.org/wiki/BREACH +- https://en.wikipedia.org/wiki/CRIME +- https://crypto.stackexchange.com/questions/2283/crypto-compression-algorithms +- openpgp https://www.rfc-editor.org/rfc/rfc4880 +- https://security.stackexchange.com/questions/19911/crime-how-to-beat-the-beast-successor +- https://blog.qualys.com/product-tech/2012/09/14/crime-information-leakage-attack-against-ssltls +- Algorithms: + *Symmetric* + - AESI + - Blowfish + - Twofish + - Rivest Cipher (RC4) + *Assymetric* + - Data Encryption Standard (DES) + - ECDSA + - RSA + - Diffie-Hellman + - PGP + _Hash_ + - Deflate + - Huffman Coding + - LZ77 + Other + - ChaCha20-Poly1305 + - AES(-GCM) diff --git a/notes/insert.md b/notes/insert.md new file mode 100644 index 0000000..cb00d1c --- /dev/null +++ b/notes/insert.md @@ -0,0 +1,68 @@ +# 1. InputBox + +InputBox(u32 BoxX, u32 BoxY, u32 BoxWidth, u32 BoxHeight, + wchar_t *Text, u32 TextLen, u32 TextIndex, + Bool Focused) + +TextIndex is where the edit cursor is in the text, so where the text should be displayed? + +It is most user friendly when the text moves as least as possible, so giving an offset will make it +scroll each time the user moves the cursor. No it should only move when the user moves the cursor +out of bounds. + + +There should be cursor logic and text logic + +# 2. Redraw text based on cursor + +```c +void +Redraw(u32 DrawnStart, u32 DrawnEnd) +{ + +} + +Box(BoxX, BoxY, Width, Height); +BoxedText(X+1, Y+1, Width-2, Height-2, + TextLen, Text); + +if (ev.key == TB_KEY_ARROW_LEFT) +{ + CursorX--; + TextIndex--; +} +else if (ev.key == TB_KEY_ARROW_RIGHT) +{ + CursorX++; + TextIndex++; +} +else if (ev.key == TB_KEY_ARROW_UP) +{ + // Scroll Up, Update TextIndex, Redraw +} +else if (ev.key == TB_KEY_ARROW_DOWN) +{ + // Scroll Down, Update TextIndex, Redraw +} +else if (ev.ch) +{ + Text[Textlen++] = ev.ch; + TextIndex++; + CursorX++; +} + +if (CursorX == BoxX + Width) +{ + // Scroll Down || Next Line (if empty) + // Update TextIndex, Redraw +} +else if (CursorX == BoxX) +{ + // Scroll up, Update TextIndex, Redraw +} +```` +It can be deterministic how we draw the text, and the cursor position is only needed to know where +to insert text. + +1. Do text insertion +2. Redrawing diff --git a/notes/scroll.md b/notes/scroll.md new file mode 100644 index 0000000..f36e774 --- /dev/null +++ b/notes/scroll.md @@ -0,0 +1,63 @@ +# The scrolling problem: +If the text is wrapped on whitespace this means that when we scroll up we do not go +backwards by text width. It is important to update the text correctly because we want +the complete text to always look the same so that when the user can see what they expect +to see (in this case the previous text). + +# 1. Find based on skipped characters +New position could be +`InputPos - (TextWidth - Skipped)` + +This means that the place where the wrapping is done must somehow return the number of +skipped characters. + +Or we can run the wrapping algorithm on the current line to check if wrapping would +have been done and how much characters it would have skipped. + +# 2. Wrapping as is: +Searching the text backwards for whitespace works good for an existsing text. But we +have a text that we append to a lot, so it might be more interesting to add wrapping +when adding characters. + +When we add a character at the end of the line and it is not a space *wrap*. + +Problem: What if we are in the middle of the text. +1. Check the last character +2. Is it a space + y) ignore + n) search backwards for space and wrap there + +Problem: How to print +Have the wrapping algorithms on adding a character and printing be the same, which +means that if we wrapped after printing a character we should print it correctly. + +# 3. Wrapping with special characters: +To create special characters (formatting, wrapping, etc.) and preserving unicode we can +implement special characters by making our text into a special linked list structure. + +> Example +```c +typedef enum { + NodeText = 0, + NodeBold, + NodeWrap +} node_type; + +typedef stuct { + node_type Type; + wchar_t *Text; + text_node *Prev; + text_node *Next; +} text_node; + +text_node First = { NodeText, L"Hello Foo!", 0, 0 }; +text_node Second = { NodeWrap, 0, &NodeText, 0 }; +First.Next = &Second; +``` +# Notes +- We should add a special (colored?) char to show the text is wrapped because that can + be hard to tell when spaces are same as empty. +- What if we end up on a wrapping char? +- Implement 2 for wrapping +- Implement 1 for cursor movement +- Implement 3 for formatting later and instead use text start and finish diff --git a/protocol.h b/protocol.h deleted file mode 100644 index 0c5b04a..0000000 --- a/protocol.h +++ /dev/null @@ -1,375 +0,0 @@ -#ifndef PROTOCOL_H -#define PROTOCOL_H - -#include "arena.h" -#include "types.h" - -/// Protocol -// - every message has format Header + Message -// TODO: security -// -/// ID -// - So clients can be identified uniquely. -// - 8 bytes -// - number that increments for each new client -// -/// Strings -// - strings are sent with their null terminator -// -/// Authentication -// Each header contains the id of the sender, because ids start at 1 -// message with id 0 is considered unauthenticated. -// -// When the server receives a header with id 0, this can happen -// Scenario 1: IDMessage, client already has an ID -// 1. client-> Send own ID -// 2. server-> knows ID? -// y. server-> Success -// n. 1. server-> Error 'notfound' -// 2. client-> exit -// Scenario 2: IntroductionMessage, client requests a new ID -// 1. client-> Introduces -// 2. server-> Sends & Saves ID -// 3. Save ID -// -// BIFD & UNIFD -// Each client has 2 connections that must be authenticated one for 2-way -// communication and one for 1-way communication. Respectively BIFD and -// UNIFD. BIFD must be authenticated first and is meant for requests such -// as getting an IntroductionMessage for a sent IDMessage. -// UNIFD is for messages that are like notifications. For example -// PresenceMessage that tells us when another user connected. -// These two connections separate these message types so we do not have to -// worry about receiving a PresenceMessage when waiting for an a response. -// -/// Naming conventions -// Messages end with the Message suffix (eg. TextMessag, HistoryMessage) -// -// A function that is coupled to a type works like -// eg. (printTextMessage, formatTimestamp) - -#define PROTOCOL_VERSION 0 -// Size of author string including null terminator -#define AUTHOR_LEN 13 -// Size of formatted timestamp string including null terminator -#define TIMESTAMP_LEN 9 -#define TIMESTAMP_FORMAT "%H:%M:%S" - -typedef u64 ID; - -// - 2 bytes for version -// - 1 byte for message type -// - 16 bytes for checksum -typedef struct { - u16 version; - u8 type; - ID id; -} HeaderMessage; - -typedef enum { - HEADER_TYPE_TEXT = 0, - HEADER_TYPE_HISTORY, - HEADER_TYPE_PRESENCE, - HEADER_TYPE_ID, - HEADER_TYPE_INTRODUCTION, - HEADER_TYPE_ERROR -} HeaderType; -// shorthand for creating a header with a value from the enum -#define HEADER_INIT(t) {.version = PROTOCOL_VERSION, .type = t, .id = 0} -// from Tsoding video on minicel (https://youtu.be/HCAgvKQDJng?t=4546) -// sv(https://github.com/tsoding/sv) -#define HEADER_FMT "header: v%d %s(%d) [%d]" -#define HEADER_ARG(header) header.version, headerTypeString(header.type), header.type, header.id - -// For sending texts to other clients -// - 13 bytes for the author -// - 8 bytes for the timestamp -// - 2 bytes for the text length -// - x*4 bytes for the text -typedef struct { - u64 timestamp; // timestamp of when the message was sent - u16 len; - wchar_t* text; // placeholder for indexing - // wchar_t* is used, because this renders the text in the debugger -} TextMessage; -// Size of TextMessage without text pointer -#define TEXTMESSAGE_SIZE (sizeof(TextMessage) - sizeof(u32*)) - -// Requesting messages sent after a timestamp. -// - 8 bytes for the timestamp -typedef struct { - u64 timestamp; -} HistoryMessage; - -// Introduce the client to the server by sending the client's information. -// See "First connection". -// - 13 bytes for author -typedef struct { - u8 author[AUTHOR_LEN]; -} IntroductionMessage; -#define INTRODUCTION_FMT "introduction: %s" -#define INTRODUCTION_ARG(message) message.author - -// Notifying the sender's state, such as "connected", "disconnected", "AFK", ... -// - 1 byte for type -typedef struct { - u8 type; -} PresenceMessage; -typedef enum { - PRESENCE_TYPE_CONNECTED = 0, - PRESENCE_TYPE_DISCONNECTED, - PRESENCE_TYPE_AFK -} PresenceType; - -// Send an error message -// - 1 byte for type -typedef struct { - u8 type; -} ErrorMessage; -typedef enum { - ERROR_TYPE_BADMESSAGE = 0, - ERROR_TYPE_NOTFOUND, - ERROR_TYPE_SUCCESS, - ERROR_TYPE_ALREADYCONNECTED, - ERROR_TYPE_TOOMANYCONNECTIONS -} ErrorType; -#define ERROR_INIT(t) {.type = t} - -typedef struct { - ID id; -} IDMessage; - -typedef struct { - s32 nrecv; - TextMessage* message; -} recvTextMessageResult; - -// Returns string for type byte in HeaderMessage -u8* -headerTypeString(HeaderType type) -{ - switch (type) - { - case HEADER_TYPE_TEXT: return (u8*)"TextMessage"; - case HEADER_TYPE_HISTORY: return (u8*)"HistoryMessage"; - case HEADER_TYPE_PRESENCE: return (u8*)"PresenceMessage"; - case HEADER_TYPE_ID: return (u8*)"IDMessage"; - case HEADER_TYPE_INTRODUCTION: return (u8*)"IntroductionMessage"; - case HEADER_TYPE_ERROR: return (u8*)"ErrorMessage"; - default: return (u8*)"Unknown"; - } -} - -u8* -presenceTypeString(PresenceType type) -{ - switch (type) - { - case PRESENCE_TYPE_CONNECTED: return (u8*)"connected"; - case PRESENCE_TYPE_DISCONNECTED: return (u8*)"disconnected"; - case PRESENCE_TYPE_AFK: return (u8*)"afk"; - default: return (u8*)"Unknown"; - } -} - -u8* -errorTypeString(ErrorType type) -{ - switch (type) - { - case ERROR_TYPE_BADMESSAGE: return (u8*)"bad message"; - case ERROR_TYPE_NOTFOUND: return (u8*)"not found"; - case ERROR_TYPE_SUCCESS: return (u8*)"success"; - case ERROR_TYPE_ALREADYCONNECTED: return (u8*)"already connected"; - case ERROR_TYPE_TOOMANYCONNECTIONS: return (u8*)"too many connections"; - default: return (u8*)"Unknown"; - } -} - -// Formats time t into tmsp string -void -formatTimestamp(u8 timestamp_str[TIMESTAMP_LEN], u64 timestamp) -{ - struct tm* ltime; - ltime = localtime((time_t*)×tamp); - strftime((char*)timestamp_str, TIMESTAMP_LEN, TIMESTAMP_FORMAT, ltime); -} - -// Receive a message from fd and store it in the msgsArena, -// Returns pointer to the allocated memory -TextMessage* -recvTextMessage(Arena* msgsArena, u32 fd) -{ - TextMessage* message = ArenaPush(msgsArena, TEXTMESSAGE_SIZE); - - // Receive everything but the text so we can know the text's size and act accordingly - s32 nrecv = recv(fd, message, TEXTMESSAGE_SIZE, 0); - assert(nrecv != -1); - assert(nrecv == TEXTMESSAGE_SIZE); - - // Allocate memory for text and receive in that memory - u32 text_size = message->len * sizeof(*message->text); - ArenaPush(msgsArena, text_size); - - nrecv = recv(fd, (u8*)&message->text, text_size, 0); - assert(nrecv != -1); - assert(nrecv == (s32)(message->len * sizeof(*message->text))); - - return message; -} - -typedef struct { - HeaderMessage* header; - void* message; -} Message; - -u32 -getMessageSize(HeaderType type) -{ - u32 size = 0; - switch (type) - { - case HEADER_TYPE_ERROR: size = sizeof(ErrorMessage); break; - case HEADER_TYPE_HISTORY: size = sizeof(HistoryMessage); break; - case HEADER_TYPE_INTRODUCTION: size = sizeof(IntroductionMessage); break; - case HEADER_TYPE_PRESENCE: size = sizeof(PresenceMessage); break; - case HEADER_TYPE_ID: size = sizeof(IDMessage); break; - default: assert(0); - } - return size; -} - -s32 -recvAnyMessageType(s32 fd, HeaderMessage* header, void *anyMessage, HeaderType type) -{ - s32 nrecv = recv(fd, header, sizeof(*header), 0); - if (nrecv == -1 || nrecv == 0) - return nrecv; - assert(nrecv == sizeof(*header)); - - s32 size = 0; - switch (type) - { - case HEADER_TYPE_ERROR: - case HEADER_TYPE_HISTORY: - case HEADER_TYPE_INTRODUCTION: - case HEADER_TYPE_PRESENCE: - case HEADER_TYPE_ID: - size = getMessageSize(header->type); - break; - case HEADER_TYPE_TEXT: - { - TextMessage* message = anyMessage; - size = TEXTMESSAGE_SIZE + message->len * sizeof(*message->text); - } break; - default: assert(0); break; - } - assert(header->type == type); - - nrecv = recv(fd, anyMessage, size, 0); - assert(nrecv != -1); - assert(nrecv == size); - - return size; -} - -// Get any message into arena -Message -recvAnyMessage(Arena* arena, s32 fd) -{ - HeaderMessage* header = ArenaPush(arena, sizeof(*header)); - s32 nrecv = recv(fd, header, sizeof(*header), 0); - assert(nrecv != -1); - assert(nrecv == sizeof(*header)); - - s32 size = 0; - switch (header->type) - { - case HEADER_TYPE_TEXT: - { - Message result; - result.header = header; - result.message = recvTextMessage(arena, fd); - return result; - } break; - default: - { - size = getMessageSize(header->type); - } break; - } - - void* message = ArenaPush(arena, size); - nrecv = recv(fd, message, size, 0); - assert(nrecv != -1); - assert(nrecv == size); - - Message result; - result.header = header; - result.message = message; - - return result; -} - -Message -waitForMessageType(Arena* arena, Arena* queueArena, u32 fd, HeaderType type) -{ - Message message; - while (1) - { - message = recvAnyMessage(arena, fd); - if (message.header->type == type) - break; - ArenaPush(queueArena, getMessageSize(message.header->type)); - } - return message; -} - -// Generic sending function for sending any type of message to fd -// Returns number of bytes sent in message or -1 if there was an error. -s32 -sendAnyMessage(u32 fd, HeaderMessage header, void* anyMessage) -{ - s32 nsend_total; - s32 nsend = send(fd, &header, sizeof(header), 0); - if (nsend == -1) return nsend; - LoggingF("sendAnyMessage (%d)|sending "HEADER_FMT"\n", fd, HEADER_ARG(header)); - assert(nsend == sizeof(header)); - nsend_total = nsend; - - s32 size = 0; - switch (header.type) - { - case HEADER_TYPE_ERROR: - case HEADER_TYPE_HISTORY: - case HEADER_TYPE_INTRODUCTION: - case HEADER_TYPE_PRESENCE: - case HEADER_TYPE_ID: - size = getMessageSize(header.type); - break; - case HEADER_TYPE_TEXT: - { - nsend = send(fd, anyMessage, TEXTMESSAGE_SIZE, 0); - assert(nsend != -1); - assert(nsend == TEXTMESSAGE_SIZE); - nsend_total += nsend; - // set size to remaning text size that should be sent - TextMessage* message = (TextMessage*)anyMessage; - size = message->len * sizeof(*message->text); - nsend = 0; - - anyMessage = &message->text; - } break; - default: - LoggingF("sendAnyMessage (%d)|Cannot send %s\n", fd, headerTypeString(header.type)); - return -1; - } - - nsend = send(fd, anyMessage, size, 0); - if (nsend == -1) return nsend; - assert(nsend == size); - nsend_total += nsend; - - return nsend_total; -} - -#endif diff --git a/scroll.md b/scroll.md deleted file mode 100644 index f36e774..0000000 --- a/scroll.md +++ /dev/null @@ -1,63 +0,0 @@ -# The scrolling problem: -If the text is wrapped on whitespace this means that when we scroll up we do not go -backwards by text width. It is important to update the text correctly because we want -the complete text to always look the same so that when the user can see what they expect -to see (in this case the previous text). - -# 1. Find based on skipped characters -New position could be -`InputPos - (TextWidth - Skipped)` - -This means that the place where the wrapping is done must somehow return the number of -skipped characters. - -Or we can run the wrapping algorithm on the current line to check if wrapping would -have been done and how much characters it would have skipped. - -# 2. Wrapping as is: -Searching the text backwards for whitespace works good for an existsing text. But we -have a text that we append to a lot, so it might be more interesting to add wrapping -when adding characters. - -When we add a character at the end of the line and it is not a space *wrap*. - -Problem: What if we are in the middle of the text. -1. Check the last character -2. Is it a space - y) ignore - n) search backwards for space and wrap there - -Problem: How to print -Have the wrapping algorithms on adding a character and printing be the same, which -means that if we wrapped after printing a character we should print it correctly. - -# 3. Wrapping with special characters: -To create special characters (formatting, wrapping, etc.) and preserving unicode we can -implement special characters by making our text into a special linked list structure. - -> Example -```c -typedef enum { - NodeText = 0, - NodeBold, - NodeWrap -} node_type; - -typedef stuct { - node_type Type; - wchar_t *Text; - text_node *Prev; - text_node *Next; -} text_node; - -text_node First = { NodeText, L"Hello Foo!", 0, 0 }; -text_node Second = { NodeWrap, 0, &NodeText, 0 }; -First.Next = &Second; -``` -# Notes -- We should add a special (colored?) char to show the text is wrapped because that can - be hard to tell when spaces are same as empty. -- What if we end up on a wrapping char? -- Implement 2 for wrapping -- Implement 1 for cursor movement -- Implement 3 for formatting later and instead use text start and finish diff --git a/scrollandwrapped.c b/scrollandwrapped.c deleted file mode 100644 index 3cd3a42..0000000 --- a/scrollandwrapped.c +++ /dev/null @@ -1,225 +0,0 @@ -#define TB_IMPL -#include "external/termbox2.h" -#undef TB_IMPL - -#define MAX_INPUT_LEN 255 -#define DEBUG - -#define CHATTY_IMPL -#include "ui.h" - -#undef Assert -#define Assert(expr) \ - if (!(expr)) \ - { \ - tb_shutdown(); \ - raise(SIGTRAP); \ - } - -void -Right(rect TextR, s32 *TextOffset) -{ - if (global.cursor_x == TextR.X + TextR.W - 1) - { - if (global.cursor_y == TextR.Y + TextR.H - 1) - { - *TextOffset += TextR.W; - } - else - { - global.cursor_y++; - } - global.cursor_x = TextR.X; - } - else - { - global.cursor_x++; - } -} - -void -Left(rect TextR, s32 *TextOffset) -{ - if (global.cursor_x == TextR.X) - { - if (global.cursor_y == TextR.Y) - { - *TextOffset -= TextR.W; - } - else - { - global.cursor_y--; - } - global.cursor_x = TextR.X + TextR.W - 1; - } - else - { - global.cursor_x--; - } - -} - -void -PrintString(s32 X, s32 Y, s32 FG, s32 BG, wchar_t *Text, s32 TextLen) -{ - for (s32 At = 0; - At < TextLen; - At++) - { -#ifdef DEBUG - tb_set_cell(X + At, Y, Text[At], TB_BLACK, TB_CYAN); - tb_present(); -#else - tb_set_cell(X + At, Y, Text[At], FG, BG); -#endif - } -} - -int -main(int Argc, char* Argv[]) -{ - wchar_t Text[MAX_INPUT_LEN] = {0}; - s32 TextLen = 0; - s32 TextPos = 0; - s32 TextOffset = 0; - rect BoxR = {0, 0, 24, 4}; - rect TextR = TEXTBOXFROMBOX(BoxR); - struct tb_event ev = {0}; - - Assert(setlocale(LC_ALL, "")); - tb_init(); - - bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); - global.cursor_x = TextR.X; - global.cursor_y = TextR.Y; - -#if 0 - { - wchar_t DummyText[] = L"This is some dummy text m" - "eant to be wrapped sever" - "al times so I can try ou" - "t how scrolling works"; - s32 Len = wcslen(DummyText); - wcsncpy(Text, DummyText, Len); - TextLen = Len; - } -#endif - - while (ev.key != TB_KEY_CTRL_C) - { - tb_clear(); - DrawBox(BoxR, 0); - -#if 1 - { - s32 SearchIndex = TextR.W; - s32 PrevIndex = 0; - s32 BreakOffset = 0; - s32 YOffset = 0; - - if (TextLen - TextOffset <= TextR.W) - { - PrintString(TextR.X, TextR.Y, 0, 0, Text + TextOffset, TextLen); - } - else - { - while (SearchIndex < TextLen - TextOffset) - { - while (Text[SearchIndex] != ' ') - { - SearchIndex--; - if (SearchIndex == PrevIndex) - { - SearchIndex += TextR.W; - break; - } - } - - BreakOffset = (PrevIndex == SearchIndex) ? TextR.W : SearchIndex - PrevIndex; - PrintString(TextR.X, TextR.Y + YOffset, 0, 0, - Text + TextOffset + PrevIndex, BreakOffset); - - YOffset++; - if (YOffset == TextR.H) - { - break; - } - - PrevIndex = SearchIndex; - SearchIndex += TextR.W; - } - - if (YOffset < TextR.H) - { - PrintString(TextR.X, TextR.Y + YOffset, 0, 0, - Text + TextOffset + PrevIndex, - TextLen - TextOffset - PrevIndex); - } - } - - - } -#endif - - tb_printf(BoxR.X, BoxR.Y, 0, 0, - "#%d/%d +%d", - TextPos, TextLen, - TextOffset); - tb_printf(BoxR.X, BoxR.Y + BoxR.H, 0, 0, - "'%lc'/'%lc'", - ev.ch ? ev.ch : L'|', Text[TextPos] ? ev.ch : L'|'); - - tb_present(); - tb_poll_event(&ev); - switch (ev.key) - { - case TB_KEY_ARROW_RIGHT: - { - if (TextPos == TextLen) break; - TextPos++; - Right(TextR, &TextOffset); - continue; - } break; - case TB_KEY_ARROW_LEFT: - { - if (TextPos == 0) break; - TextPos--; - Left(TextR, &TextOffset); - continue; - } break; - case TB_KEY_CTRL_8: - { - if (TextPos == 0) break; - TextPos--; - TextLen--; - memmove(Text + TextPos, - Text + TextPos + 1, - (MAX_INPUT_LEN - TextPos - 1)*sizeof(*Text)); - Left(TextR, &TextOffset); - continue; - } break; - } - - if (!ev.mod && ev.ch && TextLen < MAX_INPUT_LEN) - { - if (TextPos < TextLen) - { - memmove(Text + TextPos + 1, - Text + TextPos, - (MAX_INPUT_LEN - TextPos - 1)*sizeof(*Text)); - } - Text[TextPos] = ev.ch; - TextPos++; - TextLen++; - - // Advance more if wrapping will happen - if (global.cursor_x == TextR.X + TextR.W - 1 && - Text) - - Right(TextR, &TextOffset); - } - } - - tb_shutdown(); - return 0; -} diff --git a/send.c b/send.c deleted file mode 100644 index 396b337..0000000 --- a/send.c +++ /dev/null @@ -1,80 +0,0 @@ -// minimal client implementation - -#include -#include -#include -#include -#include -#include - -#define CHATTY_IMPL -#include "chatty.h" -#undef CHATTY_IMPL -#include "protocol.h" - -int -main(int argc, char** argv) -{ - if (argc < 3) { - fprintf(stderr, "usage: send \n"); - return 1; - } - - s32 err, serverfd; - - serverfd = socket(AF_INET, SOCK_STREAM, 0); - assert(serverfd != -1); - - const struct sockaddr_in address = {AF_INET, htons(PORT), {0}, {0}}; - err = connect(serverfd, (struct sockaddr*)&address, sizeof(address)); - assert(err == 0); - - // Get our ID - ID id = 0; - { - // get author len - u32 author_len = strlen(argv[1]); - assert(author_len + 1 <= AUTHOR_LEN); // add 1 for null terminator - - // Introduce ourselves - HeaderMessage header = HEADER_INIT(HEADER_TYPE_INTRODUCTION); - IntroductionMessage message; - memcpy(message.author, argv[1], author_len); - s32 nsend = sendAnyMessage(serverfd, header, &message); - assert(nsend != -1); - - // Get id - IDMessage id_message; - s32 nrecv = recvAnyMessageType(serverfd, &header, &id_message, HEADER_TYPE_ID); - assert(nrecv != -1); - fprintf(stderr, "Got id: %lu\n", id_message.id); - id = id_message.id; - } - - // convert text to wide string - u32 text_len = strlen(argv[2]) + 1; - u32 text_wide[text_len]; - u32 size = mbstowcs((wchar_t*)text_wide, argv[2], text_len - 1); - assert(size == text_len - 1); - text_wide[text_len - 1] = 0; - - HeaderMessage header = HEADER_INIT(HEADER_TYPE_TEXT); - header.id = id; - TextMessage message; - bzero(&message, TEXTMESSAGE_SIZE); - message = (TextMessage){.timestamp = time(NULL), .len = text_len}; - - s32 nsend = send(serverfd, &header, sizeof(header), 0); - assert(nsend != -1); - fprintf(stderr, "header bytes sent: %d\n", nsend); - - nsend = send(serverfd, &message, TEXTMESSAGE_SIZE, 0); - assert(nsend != -1); - fprintf(stderr, "message bytes sent: %d\n", nsend); - - u32 text_size = message.len * sizeof(*message.text); - nsend = send(serverfd, text_wide, text_size, 0); - fprintf(stderr, "text bytes sent: %d\n", nsend); - - return 0; -} diff --git a/server.c b/server.c deleted file mode 100644 index a6613d6..0000000 --- a/server.c +++ /dev/null @@ -1,581 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Assertion macro */ -#ifndef Assert -#ifdef DEBUG -#define Assert(expr) if (!(expr)) { \ - raise(SIGTRAP); \ -} -#else -#define Assert(expr) if (!(expr)) { \ - raise(SIGTRAP); \ -} -#endif // DEBUG -#endif // Assert - -/* Dependencies */ -#define CHATTY_IMPL -#include "chatty.h" -#undef CHATTY_IMPL - -#define ARENA_IMPL -#include "arena.h" -#undef ARENA_IMPL -#include "protocol.h" - -/* Configuration options */ -// timeout on polling -#define TIMEOUT 60 * 1000 -// max pending connections -#define MAX_CONNECTIONS 1600 -// Get number of connections from arena position -// NOTE: this is somewhat wrong, because of when disconnections happen -#define FDS_SIZE (fdsArena.pos / sizeof(struct pollfd)) -#define CLIENTS_SIZE (clientsArena.pos / sizeof(Client)) - -// Where to save clients -#define CLIENTS_FILE "_clients" -// Where to write logs -#define LOGFILE "server.log" -// Log to LOGFILE instead of stderr -// #define LOGGING - -// enum for indexing the fds array -enum { FDS_STDIN = 0, - FDS_SERVER, - FDS_CLIENTS }; - -// Client information -typedef struct { - u8 author[AUTHOR_LEN]; // matches author property on other message types - ID id; - struct pollfd* bifd; // Index in fds array - struct pollfd* unifd; // Index in fds array -} Client; -#define CLIENT_FMT "[%s](%lu)" -#define CLIENT_ARG(client) client.author, client.id - -typedef enum { - BIFD = 0, - UNIFD, -} ClientFD; - -// TODO: remove global variable -// For handing out new ids to connections. -// Start at 1 because this makes 0 an invalid client id. -global_variable u32 nclients = 1; - -// Returns client matching id in clients nclients number of clients. -// Returns 0 if no client was found or if id was 0. -Client* -getClientByID(Client* clients, u32 nclients, ID id) -{ - if (!id) return 0; - - for (u32 i = 0; i < nclients; i++) - { - if (clients[i].id == id) - return clients + i; - } - return 0; -} - -// Returns client matching fd in clients nclients number of clients. -// Returns 0 if no clients was found or if fd was -1. -Client* -getClientByFD(Client* clients, u32 nclients, s32 fd) -{ - if (fd == -1) return 0; - - for (u32 i = 0; i < nclients; i++) - { - if ((clients[i].unifd && clients[i].unifd->fd == fd) || - (clients[i].bifd && clients[i].bifd->fd == fd)) - return clients + i; - } - return 0; -} - -// Print TextMessage prettily -void -printTextMessage(TextMessage* message, Client* client, u8 wide) -{ - u8 timestamp[TIMESTAMP_LEN] = {0}; - formatTimestamp(timestamp, message->timestamp); - - if (wide) - { - setlocale(LC_ALL, ""); - wprintf(L"TextMessage: %s [%s] %ls\n", timestamp, client->author, (wchar_t*)&message->text); - } else { - u8 str[message->len]; - wcstombs((char*)str, (wchar_t*)&message->text, message->len * sizeof(*message->text)); - LoggingF("TextMessage: %s [%s] (%d)%s\n", timestamp, client->author, message->len, str); - } -} - -// Send header and anyMessage to each connection in fds that is nfds number of connections except -// for connfd. -// Does not send if pollfd is not set or pollfd->fd is -1. -// Type will filter out only connections matching the type. -void -sendToOthers(Client* clients, u32 nclients, Client* client, ClientFD type, HeaderMessage* header, void* anyMessage) -{ - s32 nsend, fd; - for (u32 i = 0; i < nclients - 1; i ++) - { - if (clients + i == client) continue; - - if (type == UNIFD) - { - if (clients[i].unifd && clients[i].unifd->fd != -1) - fd = clients[i].unifd->fd; - else - continue; - } - else if (type == BIFD) - { - if (clients[i].bifd && clients[i].bifd->fd != -1) - fd = clients[i].bifd->fd; - else - continue; - } - nsend = sendAnyMessage(fd, *header, anyMessage); - - assert(nsend != -1); - LoggingF("sendToOthers "CLIENT_FMT"|%d<-%s %d bytes\n", CLIENT_ARG((clients[i])), fd, headerTypeString(header->type), nsend); - } -} - -// Send header and anyMessage to each connection in fds that is nfds number of connections. -// Does not send if pollfd is not set or pollfd->fd is -1. -// Type will filter out only connections matching the type. -void -sendToAll(Client* clients, u32 nclients, ClientFD type, HeaderMessage* header, void* anyMessage) -{ - s32 nsend; - for (u32 i = 0; i < nclients - 1; i++) - { - if (type == UNIFD) - { - if (clients[i].unifd && clients[i].unifd->fd != -1) - nsend = sendAnyMessage(clients[i].unifd->fd, *header, anyMessage); - else - continue; - } - else if (type == BIFD) - { - if (clients[i].bifd && clients[i].bifd->fd != -1) - nsend = sendAnyMessage(clients[i].bifd->fd, *header, anyMessage); - else - continue; - } - else - assert(0); - assert(nsend != -1); - LoggingF("sendToAll|[%s]->"CLIENT_FMT" %d bytes\n", headerTypeString(header->type), - CLIENT_ARG(clients[i]), - nsend); - } -} - -// Disconnect a client by closing the matching file descriptors -void -disconnect(Client* client) -{ - LoggingF("Disconnecting "CLIENT_FMT"\n", CLIENT_ARG((*client))); - if (client->unifd && client->unifd->fd != -1) - { - close(client->unifd->fd); - client->unifd->fd = -1; - client->unifd = 0; - } - if (client->bifd && client->bifd->fd != -1) - { - close(client->bifd->fd); - client->bifd->fd = -1; - client->bifd = 0; - } -} - -// Disconnects fds+conn from fds with nfds connections, then send a PresenceMessage to other -// clients about disconnection. -void -disconnectAndNotify(Client* clients, u32 nclients, Client* client) -{ - disconnect(client); - - local_persist HeaderMessage header = HEADER_INIT(HEADER_TYPE_PRESENCE); - header.id = client->id; - PresenceMessage message = {.type = PRESENCE_TYPE_DISCONNECTED}; - sendToAll(clients, nclients, UNIFD, &header, &message); -} - -// Receive authentication from pollfd->fd and create client out of it. Look in -// clientsArena if it already exists. Otherwise push a new onto the arena and write its information -// to clients_file. -// See "Authentication" in chatty.h -// Assumes that the client will send a IDMessage or IntroductionMessage -// Returns authenticated client -Client* -authenticate(Arena* clientsArena, s32 clients_file, struct pollfd* pollfd, HeaderMessage header) -{ - s32 nrecv = 0; - Client* client = 0; - - LoggingF("authenticate (%d)|" HEADER_FMT "\n", pollfd->fd, HEADER_ARG(header)); - - /* Scenario 1: Search for existing client */ - if (header.type == HEADER_TYPE_ID) - { - IDMessage message; - s32 nrecv = recv(pollfd->fd, &message, sizeof(message), 0); - assert(nrecv == sizeof(message)); - - client = getClientByID((Client*)clientsArena->addr, nclients, message.id); - if (!client) - { - LoggingF("authenticate (%d)|notfound\n", pollfd->fd); - header.type = HEADER_TYPE_ERROR; - ErrorMessage error_message = ERROR_INIT(ERROR_TYPE_NOTFOUND); - sendAnyMessage(pollfd->fd, header, &error_message); - return 0; - } - else - { - LoggingF("authenticate (%d)|found [%s](%lu)\n", pollfd->fd, client->author, client->id); - header.type = HEADER_TYPE_ERROR; - ErrorMessage error_message = ERROR_INIT(ERROR_TYPE_SUCCESS); - sendAnyMessage(pollfd->fd, header, &error_message); - } - - if (!client->bifd) - client->bifd = pollfd; - else if (!client->unifd) - client->unifd = pollfd; - else - assert(0); - - - return client; - } - /* Scenario 2: Create a new client */ - else if (header.type == HEADER_TYPE_INTRODUCTION) - { - IntroductionMessage message; - nrecv = recv(pollfd->fd, &message, sizeof(message), 0); - if (nrecv != sizeof(message)) - { - LoggingF("authenticate (%d)|err: %d/%lu bytes\n", pollfd->fd, nrecv, sizeof(message)); - return 0; - } - - // Copy metadata from IntroductionMessage - client = ArenaPush(clientsArena, sizeof(*client)); - memcpy(client->author, message.author, AUTHOR_LEN); - client->id = nclients; - - if (!client->bifd) - client->bifd = pollfd; - else if (!client->unifd) - client->unifd = pollfd; - else - assert(0); - - nclients++; - -#ifdef IMPORT_ID - write(clients_file, client, sizeof(*client)); -#endif - LoggingF("authenticate (%d)|Added [%s](%lu)\n", pollfd->fd, client->author, client->id); - - // Send ID to new client - HeaderMessage header = HEADER_INIT(HEADER_TYPE_ID); - IDMessage id_message; - id_message.id = client->id; - - s32 nsend = sendAnyMessage(pollfd->fd, header, &id_message); - assert(nsend != -1); - - return client; - } - - LoggingF("authenticate (%d)|Wrong header expected %s or %s\n", pollfd->fd, - headerTypeString(HEADER_TYPE_INTRODUCTION), - headerTypeString(HEADER_TYPE_ID)); - return 0; -} - -int -main(int argc, char** argv) -{ - signal(SIGPIPE, SIG_IGN); - - LogFD = 2; - // optional logging - if (argc > 1) - { - if (*argv[1] == '-') - if (argv[1][1] == 'l') - { - LogFD = open(LOGFILE, O_RDWR | O_CREAT | O_TRUNC, 0600); - assert(LogFD != -1); - } - } - - s32 serverfd; - // Start listening on the socket - { - s32 err; - u32 on = 1; - serverfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - assert(serverfd > 2); - - err = setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, (u8*)&on, sizeof(on)); - assert(!err); - - const struct sockaddr_in address = { - AF_INET, - htons(PORT), - {0}, - {0}, - }; - - err = bind(serverfd, (const struct sockaddr*)&address, sizeof(address)); - assert(!err); - - err = listen(serverfd, MAX_CONNECTIONS); - assert(!err); - LoggingF("Listening on :%d\n", PORT); - } - - Arena clientsArena; - Arena fdsArena; - Arena msgsArena; - ArenaAlloc(&clientsArena, MAX_CONNECTIONS * sizeof(Client)); - ArenaAlloc(&fdsArena, MAX_CONNECTIONS * 2 * sizeof(struct pollfd)); - ArenaAlloc(&msgsArena, Megabytes(128)); // storing received messages - struct pollfd* fds = fdsArena.addr; - Client* clients = clientsArena.addr; - - // Initializing fds - struct pollfd* fdsAddr; - struct pollfd newpollfd = {-1, POLLIN, 0}; // for copying with events already set - // initialize fds structure - newpollfd.fd = 0; - fdsAddr = ArenaPush(&fdsArena, sizeof(*fds)); - memcpy(fdsAddr, &newpollfd, sizeof(*fds)); - // add serverfd - newpollfd.fd = serverfd; - fdsAddr = ArenaPush(&fdsArena, sizeof(*fds)); - memcpy(fdsAddr, &newpollfd, sizeof(*fds)); - newpollfd.fd = -1; - - s32 clients_file; -#ifdef IMPORT_ID - clients_file = open(CLIENTS_FILE, O_RDWR | O_CREAT | O_APPEND, 0600); - assert(clients_file != -1); - struct stat statbuf; - assert(fstat(clients_file, &statbuf) != -1); - - read(clients_file, clients, statbuf.st_size); - if (statbuf.st_size > 0) - { - ArenaPush(&clientsArena, statbuf.st_size); - LoggingF("Imported %lu client(s)\n", statbuf.st_size / sizeof(*clients)); - nclients += statbuf.st_size / sizeof(*clients); - - // Reset pointers on imported clients - for (u32 i = 0; i < nclients - 1; i++) - { - clients[i].unifd = 0; - clients[i].bifd = 0; - } - } - for (u32 i = 0; i < nclients - 1; i++) - LoggingF("Imported: " CLIENT_FMT "\n", CLIENT_ARG(clients[i])); -#else - clients_file = 0; -#endif - - // Initialize the rest of the fds array - for (u32 i = FDS_CLIENTS; i < MAX_CONNECTIONS; i++) - fds[i] = newpollfd; - - while (1) - { - s32 err = poll(fds, FDS_SIZE, TIMEOUT); - assert(err != -1); - - if (fds[FDS_STDIN].revents & POLLIN) - { - u8 c; // exit on ctrl-d - if (!read(fds[FDS_STDIN].fd, &c, 1)) - break; - } - else if (fds[FDS_SERVER].revents & POLLIN) - { - // TODO: what if we are not aligned by 2 anymore? - s32 clientfd = accept(serverfd, 0, 0); - - if (clientfd == -1) - { - LoggingF("Error while accepting connection (%d)\n", clientfd); - continue; - } - else - LoggingF("New connection(%d)\n", clientfd); - - // TODO: find empty space in arena (fragmentation) - if (nclients + 1 == MAX_CONNECTIONS) - { - local_persist HeaderMessage header = HEADER_INIT(HEADER_TYPE_ERROR); - local_persist ErrorMessage message = ERROR_INIT(ERROR_TYPE_TOOMANYCONNECTIONS); - sendAnyMessage(clientfd, header, &message); - if (clientfd != -1) - close(clientfd); - LoggingF("Max clients reached. Rejected connection\n"); - } - else - { - // no more space, allocate - struct pollfd* pollfd = ArenaPush(&fdsArena, sizeof(*pollfd)); - pollfd->fd = clientfd; - LoggingF("Added pollfd(%d)\n", clientfd); - } - } - - for (u32 conn = FDS_CLIENTS; conn < FDS_SIZE; conn++) - { - if (!(fds[conn].revents & POLLIN)) continue; - if (fds[conn].fd == -1) continue; - LoggingF("Message(%d)\n", fds[conn].fd); - - // We received a message, try to parse the header - HeaderMessage header; - s32 nrecv = recv(fds[conn].fd, &header, sizeof(header), 0); - if(nrecv == -1) - { - LoggingF("Received error from fd: %d, errno: %d\n", fds[conn].fd, errno); - }; - - Client* client; - if (nrecv != sizeof(header)) - { - client = getClientByFD(clients, nclients, fds[conn].fd); - if (client) - { - LoggingF("Received %d/%lu bytes "CLIENT_FMT"\n", nrecv, sizeof(header), CLIENT_ARG((*client))); - disconnectAndNotify(clients, nclients, client); - } - else - { - LoggingF("Got error/disconnect from unauthenticated client\n"); - close(fds[conn].fd); - fds[conn].fd = -1; - } - continue; - } - LoggingF("Received(%d): " HEADER_FMT "\n", fds[conn].fd, HEADER_ARG(header)); - - // Authentication - if (!header.id) - { - LoggingF("No client for connection(%d)\n", fds[conn].fd); - - client = authenticate(&clientsArena, clients_file, fds + conn, header); - - if (!client) - { - LoggingF("Could not initialize client (%d)\n", fds[conn].fd); - close(fds[conn].fd); - fds[conn].fd = -1; - } - /* This is the first time a message is sent, because unifd is not yet set. */ - else if (!client->unifd) - { - LoggingF("Send connected message\n"); - local_persist HeaderMessage header = HEADER_INIT(HEADER_TYPE_PRESENCE); - header.id = client->id; - PresenceMessage message = {.type = PRESENCE_TYPE_CONNECTED}; - sendToOthers(clients, nclients, client, UNIFD, &header, &message); - } - continue; - } - - client = getClientByID(clients, nclients, header.id); - if (!client) - { - LoggingF("No client for id %d\n", fds[conn].fd); - - header.type = HEADER_TYPE_ERROR; - ErrorMessage message = ERROR_INIT(ERROR_TYPE_NOTFOUND); - - sendAnyMessage(fds[conn].fd, header, &message); - - // Reject connection - fds[conn].fd = -1; - close(fds[conn].fd); - continue; - } - - switch (header.type) { - /* Send text message to all other clients */ - case HEADER_TYPE_TEXT: - { - TextMessage* text_message = recvTextMessage(&msgsArena, fds[conn].fd); - LoggingF("Received(%d): ", fds[conn].fd); - printTextMessage(text_message, client, 0); - - sendToOthers(clients, nclients, client, UNIFD, &header, text_message); - } break; - /* Send back client information */ - case HEADER_TYPE_ID: - { - IDMessage id_message; - s32 nrecv = recv(fds[conn].fd, &id_message, sizeof(id_message), 0); - assert(nrecv == sizeof(id_message)); - - client = getClientByID(clients, nclients, id_message.id); - if (!client) - { - header.type = HEADER_TYPE_ERROR; - ErrorMessage message = ERROR_INIT(ERROR_TYPE_NOTFOUND); - s32 nsend = sendAnyMessage(fds[conn].fd, header, &message); - assert(nsend != -1); - break; - } - - HeaderMessage header = HEADER_INIT(HEADER_TYPE_INTRODUCTION); - IntroductionMessage introduction_message; - header.id = client->id; - memcpy(introduction_message.author, client->author, AUTHOR_LEN); - - nrecv = sendAnyMessage(fds[conn].fd, header, &introduction_message); - assert(nrecv != -1); - } break; - default: - LoggingF("Unhandled '%s' from "CLIENT_FMT"(%d)\n", headerTypeString(header.type), - CLIENT_ARG((*client)), - fds[conn].fd); - disconnectAndNotify(client, nclients, client); - continue; - } - } - } - -#ifdef IMPORT_ID - close(clients_file); -#endif - - return 0; -} diff --git a/source/archived/array.h b/source/archived/array.h new file mode 100644 index 0000000..04d2f38 --- /dev/null +++ b/source/archived/array.h @@ -0,0 +1,70 @@ +#include "chatty.h" + +u64 ArrayLength(u8 *Array); +void ArrayInsert(u8 *Array, u64 Position, u8 Element); +void ArrayCopy(u8 *To, u8 *From); +void ArrayDelete(u8* Array, u64 Position); +u8* ArrayCreate(u8* Container, u64 Capacity); + +// EXAMPLE: CREATE +// +// u64 Capacity = 15; +// u8 ArrayContainer[Capacity + sizeof(Capacity)]; +// u8* Array = ArrayCreate(ArrayContainer, Capacity); +// +// EXAMPLE: API +// +// ArrayCopy(Array, (u8*)"Hello, world!"); +// +// ArrayInsert(Array, 3, 'f'); +// ArrayInsert(Array, 3, 'e'); +// Array[14] = 'd'; +// ArrayDelete(Array, 3); + +#ifdef ARRAY_IMPL + +#include +#include + +u64 +ArrayLength(u8 *Array) +{ + return *((u64*)(Array - sizeof(u64))); +} + +void +ArrayInsert(u8 *Array, u64 Position, u8 Element) +{ + memmove(Array + Position + 1, Array + Position, ArrayLength(Array) - Position - 1); + Array[Position] = Element; +} + +// Copy null terminated string without copying over the null terminator +void +ArrayCopy(u8 *To, u8 *From) +{ + u32 i = 0; + while (From[i]) + { + To[i] = From[i]; + i++; + } +} + +void +ArrayDelete(u8* Array, u64 Position) +{ + memmove(Array + Position, Array + Position + 1, ArrayLength(Array) - Position - 1); + Array[ArrayLength(Array) - 1] = 0; +} + +u8* +ArrayCreate(u8* Container, u64 Capacity) +{ + *(u64*)Container = Capacity; + u8 *Array = Container + sizeof(Capacity); + bzero(Array, Capacity); + return Array; +} + +#endif diff --git a/source/archived/input_box.c b/source/archived/input_box.c new file mode 100644 index 0000000..729af02 --- /dev/null +++ b/source/archived/input_box.c @@ -0,0 +1,81 @@ +#define Assert(expr) if (!(expr)) { \ + tb_shutdown(); \ + raise(SIGTRAP); \ +} + +#define TB_IMPL +#include "termbox2.h" +#undef TB_IMPL + +#define TEXTBOX_MAX_INPUT 255 + +#define UI_IMPL +#include "ui.h" + +#define ARENA_IMPL +#include "arena.h" + +#include + +#include +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; +typedef u32 b32; + +int +main(void) +{ + struct tb_event ev = {0}; + + u32 InputLen = 0; + wchar_t Input[TEXTBOX_MAX_INPUT] = {0}; + rect TextBox = {0, 0, 32, 5}; + rect TextR = { + 2, 1, + TextBox.W - 2*TEXTBOX_PADDING_X - 2*TEXTBOX_BORDER_WIDTH, + TextBox.H - 2*TEXTBOX_BORDER_WIDTH + }; + // Used for scrolling the text. Text before TextOffset will not be printed. + u32 TextOffset = 0; + // Position in input based on cursor position. + u32 InputPos = 0; + + Assert(setlocale(LC_ALL, "")); + tb_init(); + bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); + global.cursor_x = TextR.X; + global.cursor_y = TextR.Y; + + DrawBox(TextBox, 0); + + while (ev.key != TB_KEY_CTRL_C) + { + DrawTextBoxWrapped(TextR, Input + TextOffset, InputLen - TextOffset); + + InputPos = TextOffset + (global.cursor_x - TextR.X) + (global.cursor_y - TextR.Y) * TextR.W; + Assert(InputPos <= InputLen); + + tb_present(); + tb_poll_event(&ev); + + u32 Ret = TextBoxKeypress(ev, TextR, Input, &InputLen, InputPos, &TextOffset); + + u32 ShouldInsert = (!Ret) && (ev.ch && InputLen < TEXTBOX_MAX_INPUT); + // Insert new character in Input at InputPos + if (ShouldInsert) + { + TextBoxInsert(Input, InputPos, InputLen++, ev.ch); + TextBoxScrollRight(TextR, &TextOffset); + } + } + + + tb_shutdown(); + return 0; +} diff --git a/source/archived/network_compression.c b/source/archived/network_compression.c new file mode 100644 index 0000000..eef9e3c --- /dev/null +++ b/source/archived/network_compression.c @@ -0,0 +1,154 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; + +size_t +GetMaximumCompressedOutputSize(size_t FileSize) +{ + // TODO: figure out equation + return FileSize*2 + 256; +} + +u32 +RLECompress(size_t InSize, u8* In, size_t MaxOutSize, u8* OutBase) +{ + u8* Out = OutBase; + +#define MAX_LITERAL_COUNT 255 +#define MAX_RUN_COUNT 255 + u32 LiteralCount = 0; + u8 Literals[MAX_LITERAL_COUNT] = {0}; + + u8 *InEnd = In + InSize; + while(In < InEnd) + { + u8 StartingValue = In[0]; + size_t Run = 1; // first one is the character itself + while((Run < (InEnd - In)) && + (Run < MAX_RUN_COUNT) && + (In[Run] == StartingValue)) + { + ++Run; + } + + if ((Run > 1) || + (LiteralCount == MAX_LITERAL_COUNT)) // stop doing runs when there is no + // space left in the buffer. + { + // Encode a literal/run pair + u8 LiteralCount8 = (u8)LiteralCount; + assert(LiteralCount8 == LiteralCount); + *Out++ = LiteralCount8; + + for(u32 LiteralIndex = 0; + LiteralIndex < LiteralCount; + ++LiteralIndex) + { + *Out++ = Literals[LiteralIndex]; + } + LiteralCount = 0; + + u8 Run8 = (u8)Run; + assert(Run8 == Run); + *Out++ = Run8; + + *Out++ = StartingValue; + + In += Run; + } + else + { + // Buffer literals, you have to because we are encoding them in pairs + Literals[LiteralCount++] = StartingValue; + ++In; + } + + } +#undef MAX_LITERAL_COUNT +#undef MAX_RUN_COUNT + + assert(In == InEnd); + + size_t OutSize = Out - OutBase; + assert(OutSize <= MaxOutSize); + + return OutSize; +} + +void +RLEDecompress(size_t InSize, u8* In, size_t OutSize, u8* Out) +{ + u8 *InEnd = In + InSize; + while(In < InEnd) + { + // TODO: I think Casey made a mistake and this should be an u8 + u8 LiteralCount = *In++; + while(LiteralCount--) + { + *Out++ = *In++; + } + + // Alternate to Run + u8 RepCount = *In++; + u8 RepValue = *In++; + while(RepCount--) + { + *Out++ = RepValue; + } + } + + assert(In == InEnd); +} + +int main(int Argc, char* Argv[]) { + + if (Argc < 2) + { + fprintf(stderr, "Usage: %s [compress|decompress]\n", Argv[0]); + return 1; + } + + char* Command = Argv[1]; + + if (!strcmp(Command, "compress")) + { + u32* Message = (u32*)L"abcabcbbbbbbb"; + size_t MessageSize = wcslen((const wchar_t*)Message) * 4; + + size_t OutBufferSize = GetMaximumCompressedOutputSize(MessageSize); + u8 OutBuffer[OutBufferSize + 8]; + bzero(OutBuffer, OutBufferSize + 8); + *(u32*)OutBuffer = MessageSize; + + size_t CompressedSize = RLECompress(MessageSize, (u8*)Message, OutBufferSize, OutBuffer + 8); + + s32 OutFile = open("test.compressed", O_WRONLY | O_CREAT, 0600); + + write(OutFile, OutBuffer, CompressedSize); + + fprintf(stdout, "%lu -> %lu bytes\n", MessageSize, CompressedSize); + } + else if (!strcmp(Command, "decompress")) + { + fprintf(stderr, "Not implemented yet.\n"); + } + else + fprintf(stderr, "Unknown command: '%s'\n", Command); + + return 0; +} diff --git a/source/archived/scrollandwrapped.c b/source/archived/scrollandwrapped.c new file mode 100644 index 0000000..3cd3a42 --- /dev/null +++ b/source/archived/scrollandwrapped.c @@ -0,0 +1,225 @@ +#define TB_IMPL +#include "external/termbox2.h" +#undef TB_IMPL + +#define MAX_INPUT_LEN 255 +#define DEBUG + +#define CHATTY_IMPL +#include "ui.h" + +#undef Assert +#define Assert(expr) \ + if (!(expr)) \ + { \ + tb_shutdown(); \ + raise(SIGTRAP); \ + } + +void +Right(rect TextR, s32 *TextOffset) +{ + if (global.cursor_x == TextR.X + TextR.W - 1) + { + if (global.cursor_y == TextR.Y + TextR.H - 1) + { + *TextOffset += TextR.W; + } + else + { + global.cursor_y++; + } + global.cursor_x = TextR.X; + } + else + { + global.cursor_x++; + } +} + +void +Left(rect TextR, s32 *TextOffset) +{ + if (global.cursor_x == TextR.X) + { + if (global.cursor_y == TextR.Y) + { + *TextOffset -= TextR.W; + } + else + { + global.cursor_y--; + } + global.cursor_x = TextR.X + TextR.W - 1; + } + else + { + global.cursor_x--; + } + +} + +void +PrintString(s32 X, s32 Y, s32 FG, s32 BG, wchar_t *Text, s32 TextLen) +{ + for (s32 At = 0; + At < TextLen; + At++) + { +#ifdef DEBUG + tb_set_cell(X + At, Y, Text[At], TB_BLACK, TB_CYAN); + tb_present(); +#else + tb_set_cell(X + At, Y, Text[At], FG, BG); +#endif + } +} + +int +main(int Argc, char* Argv[]) +{ + wchar_t Text[MAX_INPUT_LEN] = {0}; + s32 TextLen = 0; + s32 TextPos = 0; + s32 TextOffset = 0; + rect BoxR = {0, 0, 24, 4}; + rect TextR = TEXTBOXFROMBOX(BoxR); + struct tb_event ev = {0}; + + Assert(setlocale(LC_ALL, "")); + tb_init(); + + bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); + global.cursor_x = TextR.X; + global.cursor_y = TextR.Y; + +#if 0 + { + wchar_t DummyText[] = L"This is some dummy text m" + "eant to be wrapped sever" + "al times so I can try ou" + "t how scrolling works"; + s32 Len = wcslen(DummyText); + wcsncpy(Text, DummyText, Len); + TextLen = Len; + } +#endif + + while (ev.key != TB_KEY_CTRL_C) + { + tb_clear(); + DrawBox(BoxR, 0); + +#if 1 + { + s32 SearchIndex = TextR.W; + s32 PrevIndex = 0; + s32 BreakOffset = 0; + s32 YOffset = 0; + + if (TextLen - TextOffset <= TextR.W) + { + PrintString(TextR.X, TextR.Y, 0, 0, Text + TextOffset, TextLen); + } + else + { + while (SearchIndex < TextLen - TextOffset) + { + while (Text[SearchIndex] != ' ') + { + SearchIndex--; + if (SearchIndex == PrevIndex) + { + SearchIndex += TextR.W; + break; + } + } + + BreakOffset = (PrevIndex == SearchIndex) ? TextR.W : SearchIndex - PrevIndex; + PrintString(TextR.X, TextR.Y + YOffset, 0, 0, + Text + TextOffset + PrevIndex, BreakOffset); + + YOffset++; + if (YOffset == TextR.H) + { + break; + } + + PrevIndex = SearchIndex; + SearchIndex += TextR.W; + } + + if (YOffset < TextR.H) + { + PrintString(TextR.X, TextR.Y + YOffset, 0, 0, + Text + TextOffset + PrevIndex, + TextLen - TextOffset - PrevIndex); + } + } + + + } +#endif + + tb_printf(BoxR.X, BoxR.Y, 0, 0, + "#%d/%d +%d", + TextPos, TextLen, + TextOffset); + tb_printf(BoxR.X, BoxR.Y + BoxR.H, 0, 0, + "'%lc'/'%lc'", + ev.ch ? ev.ch : L'|', Text[TextPos] ? ev.ch : L'|'); + + tb_present(); + tb_poll_event(&ev); + switch (ev.key) + { + case TB_KEY_ARROW_RIGHT: + { + if (TextPos == TextLen) break; + TextPos++; + Right(TextR, &TextOffset); + continue; + } break; + case TB_KEY_ARROW_LEFT: + { + if (TextPos == 0) break; + TextPos--; + Left(TextR, &TextOffset); + continue; + } break; + case TB_KEY_CTRL_8: + { + if (TextPos == 0) break; + TextPos--; + TextLen--; + memmove(Text + TextPos, + Text + TextPos + 1, + (MAX_INPUT_LEN - TextPos - 1)*sizeof(*Text)); + Left(TextR, &TextOffset); + continue; + } break; + } + + if (!ev.mod && ev.ch && TextLen < MAX_INPUT_LEN) + { + if (TextPos < TextLen) + { + memmove(Text + TextPos + 1, + Text + TextPos, + (MAX_INPUT_LEN - TextPos - 1)*sizeof(*Text)); + } + Text[TextPos] = ev.ch; + TextPos++; + TextLen++; + + // Advance more if wrapping will happen + if (global.cursor_x == TextR.X + TextR.W - 1 && + Text) + + Right(TextR, &TextOffset); + } + } + + tb_shutdown(); + return 0; +} diff --git a/source/archived/send.c b/source/archived/send.c new file mode 100644 index 0000000..396b337 --- /dev/null +++ b/source/archived/send.c @@ -0,0 +1,80 @@ +// minimal client implementation + +#include +#include +#include +#include +#include +#include + +#define CHATTY_IMPL +#include "chatty.h" +#undef CHATTY_IMPL +#include "protocol.h" + +int +main(int argc, char** argv) +{ + if (argc < 3) { + fprintf(stderr, "usage: send \n"); + return 1; + } + + s32 err, serverfd; + + serverfd = socket(AF_INET, SOCK_STREAM, 0); + assert(serverfd != -1); + + const struct sockaddr_in address = {AF_INET, htons(PORT), {0}, {0}}; + err = connect(serverfd, (struct sockaddr*)&address, sizeof(address)); + assert(err == 0); + + // Get our ID + ID id = 0; + { + // get author len + u32 author_len = strlen(argv[1]); + assert(author_len + 1 <= AUTHOR_LEN); // add 1 for null terminator + + // Introduce ourselves + HeaderMessage header = HEADER_INIT(HEADER_TYPE_INTRODUCTION); + IntroductionMessage message; + memcpy(message.author, argv[1], author_len); + s32 nsend = sendAnyMessage(serverfd, header, &message); + assert(nsend != -1); + + // Get id + IDMessage id_message; + s32 nrecv = recvAnyMessageType(serverfd, &header, &id_message, HEADER_TYPE_ID); + assert(nrecv != -1); + fprintf(stderr, "Got id: %lu\n", id_message.id); + id = id_message.id; + } + + // convert text to wide string + u32 text_len = strlen(argv[2]) + 1; + u32 text_wide[text_len]; + u32 size = mbstowcs((wchar_t*)text_wide, argv[2], text_len - 1); + assert(size == text_len - 1); + text_wide[text_len - 1] = 0; + + HeaderMessage header = HEADER_INIT(HEADER_TYPE_TEXT); + header.id = id; + TextMessage message; + bzero(&message, TEXTMESSAGE_SIZE); + message = (TextMessage){.timestamp = time(NULL), .len = text_len}; + + s32 nsend = send(serverfd, &header, sizeof(header), 0); + assert(nsend != -1); + fprintf(stderr, "header bytes sent: %d\n", nsend); + + nsend = send(serverfd, &message, TEXTMESSAGE_SIZE, 0); + assert(nsend != -1); + fprintf(stderr, "message bytes sent: %d\n", nsend); + + u32 text_size = message.len * sizeof(*message.text); + nsend = send(serverfd, text_wide, text_size, 0); + fprintf(stderr, "text bytes sent: %d\n", nsend); + + return 0; +} diff --git a/source/archived/ui_checkmark.c b/source/archived/ui_checkmark.c new file mode 100644 index 0000000..df7e507 --- /dev/null +++ b/source/archived/ui_checkmark.c @@ -0,0 +1,87 @@ +#define TB_IMPL +#include "../chatty/external/termbox2.h" + +int +main(void) +{ + struct tb_event ev = {0}; + + typedef struct { + int X, Y; + int Checked; + } Checkmark; + +#define NUM_CHECKMARKS 4 + int Y = 0; + Checkmark Marks[NUM_CHECKMARKS] = { + {0, Y++, 0}, + {0, Y++, 0}, + {0, Y++, 1}, + {0, Y++, 0} + }; + Y++; + + int Selected = 0; + + tb_init(); + + int Quit = 0; + while (!Quit) + { + tb_clear(); + + for (int CheckmarkIndex = 0; + CheckmarkIndex < NUM_CHECKMARKS; + CheckmarkIndex++) + { + Checkmark Mark = Marks[CheckmarkIndex]; + if (Mark.Checked) + { + tb_printf(Mark.X, Mark.Y, 0, 0, "[x]"); + } + else + { + tb_printf(Mark.X, Mark.Y, 0, 0, "[ ]"); + } + } + Checkmark Mark = Marks[Selected]; + if (Mark.Checked) + { + tb_set_cell(Mark.X + 1, Mark.Y, L'x', TB_UNDERLINE, 0); + } + else + { + tb_set_cell(Mark.X + 1, Mark.Y, L' ', TB_UNDERLINE, 0); + } + + int BaseY = Y; + tb_printf(0, BaseY, TB_BOLD, 0, "j"); tb_printf(2, BaseY++, 0, 0, "next"); + tb_printf(0, BaseY, TB_BOLD, 0, "k"); tb_printf(2, BaseY++, 0, 0, "previous"); + tb_printf(0, BaseY, TB_BOLD, 0, "c"); tb_printf(2, BaseY++, 0, 0, "toggle"); + tb_printf(0, BaseY, TB_BOLD, 0, "q"); tb_printf(2, BaseY++, 0, 0, "quit"); + + tb_present(); + + tb_poll_event(&ev); + if (ev.ch == 'q') + { + Quit = 1; + } + else if (ev.ch == 'j') + { + if (Selected == NUM_CHECKMARKS - 1) Selected = 0; + else Selected++; + } + else if (ev.ch == 'k') + { + if (Selected) Selected--; + else Selected = NUM_CHECKMARKS - 1; + } + else if (ev.ch == 'c') + { + Marks[Selected].Checked = !Marks[Selected].Checked; + } + } + + tb_shutdown(); +} diff --git a/source/archived/ui_meter b/source/archived/ui_meter new file mode 100755 index 0000000..c12b0fe Binary files /dev/null and b/source/archived/ui_meter differ diff --git a/source/archived/ui_meter.c b/source/archived/ui_meter.c new file mode 100644 index 0000000..b0ee5a0 --- /dev/null +++ b/source/archived/ui_meter.c @@ -0,0 +1,108 @@ +#define TB_IMPL +#include + +#include +#include +#include +#include + +#define Assert(expr) \ + if (!(expr)) \ + { \ + tb_shutdown(); \ + raise(SIGTRAP); \ + } + +#define METER_WIDTH 4 + +static char ***EnvironmentPath = 0; + +void +draw_meter(int X, int Y, int Height, int MaxLevel, int Level) +{ + int FillColor = 0; + + Height -= 2; // substract top and bottom border + + tb_printf(X, Y, 0, 0, "%ls", L"┌──┐"); + tb_printf(X, Y + Height + 1, 0, 0, "%ls", L"└ ┘"); + + if (Level == MaxLevel) + { + FillColor = TB_GREEN; + tb_printf(X + 1, Y + Height + 1, 0, 0, "00"); + } + else + { + FillColor = TB_CYAN; + tb_printf(X + 1, Y + Height + 1, 0, 0, "%02d", Level); + } + + int FilledLevel = (Level * Height)/MaxLevel; + + for (int LevelIndex = 0; LevelIndex < FilledLevel; LevelIndex++) + { + int YLevel = Y + (Height - LevelIndex); + tb_set_cell(X + 1, YLevel, L'▒', FillColor, FillColor); + tb_set_cell(X + 2, YLevel, L'▒', FillColor, FillColor); + } + + for (int YOffset = 0; YOffset < Height; YOffset++) + { + tb_set_cell(X + 0, Y + YOffset + 1, L'│', 0, 0); + tb_set_cell(X + 3, Y + YOffset + 1, L'│', 0, 0); + } + +} + +int +main(int Argc, char *Args[], char *Envp[]) +{ + EnvironmentPath = &Envp; + + struct tb_event ev = {0}; + Assert(setlocale(LC_ALL, "")); + int Quit = 0; + int Level = 0; + int LevelStep = 10; + int MeterMaxLevel = 100; + int MeterHeight = 10 + 2; + + tb_init(); + tb_set_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE); + + while (!Quit) + { + tb_clear(); + + int Y = 0; + draw_meter(1, Y, MeterHeight, MeterMaxLevel, Level); + Y += MeterHeight + 1;; + + tb_print(0, Y, TB_BOLD, 0, "j"); tb_print(2, Y++, 0, 0, "decrease"); + tb_print(0, Y, TB_BOLD, 0, "k"); tb_print(2, Y++, 0, 0, "increase"); + tb_print(0, Y, TB_BOLD, 0, "q"); tb_print(2, Y++, 0, 0, "to quit"); + + tb_present(); + + tb_poll_event(&ev); + + if (ev.ch == 'q' || ev.key == TB_KEY_CTRL_C) Quit = 1; + else if ((ev.ch == 'j' || ev.key == TB_KEY_MOUSE_WHEEL_DOWN) && + Level > 0) + { + if (LevelStep > Level) Level = 0; + else Level -= LevelStep; + } + else if ((ev.ch == 'k' || ev.key == TB_KEY_MOUSE_WHEEL_UP) && + Level < MeterMaxLevel) + { + if (Level + LevelStep > MeterMaxLevel) Level = MeterMaxLevel; + else Level += LevelStep; + } + + } + + tb_shutdown(); + return 0; +} diff --git a/source/archived/ui_selection.c b/source/archived/ui_selection.c new file mode 100644 index 0000000..3e45ee2 --- /dev/null +++ b/source/archived/ui_selection.c @@ -0,0 +1,71 @@ +#define TB_IMPL +#include "../chatty/external/termbox2.h" + +#include + +#define Assert(expr) if (!(expr)) raise(SIGTRAP) + +typedef struct { + int X, Y; +} Position; + +int +main(void) +{ + int Selected = 0; + int NumChoices = 3; + int Y = 0; + + Position Positions[NumChoices]; + Positions[0] = (Position){1, Y}; + Positions[1] = (Position){5, Y}; + Positions[2] = (Position){9, Y}; + Y += 2; + + struct tb_event ev = {0}; + int Color = TB_GREEN; + + tb_init(); + + int Quit = 0; + while (!Quit) + { + tb_clear(); + + int BaseY = Y; + tb_printf(0, BaseY, TB_BOLD, 0, "j"); tb_printf(2, BaseY++, 0, 0, "select next"); + tb_printf(0, BaseY, TB_BOLD, 0, "k"); tb_printf(2, BaseY++, 0, 0, "select previous"); + tb_printf(0, BaseY, TB_BOLD, 0, "s"); tb_printf(2, BaseY++, 0, 0, "change color"); + tb_printf(0, BaseY, TB_BOLD, 0, "q"); tb_printf(2, BaseY++, 0, 0, "quit"); + + // Draw a box at position + for (int PositionsIndex = 0; PositionsIndex < NumChoices; PositionsIndex++) + { + Assert(Positions[PositionsIndex].X > 0); + tb_printf(Positions[PositionsIndex].X - 1, Positions[PositionsIndex].Y, Color, 0, "[ ]"); + } + // Draw mark in selected box + tb_printf(Positions[Selected].X, Positions[Selected].Y, Color, 0, "x"); + + tb_present(); + tb_poll_event(&ev); + + if (ev.ch == 'q') Quit = 1; + else if (ev.ch == 'j' && Selected < NumChoices - 1) Selected++; + else if (ev.ch == 'k' && Selected) Selected--; + else if (ev.ch == 's') + { + switch (Selected) + { + case 0: Color = TB_GREEN; break; + case 1: Color = TB_BLUE; break; + case 2: Color = TB_RED; break; + default: Assert(0); break; + } + } + } + + tb_shutdown(); + + return 0; +} diff --git a/source/archived/ui_wrapped.c b/source/archived/ui_wrapped.c new file mode 100644 index 0000000..67ddffe --- /dev/null +++ b/source/archived/ui_wrapped.c @@ -0,0 +1,82 @@ +#define MAX_INPUT_LEN 255 + +#define TB_IMPL +#include "external/termbox2.h" +#undef TB_IMPL +#define CHATTY_IMPL +#include "ui.h" +#undef CHATTY_IMPL + +void +DrawBoxTest(rect Box, wchar_t *DummyText) +{ + u32 TextLen = wcslen(DummyText); + + wchar_t Text[TextLen]; + bzero(Text, sizeof(Text)); + wcsncpy(Text, DummyText, TextLen + 1); // copy n*ull terminator + + rect TextR = TEXTBOXFROMBOX(Box); + // fill the cursor for reference + for (s32 Y = 0; Y < TextR.H; Y++) + for (s32 X = 0; X < TextR.W; X++) + tb_printf(TextR.X + X, TextR.Y + Y, 0, TB_BLUE, " "); + + DrawBox(Box, 0); + tb_present(); + TextBoxDrawWrapped(TextR, Text, TextLen); + tb_printf(0, TextR.Y - 1, 0, 0, + "%d (%d, %d) ~(%d, %d)", + TextLen, + global.cursor_x, global.cursor_y, + global.cursor_x - TextR.X, global.cursor_y - TextR.Y); + tb_printf(global.cursor_x, global.cursor_y, 0, TB_RED, " "); +} + +struct tb_cell +tb_get_cell(s32 X, s32 Y) +{ + return global.back.cells[global.width * Y + X]; +} + + +int +main(int Argc, char* Argv[]) +{ + struct tb_event ev; + rect Box = {0, 0, 24, 4}; + + setlocale(LC_ALL, ""); + + tb_init(); + bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); + + /* Text that does not fit */ + DrawBoxTest(Box, L"This is some dummy text meant to be wrapped multiple times."); + Box.Y += Box.H; + + /* Text that fits */ + DrawBoxTest(Box, L"That is some."); + Box.Y += Box.H; + + /* Text ending on a space */ + DrawBoxTest(Box, L"There is something. "); + Box.Y += Box.H; + + /* Text that fits the surface */ + DrawBoxTest(Box, L"This is something. This is something."); + Box.Y += Box.H; + + /* Text that wraps once */ + DrawBoxTest(Box, L"This is something I do not."); + + rect NewBox = Box; + NewBox.X += Box.W + 2; + DrawBox(NewBox, 0); + + tb_present(); + tb_poll_event(&ev); + + tb_shutdown(); + return 0; +} diff --git a/source/archived/utf8toASCII.c b/source/archived/utf8toASCII.c new file mode 100644 index 0000000..988a69b --- /dev/null +++ b/source/archived/utf8toASCII.c @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include +#include +#include + +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; + +static size_t +UTF8Compress(size_t InSize, wchar_t* In, size_t OutSize, u8* OutBase) +{ + wchar_t* InEnd = (wchar_t*)((u8*)In + InSize); + u8* Out = OutBase; + +#define MAX_LITERAL_COUNT 255 + u8 ASCIILiterals[MAX_LITERAL_COUNT] = {0}; + u8 ASCIILiteralsCount = 0; + wchar_t UTF8Literals[MAX_LITERAL_COUNT] = {0}; + u8 UTF8LiteralsCount = 0; + + while (In < InEnd) + { + wchar_t CurrentChar = In[0]; + + // Check consecutive ascii characters + while(CurrentChar == (u8)CurrentChar && + ASCIILiteralsCount < MAX_LITERAL_COUNT) + { + ASCIILiterals[ASCIILiteralsCount++] = (u8)CurrentChar; + CurrentChar = *++In; + } + + while(CurrentChar != (u8)CurrentChar && + UTF8LiteralsCount < MAX_LITERAL_COUNT) + { + UTF8Literals[UTF8LiteralsCount++] = CurrentChar; + CurrentChar = *++In; + } + + // Encode ASCII/UTF8 pair + *Out++ = ASCIILiteralsCount; + for (u8 ch = 0; + ch < ASCIILiteralsCount; + ch++) + { + *Out = ASCIILiterals[ch]; + Out += sizeof(ASCIILiterals[ch]); + } + ASCIILiteralsCount = 0; + + *Out++ = UTF8LiteralsCount; + for (u8 ch = 0; + ch < UTF8LiteralsCount; + ch++) + { + *(wchar_t*)Out = UTF8Literals[ch]; + Out += sizeof(UTF8Literals[ch]); + } + UTF8LiteralsCount = 0; + + } +#undef MAX_LITERAL_COUNT + assert(In == InEnd); + + return Out - OutBase; +} + +static void +PrintCompressedUTF8(u8* In, size_t InSize) +{ + u8* InEnd = In + InSize; + + while (In < InEnd) + { + u8 ASCIICount = *In++; + wprintf(L"%dA(\"", ASCIICount); + while(ASCIICount--) + { + wprintf(L"%c", *In); + In += sizeof(u8); + } + wprintf(L"\") "); + + u8 UTF8Count = *In++; + wprintf(L"%dU(\"", UTF8Count); + while(UTF8Count--) + { + wprintf(L"%lc", *(wchar_t*)In); + In += sizeof(wchar_t); + } + wprintf(L"\") "); + } + wprintf(L"\n"); + + assert(In == InEnd); +} + +static void +UTF8Decompress(size_t InSize, u8* In, size_t OutSize, wchar_t* Out) +{ + u8* InEnd = In + InSize; + + while (In < InEnd) + { + u8 ASCIICount = *In++; + while(ASCIICount--) + { + *Out++ = *In++; + } + + u8 UTF8Count = *In++; + while(UTF8Count--) + { + *Out++ = *(wchar_t*)In; + In += sizeof(wchar_t); + } + } + assert(In == InEnd); +} + +// Size is the size of the UTF8 string in bytes. "aaa" would be 12. +size_t +UTF8GetMaximumCompressedSize(size_t Size) +{ + // The largest would be if there was only one unicode point in which case we store 0 for ascii 1 + // for unicode and the raw codepoint. 1 + 1 + 4 * CodepointNum + return Size + 2; +} + +int +main(int Argc, char* Argv[]) { + assert(setlocale(LC_ALL, "") != 0); + + wchar_t* InBuf = L"text│tt│"; + size_t InSize = wcslen(InBuf) * 4; + + size_t OutSize = UTF8GetMaximumCompressedSize(InSize); + u8 OutBuf[OutSize]; + + size_t CompressedSize = UTF8Compress(InSize, InBuf, OutSize, OutBuf); + + fwprintf(stderr, L"Raw string: \"%ls\"\n", InBuf); + fwprintf(stderr, L"Compressed %lu bytes -> %lu bytes.\n", InSize, CompressedSize); + + size_t DecompressedSize = InSize; + wchar_t *DecompressedBuffer = malloc(DecompressedSize); + + UTF8Decompress(CompressedSize, OutBuf, DecompressedSize, DecompressedBuffer); + fwprintf(stderr, L"Decompressed: \"%ls\"\n", DecompressedBuffer); + + PrintCompressedUTF8(OutBuf, CompressedSize); + + return 0; +} diff --git a/source/archived/v1/README.md b/source/archived/v1/README.md new file mode 100644 index 0000000..2cf0aee --- /dev/null +++ b/source/archived/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/source/archived/v1/arena.h b/source/archived/v1/arena.h new file mode 100644 index 0000000..6f371f4 --- /dev/null +++ b/source/archived/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/source/archived/v1/build.sh b/source/archived/v1/build.sh new file mode 100755 index 0000000..a80b193 --- /dev/null +++ b/source/archived/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/source/archived/v1/client.c b/source/archived/v1/client.c new file mode 100644 index 0000000..878b678 --- /dev/null +++ b/source/archived/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/source/archived/v1/common.h b/source/archived/v1/common.h new file mode 100644 index 0000000..c7bb0dd --- /dev/null +++ b/source/archived/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/source/archived/v1/compile_flags.txt b/source/archived/v1/compile_flags.txt new file mode 100644 index 0000000..1a62790 --- /dev/null +++ b/source/archived/v1/compile_flags.txt @@ -0,0 +1,5 @@ +-Wall +-Werror +-pedantic +-std=c99 +-O3 diff --git a/source/archived/v1/recv.c b/source/archived/v1/recv.c new file mode 100644 index 0000000..042d8a9 --- /dev/null +++ b/source/archived/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/source/archived/v1/send.c b/source/archived/v1/send.c new file mode 100644 index 0000000..27e9793 --- /dev/null +++ b/source/archived/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/source/archived/v1/server.c b/source/archived/v1/server.c new file mode 100644 index 0000000..7edf558 --- /dev/null +++ b/source/archived/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/source/archived/v1/termbox2.h b/source/archived/v1/termbox2.h new file mode 100644 index 0000000..265cdab --- /dev/null +++ b/source/archived/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 diff --git a/source/archived/wrap.c b/source/archived/wrap.c new file mode 100644 index 0000000..57506b8 --- /dev/null +++ b/source/archived/wrap.c @@ -0,0 +1,52 @@ +// 1. Search backwards for whitespace +// - found? +// y) wrap +// n) break at limit +// - end? +// y) terminate +// n) goto 1. with offset += limit +void +wrap(u8* Text, u32 Len, u32 XLimit, u32 YLimit) +{ + u32 SearchingOffset = XLimit; + u32 X = SearchingOffset; + u32 Y = 0; + u8 t; + u32 PrevX = 0; + + while (X < Len) + { + // Search for whitespace to break on + while (1) + { + if (is_whitespace(Text[X])) break; + + X--; + + // if we got back to the previous position break on Text[SearchingOffset] + if (X == PrevX) + { + X = XLimit; + break; + } + } + + // break + t = Text[X]; + *(Text + X) = '\0'; + tb_printf(0, Y, 0, 0, "%s", Text + PrevX); + Text[X] = t; + Y++; + if (Y >= YLimit) break; + + // consume leading whitespace + while (is_whitespace(Text[X])) X++; + + PrevX = X; + X += XLimit; + } + + tb_printf(0, Y, 0, 0, "%s", Text + PrevX); + + return; +} diff --git a/source/arena.h b/source/arena.h new file mode 100644 index 0000000..8571e6c --- /dev/null +++ b/source/arena.h @@ -0,0 +1,63 @@ +#ifndef ARENA_H +#define ARENA_H + +#include + +#include +typedef uint8_t A_u8; +typedef uint16_t A_u16; +typedef uint32_t A_u32; +typedef uint64_t A_u64; +typedef int8_t A_s8; +typedef int16_t A_s16; +typedef int32_t A_s32; +typedef int64_t A_s64; +typedef A_u32 A_b32; + +// Arena Allocator +typedef struct { + void* addr; + A_u64 size; + A_u64 pos; +} Arena; +#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) + +void ArenaAlloc(Arena* arena, A_u64 size); +void ArenaRelease(Arena* arena); +void* ArenaPush(Arena* arena, A_u64 size); + +#endif // ARENA_H + +#ifdef ARENA_IMPL + +// Returns arena in case of success, or 0 if it failed to alllocate the memory +void +ArenaAlloc(Arena* arena, A_u64 size) +{ + arena->addr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + Assert(arena->addr != MAP_FAILED); + arena->pos = 0; + arena->size = size; +} + +void +ArenaRelease(Arena* arena) +{ + munmap(arena->addr, arena->size); +} + +void* +ArenaPush(Arena* arena, A_u64 size) +{ + A_u8* mem; + mem = (A_u8*)arena->addr + arena->pos; + arena->pos += size; + Assert(arena->pos <= arena->size); + return mem; +} + +#undef ARENA_IMPL +#endif // ARENA_IMPL diff --git a/source/build.sh b/source/build.sh new file mode 100755 index 0000000..317e9a4 --- /dev/null +++ b/source/build.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +ScriptDir="$(dirname "$(readlink -f "$0")")" +cd "$ScriptDir" +BuildDir="$ScriptDir"/../build + +mkdir -p "$BuildDir" +printf 'chatty.c\n' +gcc -DDEBUG -ggdb -Wall -pedantic -std=c11 -I external -o "$BuildDir"/chatty chatty.c +printf 'server.c\n' +gcc -DDEBUG -ggdb -Wall -pedantic -std=c99 -o "$BuildDir"/server server.c + +# printf 'archived/input_box.c\n' +# gcc -DDEBUG -ggdb -Wall -pedantic -std=c11 -I external -I . -o "$BuildDir"/input_box archived/input_box.c diff --git a/source/chatty.c b/source/chatty.c new file mode 100644 index 0000000..32a4431 --- /dev/null +++ b/source/chatty.c @@ -0,0 +1,834 @@ +#define TB_IMPL +#include "termbox2.h" +#undef TB_IMPL + + +#include +#include +#include +#include +#include +#include + +#define TIMEOUT_POLL 60 * 1000 +// time to reconnect in seconds +#define TIMEOUT_RECONNECT 1 +#define MAX_INPUT_LEN 512 +// Filepath where user ID is stored +#define ID_FILE "_id" +// Filepath where logged +#define LOGFILE "chatty.log" +// enable logging +#define LOGGING +// Number of spaces inserted when pressing Tab/Ctrl+I +#define TAB_WIDTH 4 + +#ifndef Assert +#ifdef DEBUG +#define Assert(expr) if (!(expr)) \ + { \ + tb_shutdown(); \ + raise(SIGTRAP); \ + } +#else +#define Assert(expr) ; +#endif // DEBUG +#endif // Assert + + +#define ARENA_IMPL +#include "arena.h" + +#define CHATTY_IMPL +#include "chatty.h" + +#include "protocol.h" + +#define TEXTBOX_MAX_INPUT MAX_INPUT_LEN +#define UI_IMPL +#include "ui.h" + +enum { FDS_BI = 0, // for one-way communication with the server (eg. TextMessage) + FDS_UNI, // For two-way communication with the server (eg. IDMessage) + FDS_TTY, + FDS_RESIZE, + FDS_MAX }; + +typedef struct { + u8 Author[AUTHOR_LEN]; + ID ID; +} User; +#define USER_FMT "[%s](%lu)" +#define USER_ARG(client) client.Author, client.ID + +typedef struct { + s32 NumRead; + u32 Error; +} command_output; + +// User used by chatty +global_variable User user = {0}; +// Address of chatty server +global_variable struct sockaddr_in address; + +// fill str array with char +void +fillstr(u32* Str, u32 ch, u32 Len) +{ + for (u32 i = 0; i < Len; i++) + Str[i] = ch; +} + +// Centered popup displaying message in the appropriate cololrs +void +popup(u32 fg, u32 bg, u8* text) +{ + u32 len = strlen((char*)text); + Assert(len > 0); + tb_print(global.width / 2 - len / 2, global.height / 2, fg, bg, (char*)text); +} + +// Returns client in clientsArena matching id +// Returns user if the id was the user's ID +// Returns 0 if nothing was found +User* +get_user_by_id(Arena* clientsArena, ID id) +{ + // User is not in the clientsArena + if (id == user.ID) return &user; + + User* clients = clientsArena->addr; + for (u64 i = 0; i < (clientsArena->pos / sizeof(*clients)); i++) + { + if (clients[i].ID == id) + return clients + i; + } + return 0; +} + +// Request information of client from fd byd id and add it to clientsArena +// Returns pointer to added client +User* +add_user_info(Arena* clientsArena, s32 fd, u64 id) +{ + // Request information about ID + HeaderMessage header = HEADER_INIT(HEADER_TYPE_ID); + header.id = user.ID; + IDMessage message = {id}; + s32 nsend = sendAnyMessage(fd, header, &message); + Assert(nsend != -1); + + // Wait for response + IntroductionMessage introduction_message; + recvAnyMessageType(fd, &header, &introduction_message, HEADER_TYPE_INTRODUCTION); + + // Add the information + User* client = ArenaPush(clientsArena, sizeof(*client)); + memcpy(client->Author, introduction_message.author, AUTHOR_LEN); + client->ID = id; + + LoggingF("Got " USER_FMT "\n", USER_ARG((*client))); + return client; +} + +// Tries to connect to address and populates resulting file descriptors in ConnectionResult. +s32 +get_connection(struct sockaddr_in* address) +{ + s32 fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) return -1; + + s32 err = connect(fd, (struct sockaddr*)address, sizeof(*address)); + if (err) return -1; + + return fd; +} + +// Authenticates a file descriptor with either the user's id if non-zero or +// it's information if id is zero. +// Returns 0 if an error occurred. Non-zero on success. +u32 +authenticate(User* user, s32 fd) +{ + /* Scenario 1: Already have an ID */ + if (user->ID) + { + HeaderMessage header = HEADER_INIT(HEADER_TYPE_ID); + IDMessage message = {user->ID}; + s32 nsend = sendAnyMessage(fd, header, &message); + Assert(nsend != -1); + + ErrorMessage error_message; + s32 nrecv = recvAnyMessageType(fd, &header, &error_message, HEADER_TYPE_ERROR); + Assert(nrecv != -1); + // TODO: handle not found + if (nrecv == 0) + return 0; + + if (error_message.type == ERROR_TYPE_SUCCESS) + return 1; + else + return 0; + } + /* Scenario 2: No ID, request one from server */ + else + { + HeaderMessage header = HEADER_INIT(HEADER_TYPE_INTRODUCTION); + IntroductionMessage message; + memcpy(message.author, user->Author, AUTHOR_LEN); + s32 nsend = sendAnyMessage(fd, header, &message); + Assert(nsend != -1); + + IDMessage id_message; + s32 nrecv = recvAnyMessageType(fd, &header, &id_message, HEADER_TYPE_ID); + Assert(nrecv != -1); + user->ID = id_message.id; + return 1; + } +} + +// Connect to *address_ptr of type `struct sockaddr_in*`. If it failed wait for TIMEOUT_RECONNECT +// seconds. +// This function is meant to be run by a thread. +// An offline server means fds[FDS_SERVER] is set to -1. When online +// it is set to with the appropriate file descriptor. +// Returns 0. +#define Miliseconds(s) (s*1000*1000) +void* +thread_reconnect(void* fds_ptr) +{ + s32 unifd, bifd; + struct pollfd* fds = fds_ptr; + struct timespec t = { 0, Miliseconds(300) }; // 300 miliseconds + LoggingF("Trying to reconnect\n"); + while (1) + { + // timeout + nanosleep(&t, &t); + + bifd = get_connection(&address); + if (bifd == -1) + { + LoggingF("errno: %d\n", errno); + continue; + } + unifd = get_connection(&address); + if (unifd == -1) + { + LoggingF("errno: %d\n", errno); + close(bifd); + continue; + } + + LoggingF("Reconnect succeeded (%d, %d), authenticating\n", unifd, bifd); + + if (authenticate(&user, bifd) && + authenticate(&user, unifd)) + { + break; + } + + close(bifd); + close(unifd); + + LoggingF("Failed, retrying...\n"); + } + + fds[FDS_BI].fd = bifd; + fds[FDS_UNI].fd = unifd; + + // Redraw screen + raise(SIGWINCH); + + return 0; +} + +command_output +run_command_get_output(char *Command, char *Argv[], u8 *OutputBuffer, int Len) +{ + command_output Result = {0}; + + int CommandPipe[2]; + int Error = pipe(CommandPipe); + Assert(Error != -1); + + int Pid = fork(); + Assert(Pid != -1); + + // Run command in child + if (!Pid) + { + dup2(CommandPipe[1], STDOUT_FILENO); //redirect stdout to Pipe + close(CommandPipe[0]); + close(CommandPipe[1]); + + int fd = open("/dev/null", O_WRONLY); + dup2(fd, STDERR_FILENO); + + execvp(Command, Argv); + } + + // Wait for child + int statval; + waitpid(Pid, &statval, 0); + + if(WIFEXITED(statval)) + { + int ExitCode = WEXITSTATUS(statval); + if (ExitCode) + { + Result.Error = ExitCode; + } + } + else + { + Result.Error = 1; + return Result; + } + + close(CommandPipe[1]); + + Result.NumRead = read(CommandPipe[0], OutputBuffer, Len); + Assert(Result.NumRead != -1); + + return Result; +} + +// home screen, the first screen the user sees +// it displays a prompt with the user input of input_len wide characters +// and the received messages from msgsArena +void +DisplayChat(Arena* ScratchArena, + Arena* MessagesArena, u32 MessagesNum, + Arena* ClientsArena, struct pollfd* fds, + wchar_t Input[], u32 InputLen) +{ + rect TextBox = { + 1, 0, global.width - 2, 3, + }; + u32 FreeHeight = global.height - TextBox.H; + TextBox.Y = FreeHeight; + +#define MIN_TEXT_WIDTH_FOR_WRAPPING 20 + s32 MinBoxWidth = TEXTBOX_MIN_WIDTH; + s32 InputBoxTextWidth = TextBox.W - MinBoxWidth + 2; + bool ShouldIncreaseSize = ( + (s32)InputLen >= InputBoxTextWidth && + InputBoxTextWidth > MIN_TEXT_WIDTH_FOR_WRAPPING + ); + if (ShouldIncreaseSize) + { + TextBox.H++; + } +#undef MIN_TEXT_WIDTH_FOR_WRAPPING + + rect TextR = { + TextBox.X + 2, TextBox.Y + 1, + TextBox.W - 2*TEXTBOX_PADDING_X - 2*TEXTBOX_BORDER_WIDTH, + TextBox.H - 2*TEXTBOX_BORDER_WIDTH + }; + + if (global.height < TextBox.H || global.width < TextBox.W) + { + tb_hide_cursor(); + return; + } + + bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); + global.cursor_x = TextR.X; + global.cursor_y = TextR.Y; + DrawBox(TextBox, 0); + DrawTextBox(TextR, Input, InputLen); + + // Print vertical bar + s32 VerticalBarOffset = TIMESTAMP_LEN + AUTHOR_LEN + 2; + for (u32 Y = 0; Y < FreeHeight; Y++) + tb_print(VerticalBarOffset, Y, 0, 0, "│"); + + // show error popup if server disconnected + if (fds[FDS_UNI].fd == -1 || fds[FDS_BI].fd == -1) + { + popup(TB_RED, TB_BLACK, (u8*)"Server disconnected."); + } + + // Print messages in msgsArena, if there are too many to display, start printing from an offset. + // Looks like this: + // 03:24:29 [1234567890ab] hello homes how are + // you doing? + // 03:24:33 [TlasT] │ I am fine + // 03:24:33 [Fin] │ I am too + { + + // If there is not enough space to draw, do not draw + if (FreeHeight <= 0) return; + + // Used to go to the next message in MessagesArena by incrementing with the messages' size. + u8* MessageAddress = MessagesArena->addr; + Assert(MessageAddress != 0); + + // Skip messages if there is not enough space to display them all + u32 MessagesOffset = (MessagesNum > FreeHeight) ? MessagesNum - FreeHeight : 0; + for (u32 MessageIndex = 0; MessageIndex < MessagesOffset; MessageIndex++) + { + HeaderMessage* header = (HeaderMessage*)MessageAddress; + MessageAddress += sizeof(*header); + + switch (header->type) + { + case HEADER_TYPE_TEXT: + { + TextMessage* message = (TextMessage*)MessageAddress; + MessageAddress += TEXTMESSAGE_SIZE; + MessageAddress += message->len * sizeof(*message->text); + break; + } + case HEADER_TYPE_PRESENCE: + MessageAddress += sizeof(PresenceMessage); + break; + case HEADER_TYPE_HISTORY: + MessageAddress += sizeof(HistoryMessage); + break; + default: + // unhandled message type + Assert(0); + } + } + + u32 MessageY = 0; + + for (u32 i = MessagesOffset; + i < MessagesNum; + i++) + { + if (MessageY >= FreeHeight) break; + + HeaderMessage* header = (HeaderMessage*)MessageAddress; + MessageAddress += sizeof(*header); + + User* client = get_user_by_id(ClientsArena, header->id); + if (!client) + { + LoggingF("User not known, requesting from server\n"); + client = add_user_info(ClientsArena, fds[FDS_BI].fd, header->id); + } + Assert(client); + + switch (header->type) + { + case HEADER_TYPE_TEXT: + { + TextMessage* message = (TextMessage*)MessageAddress; + + + // Color own messages + u32 fg = 0; + if (user.ID == header->id) + { + fg = TB_CYAN; + } + else + { + fg = TB_MAGENTA; + } + + // prefix is of format "HH:MM:SS [] ", create it + u8 timestamp[TIMESTAMP_LEN]; + formatTimestamp(timestamp, message->timestamp); + + tb_printf(0, MessageY, TB_WHITE, 0, "%s", timestamp); + tb_printf(TIMESTAMP_LEN, MessageY, fg, 0, "[%s]", client->Author); + + // Only display when there is enough space + if (global.width > VerticalBarOffset + 2) + { + raw_result RawText = markdown_to_raw(ScratchArena, (wchar_t*)&message->text, message->len); + markdown_formatoptions MDFormat = preprocess_markdown(ScratchArena, + (wchar_t*)&message->text, + message->len); + + u32 timesWrapped = tb_print_wrapped_with_markdown(VerticalBarOffset + 2, MessageY, fg, 0, + RawText.Text, RawText.Len, + global.width, global.height, MDFormat); + + // Free the memory + ScratchArena->pos = 0; + + MessageY += timesWrapped; + } + else + { + // We still displayed the timestamp so we need to increment the Y. + MessageY++; + } + + u32 message_size = TEXTMESSAGE_SIZE + message->len * sizeof(*message->text); + MessageAddress += message_size; + } break; + case HEADER_TYPE_PRESENCE: + { + PresenceMessage* message = (PresenceMessage*)MessageAddress; + tb_printf(TIMESTAMP_LEN, MessageY, TB_MAGENTA, 0, "[%s]", client->Author); + + // Wrap Text in '*' + u8 *Text = presenceTypeString(message->type); + u32 Len = 0; + while(Text[Len]) Len++; + u32 FormattedText[Len+2]; + FormattedText[0] = '*'; + FormattedText[Len+1] = '*'; + for (u32 i = 1; i < Len + 1; i++) FormattedText[i] = Text[i-1]; + + tb_print_markdown(VerticalBarOffset + 2, MessageY, 0, 0, FormattedText, Len + 2); + + MessageY++; + MessageAddress += sizeof(*message); + } break; + case HEADER_TYPE_HISTORY: + { + HistoryMessage* message = (HistoryMessage*)MessageAddress; + MessageAddress += sizeof(*message); + // TODO: implement + } break; + default: + tb_printf(0, MessageY, 0, 0, "%s", headerTypeString(header->type)); + MessageY++; + break; + } + } + + } +} + +int +main(int argc, char** argv) +{ + if (argc < 2) + { + fprintf(stderr, "usage: chatty \n"); + return 1; + } + + u32 arg_len = strlen(argv[1]); + Assert(arg_len <= AUTHOR_LEN - 1); + memcpy(user.Author, argv[1], arg_len); + user.Author[arg_len] = '\0'; + + s32 err = 0; // error code for functions + + u32 MessagesNum = 0; // Number of messages in msgsArena + s32 nrecv = 0; // number of bytes received + + wchar_t Input[MAX_INPUT_LEN] = {0}; // input buffer + u32 InputIndex = 0; // number of characters in input + + Arena ScratchArena; + Arena MessagesArena; + Arena ClientsArena; + ArenaAlloc(&MessagesArena, Megabytes(64)); // Messages received & sent + ArenaAlloc(&ClientsArena, Megabytes(1)); // Arena for storing clients + ArenaAlloc(&ScratchArena, Megabytes(1)); // Arena for storing clients + + struct tb_event ev; // event fork keypress & resize + u8 quit = 0; // boolean to indicate if we want to quit the main loop + u8* quitmsg = 0; // this string will be printed before returning from main + + pthread_t thr_rec; // thread for reconnecting to server when disconnected + +#ifdef LOGGING + LogFD = open(LOGFILE, O_RDWR | O_CREAT | O_TRUNC, 0600); + Assert(LogFD != -1); +#else + logfd = 2; // stderr +#endif + + // poopoo C cannot infer type + struct pollfd fds[FDS_MAX] = { + {-1, POLLIN, 0}, // FDS_BI + {-1, POLLIN, 0}, // FDS_UNI + {-1, POLLIN, 0}, // FDS_TTY + {-1, POLLIN, 0}, // FDS_RESIZE + }; + + address = (struct sockaddr_in){ + AF_INET, + htons(PORT), + {0}, + {0}, + }; + +#ifdef IMPORT_ID + // File for storing the user's ID. + u32 idfile = open(ID_FILE, O_RDWR | O_CREAT, 0600); + s32 nread = read(idfile, &user.id, sizeof(user.id)); + Assert(nread != -1); +#endif + /* Authentication */ + { + s32 unifd, bifd; + bifd = get_connection(&address); + if (bifd == -1) + { + LoggingF("errno: %d\n", errno); + return 1; + } + unifd = get_connection(&address); + if (unifd == -1) + { + LoggingF("errno: %d\n", errno); + return 1; + } + LoggingF("(%d,%d)\n", bifd, unifd); + if (!authenticate(&user, bifd) || + !authenticate(&user, unifd)) + { + LoggingF("errno: %d\n", errno); + return 1; + } + else + { + LoggingF("Authenticated (%d,%d)\n", bifd, unifd); + } + fds[FDS_BI].fd = bifd; + fds[FDS_UNI].fd = unifd; + } + +#ifdef IMPORT_ID + // Save id + write(idfile, &user.id, sizeof(user.id)); +#endif + + LoggingF("Got ID: %lu\n", user.ID); + + // for wide character printing + Assert(setlocale(LC_ALL, "")); + + // init + tb_init(); + tb_get_fds(&fds[FDS_TTY].fd, &fds[FDS_RESIZE].fd); + + DisplayChat(&ScratchArena, + &MessagesArena, MessagesNum, + &ClientsArena, fds, + Input, InputIndex); + tb_present(); + + // main loop + while (!quit) + { + err = poll(fds, FDS_MAX, TIMEOUT_POLL); + // ignore resize events and use them to redraw the screen + Assert(err != -1 || errno == EINTR); + + tb_clear(); + + if (fds[FDS_UNI].revents & POLLIN) + { + // got data from server + HeaderMessage header; + nrecv = recv(fds[FDS_UNI].fd, &header, sizeof(header), 0); + Assert(nrecv != -1); + + // Server disconnects + if (nrecv == 0) + { + // close diconnected server's socket + err = close(fds[FDS_UNI].fd); + Assert(err == 0); + fds[FDS_UNI].fd = -1; // ignore + // start trying to reconnect in a thread + err = pthread_create(&thr_rec, 0, &thread_reconnect, (void*)fds); + Assert(err == 0); + } + else + { + if (header.version != PROTOCOL_VERSION) + { + LoggingF("Header received does not match version\n"); + continue; + } + + void* addr = ArenaPush(&MessagesArena, sizeof(header)); + memcpy(addr, &header, sizeof(header)); + + // Messages handled from server + switch (header.type) + { + case HEADER_TYPE_TEXT: + recvTextMessage(&MessagesArena, fds[FDS_UNI].fd); + MessagesNum++; + break; + case HEADER_TYPE_PRESENCE:; + PresenceMessage* message = ArenaPush(&MessagesArena, sizeof(*message)); + nrecv = recv(fds[FDS_UNI].fd, message, sizeof(*message), 0); + Assert(nrecv != -1); + Assert(nrecv == sizeof(*message)); + MessagesNum++; + break; + default: + LoggingF("Got unhandled message: %s\n", headerTypeString(header.type)); + break; + } + } + } + + if (fds[FDS_TTY].revents & POLLIN) + { + // got a key event + tb_poll_event(&ev); + + switch (ev.key) + { + case TB_KEY_CTRL_W: + // delete consecutive whitespace + while (InputIndex) + { + if (Input[InputIndex - 1] == L' ') + { + Input[InputIndex - 1] = 0; + InputIndex--; + continue; + } + break; + } + // delete until whitespace + while (InputIndex) + { + if (Input[InputIndex - 1] == L' ') + break; + // erase + Input[InputIndex - 1] = 0; + InputIndex--; + } + break; + case TB_KEY_CTRL_Z: + { + pid_t pid = getpid(); + tb_shutdown(); + kill(pid, SIGSTOP); + tb_init(); + } break; + case TB_KEY_CTRL_Y: // Paste clipboard contents to input + { + u32 OutputBufferLen = MAX_INPUT_LEN - InputIndex; + if (OutputBufferLen <= 0) break; + + u8 OutputBuffer[OutputBufferLen]; + + char *PathName = "xclip"; + char *Argv[] = {PathName, "-o", "-sel", "c", 0}; + + command_output Output = run_command_get_output(PathName, Argv, OutputBuffer, OutputBufferLen - 1); + if (Output.Error) break; + + // Remove trailing whitespace + int BufferIndex = Output.NumRead - 1; + while (BufferIndex > 0 && + (OutputBuffer[BufferIndex] == '\n' || + OutputBuffer[BufferIndex] == '\t')) + { + OutputBuffer[BufferIndex] = 0; + BufferIndex--; + } + + // Append to output + for (s32 BufferIndex = 0; BufferIndex < Output.NumRead; BufferIndex++) + { + // convert u8 to u32 + u32 ch = OutputBuffer[BufferIndex]; + Input[InputIndex] = ch; + InputIndex++; + } + + } break; + case TB_KEY_CTRL_I: + { + for (u32 i = 0; + i < TAB_WIDTH && InputIndex < MAX_INPUT_LEN - 1; + i++) + { + Input[InputIndex] = L' '; + InputIndex++; + } + } break; + case TB_KEY_BACKSPACE2: + if (InputIndex) InputIndex--; + Input[InputIndex] = 0; + break; + case TB_KEY_CTRL_D: + case TB_KEY_CTRL_C: + quit = 1; + break; + case TB_KEY_CTRL_M: // send message + { + raw_result RawText = markdown_to_raw(0, Input, InputIndex); + + if (RawText.Len == 0) + // do not send empty message + break; + if (fds[FDS_UNI].fd == -1) + // do not send message to disconnected server + break; + + // null terminate + Input[InputIndex] = 0; + InputIndex++; + + // Save header + HeaderMessage* header = ArenaPush(&MessagesArena, sizeof(*header)); + header->version = PROTOCOL_VERSION; + header->type = HEADER_TYPE_TEXT; + header->id = user.ID; + + // Save message + TextMessage* sendmsg = ArenaPush(&MessagesArena, TEXTMESSAGE_SIZE); + sendmsg->timestamp = time(0); + sendmsg->len = InputIndex; + + u32 text_size = InputIndex * sizeof(*Input); + ArenaPush(&MessagesArena, text_size); + memcpy(&sendmsg->text, Input, text_size); + + sendAnyMessage(fds[FDS_UNI].fd, *header, sendmsg); + + MessagesNum++; + // also clear input + } // fallthrough + case TB_KEY_CTRL_U: // clear input + bzero(Input, InputIndex * sizeof(*Input)); + InputIndex = 0; + break; + default: + if (ev.ch == 0) + break; + + // TODO: show error + if (InputIndex == MAX_INPUT_LEN - 1) // last byte reserved for \0 + break; + + // append key to input buffer + Input[InputIndex] = ev.ch; + InputIndex++; + } + if (quit) + break; + } + + // These are used to redraw the screen from threads + if (fds[FDS_RESIZE].revents & POLLIN) + { + // ignore + tb_poll_event(&ev); + } + + DisplayChat(&ScratchArena, &MessagesArena, MessagesNum, &ClientsArena, fds, Input, InputIndex); + + tb_present(); + } + + tb_shutdown(); + + if (quitmsg != 0) + printf("%s\n", quitmsg); + + return 0; +} diff --git a/source/chatty.h b/source/chatty.h new file mode 100644 index 0000000..f7525fa --- /dev/null +++ b/source/chatty.h @@ -0,0 +1,78 @@ +#ifndef CHATTY_H +#define CHATTY_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// port for chatty +#define PORT 9983 +// max number of bytes that can be logged at once +#define LOGMESSAGE_MAX 2048 +#define LOG_FMT "%H:%M:%S " +#define LOG_LEN 10 +// Enable/Disable saving clients permanently to file +// #define IMPORT_ID + +#define Kilobytes(Value) ((Value) * 1024) +#define Megabytes(Value) (Kilobytes(Value) * 1024) +#define Gigabytes(Value) (Megabytes((u64)Value) * 1024) +#define Terabytes(Value) (Gigabytes((u64)Value) * 1024) +#define PAGESIZE 4096 +#define local_persist static +#define global_variable +#define internal static + +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; +typedef u32 b32; + +void Loggingf(char* format, ...); + +#endif // CHATTY_H + +#ifdef CHATTY_IMPL + +global_variable s32 LogFD; + +void +LoggingF(char* format, ...) +{ + char buf[LOGMESSAGE_MAX]; + va_list args; + va_start(args, format); + + vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + int n = 0; + while (*(buf + n) != 0) n++; + + u64 t = time(0); + u8 timestamp[LOG_LEN]; + struct tm* ltime = localtime((time_t*)&t); + strftime((char*)timestamp, LOG_LEN, LOG_FMT, ltime); + write(LogFD, timestamp, LOG_LEN - 1); + + write(LogFD, buf, n); +} + +#undef CHATTY_IMPL +#endif // CHATTY_IMPL diff --git a/source/protocol.h b/source/protocol.h new file mode 100644 index 0000000..f863f0a --- /dev/null +++ b/source/protocol.h @@ -0,0 +1,375 @@ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#include "arena.h" +#include "chatty.h" + +/// Protocol +// - every message has format Header + Message +// TODO: security +// +/// ID +// - So clients can be identified uniquely. +// - 8 bytes +// - number that increments for each new client +// +/// Strings +// - strings are sent with their null terminator +// +/// Authentication +// Each header contains the id of the sender, because ids start at 1 +// message with id 0 is considered unauthenticated. +// +// When the server receives a header with id 0, this can happen +// Scenario 1: IDMessage, client already has an ID +// 1. client-> Send own ID +// 2. server-> knows ID? +// y. server-> Success +// n. 1. server-> Error 'notfound' +// 2. client-> exit +// Scenario 2: IntroductionMessage, client requests a new ID +// 1. client-> Introduces +// 2. server-> Sends & Saves ID +// 3. Save ID +// +// BIFD & UNIFD +// Each client has 2 connections that must be authenticated one for 2-way +// communication and one for 1-way communication. Respectively BIFD and +// UNIFD. BIFD must be authenticated first and is meant for requests such +// as getting an IntroductionMessage for a sent IDMessage. +// UNIFD is for messages that are like notifications. For example +// PresenceMessage that tells us when another user connected. +// These two connections separate these message types so we do not have to +// worry about receiving a PresenceMessage when waiting for an a response. +// +/// Naming conventions +// Messages end with the Message suffix (eg. TextMessag, HistoryMessage) +// +// A function that is coupled to a type works like +// eg. (printTextMessage, formatTimestamp) + +#define PROTOCOL_VERSION 0 +// Size of author string including null terminator +#define AUTHOR_LEN 13 +// Size of formatted timestamp string including null terminator +#define TIMESTAMP_LEN 9 +#define TIMESTAMP_FORMAT "%H:%M:%S" + +typedef u64 ID; + +// - 2 bytes for version +// - 1 byte for message type +// - 16 bytes for checksum +typedef struct { + u16 version; + u8 type; + ID id; +} HeaderMessage; + +typedef enum { + HEADER_TYPE_TEXT = 0, + HEADER_TYPE_HISTORY, + HEADER_TYPE_PRESENCE, + HEADER_TYPE_ID, + HEADER_TYPE_INTRODUCTION, + HEADER_TYPE_ERROR +} HeaderType; +// shorthand for creating a header with a value from the enum +#define HEADER_INIT(t) {.version = PROTOCOL_VERSION, .type = t, .id = 0} +// from Tsoding video on minicel (https://youtu.be/HCAgvKQDJng?t=4546) +// sv(https://github.com/tsoding/sv) +#define HEADER_FMT "header: v%d %s(%d) [%d]" +#define HEADER_ARG(header) header.version, headerTypeString(header.type), header.type, header.id + +// For sending texts to other clients +// - 13 bytes for the author +// - 8 bytes for the timestamp +// - 2 bytes for the text length +// - x*4 bytes for the text +typedef struct { + u64 timestamp; // timestamp of when the message was sent + u16 len; + wchar_t* text; // placeholder for indexing + // wchar_t* is used, because this renders the text in the debugger +} TextMessage; +// Size of TextMessage without text pointer +#define TEXTMESSAGE_SIZE (sizeof(TextMessage) - sizeof(u32*)) + +// Requesting messages sent after a timestamp. +// - 8 bytes for the timestamp +typedef struct { + u64 timestamp; +} HistoryMessage; + +// Introduce the client to the server by sending the client's information. +// See "First connection". +// - 13 bytes for author +typedef struct { + u8 author[AUTHOR_LEN]; +} IntroductionMessage; +#define INTRODUCTION_FMT "introduction: %s" +#define INTRODUCTION_ARG(message) message.author + +// Notifying the sender's state, such as "connected", "disconnected", "AFK", ... +// - 1 byte for type +typedef struct { + u8 type; +} PresenceMessage; +typedef enum { + PRESENCE_TYPE_CONNECTED = 0, + PRESENCE_TYPE_DISCONNECTED, + PRESENCE_TYPE_AFK +} PresenceType; + +// Send an error message +// - 1 byte for type +typedef struct { + u8 type; +} ErrorMessage; +typedef enum { + ERROR_TYPE_BADMESSAGE = 0, + ERROR_TYPE_NOTFOUND, + ERROR_TYPE_SUCCESS, + ERROR_TYPE_ALREADYCONNECTED, + ERROR_TYPE_TOOMANYCONNECTIONS +} ErrorType; +#define ERROR_INIT(t) {.type = t} + +typedef struct { + ID id; +} IDMessage; + +typedef struct { + s32 nrecv; + TextMessage* message; +} recvTextMessageResult; + +// Returns string for type byte in HeaderMessage +u8* +headerTypeString(HeaderType type) +{ + switch (type) + { + case HEADER_TYPE_TEXT: return (u8*)"TextMessage"; + case HEADER_TYPE_HISTORY: return (u8*)"HistoryMessage"; + case HEADER_TYPE_PRESENCE: return (u8*)"PresenceMessage"; + case HEADER_TYPE_ID: return (u8*)"IDMessage"; + case HEADER_TYPE_INTRODUCTION: return (u8*)"IntroductionMessage"; + case HEADER_TYPE_ERROR: return (u8*)"ErrorMessage"; + default: return (u8*)"Unknown"; + } +} + +u8* +presenceTypeString(PresenceType type) +{ + switch (type) + { + case PRESENCE_TYPE_CONNECTED: return (u8*)"connected"; + case PRESENCE_TYPE_DISCONNECTED: return (u8*)"disconnected"; + case PRESENCE_TYPE_AFK: return (u8*)"afk"; + default: return (u8*)"Unknown"; + } +} + +u8* +errorTypeString(ErrorType type) +{ + switch (type) + { + case ERROR_TYPE_BADMESSAGE: return (u8*)"bad message"; + case ERROR_TYPE_NOTFOUND: return (u8*)"not found"; + case ERROR_TYPE_SUCCESS: return (u8*)"success"; + case ERROR_TYPE_ALREADYCONNECTED: return (u8*)"already connected"; + case ERROR_TYPE_TOOMANYCONNECTIONS: return (u8*)"too many connections"; + default: return (u8*)"Unknown"; + } +} + +// Formats time t into tmsp string +void +formatTimestamp(u8 timestamp_str[TIMESTAMP_LEN], u64 timestamp) +{ + struct tm* ltime; + ltime = localtime((time_t*)×tamp); + strftime((char*)timestamp_str, TIMESTAMP_LEN, TIMESTAMP_FORMAT, ltime); +} + +// Receive a message from fd and store it in the msgsArena, +// Returns pointer to the allocated memory +TextMessage* +recvTextMessage(Arena* msgsArena, u32 fd) +{ + TextMessage* message = ArenaPush(msgsArena, TEXTMESSAGE_SIZE); + + // Receive everything but the text so we can know the text's size and act accordingly + s32 nrecv = recv(fd, message, TEXTMESSAGE_SIZE, 0); + assert(nrecv != -1); + assert(nrecv == TEXTMESSAGE_SIZE); + + // Allocate memory for text and receive in that memory + u32 text_size = message->len * sizeof(*message->text); + ArenaPush(msgsArena, text_size); + + nrecv = recv(fd, (u8*)&message->text, text_size, 0); + assert(nrecv != -1); + assert(nrecv == (s32)(message->len * sizeof(*message->text))); + + return message; +} + +typedef struct { + HeaderMessage* header; + void* message; +} Message; + +u32 +getMessageSize(HeaderType type) +{ + u32 size = 0; + switch (type) + { + case HEADER_TYPE_ERROR: size = sizeof(ErrorMessage); break; + case HEADER_TYPE_HISTORY: size = sizeof(HistoryMessage); break; + case HEADER_TYPE_INTRODUCTION: size = sizeof(IntroductionMessage); break; + case HEADER_TYPE_PRESENCE: size = sizeof(PresenceMessage); break; + case HEADER_TYPE_ID: size = sizeof(IDMessage); break; + default: assert(0); + } + return size; +} + +s32 +recvAnyMessageType(s32 fd, HeaderMessage* header, void *anyMessage, HeaderType type) +{ + s32 nrecv = recv(fd, header, sizeof(*header), 0); + if (nrecv == -1 || nrecv == 0) + return nrecv; + assert(nrecv == sizeof(*header)); + + s32 size = 0; + switch (type) + { + case HEADER_TYPE_ERROR: + case HEADER_TYPE_HISTORY: + case HEADER_TYPE_INTRODUCTION: + case HEADER_TYPE_PRESENCE: + case HEADER_TYPE_ID: + size = getMessageSize(header->type); + break; + case HEADER_TYPE_TEXT: + { + TextMessage* message = anyMessage; + size = TEXTMESSAGE_SIZE + message->len * sizeof(*message->text); + } break; + default: assert(0); break; + } + assert(header->type == type); + + nrecv = recv(fd, anyMessage, size, 0); + assert(nrecv != -1); + assert(nrecv == size); + + return size; +} + +// Get any message into arena +Message +recvAnyMessage(Arena* arena, s32 fd) +{ + HeaderMessage* header = ArenaPush(arena, sizeof(*header)); + s32 nrecv = recv(fd, header, sizeof(*header), 0); + assert(nrecv != -1); + assert(nrecv == sizeof(*header)); + + s32 size = 0; + switch (header->type) + { + case HEADER_TYPE_TEXT: + { + Message result; + result.header = header; + result.message = recvTextMessage(arena, fd); + return result; + } break; + default: + { + size = getMessageSize(header->type); + } break; + } + + void* message = ArenaPush(arena, size); + nrecv = recv(fd, message, size, 0); + assert(nrecv != -1); + assert(nrecv == size); + + Message result; + result.header = header; + result.message = message; + + return result; +} + +Message +waitForMessageType(Arena* arena, Arena* queueArena, u32 fd, HeaderType type) +{ + Message message; + while (1) + { + message = recvAnyMessage(arena, fd); + if (message.header->type == type) + break; + ArenaPush(queueArena, getMessageSize(message.header->type)); + } + return message; +} + +// Generic sending function for sending any type of message to fd +// Returns number of bytes sent in message or -1 if there was an error. +s32 +sendAnyMessage(u32 fd, HeaderMessage header, void* anyMessage) +{ + s32 nsend_total; + s32 nsend = send(fd, &header, sizeof(header), 0); + if (nsend == -1) return nsend; + LoggingF("sendAnyMessage (%d)|sending "HEADER_FMT"\n", fd, HEADER_ARG(header)); + assert(nsend == sizeof(header)); + nsend_total = nsend; + + s32 size = 0; + switch (header.type) + { + case HEADER_TYPE_ERROR: + case HEADER_TYPE_HISTORY: + case HEADER_TYPE_INTRODUCTION: + case HEADER_TYPE_PRESENCE: + case HEADER_TYPE_ID: + size = getMessageSize(header.type); + break; + case HEADER_TYPE_TEXT: + { + nsend = send(fd, anyMessage, TEXTMESSAGE_SIZE, 0); + assert(nsend != -1); + assert(nsend == TEXTMESSAGE_SIZE); + nsend_total += nsend; + // set size to remaning text size that should be sent + TextMessage* message = (TextMessage*)anyMessage; + size = message->len * sizeof(*message->text); + nsend = 0; + + anyMessage = &message->text; + } break; + default: + LoggingF("sendAnyMessage (%d)|Cannot send %s\n", fd, headerTypeString(header.type)); + return -1; + } + + nsend = send(fd, anyMessage, size, 0); + if (nsend == -1) return nsend; + assert(nsend == size); + nsend_total += nsend; + + return nsend_total; +} + +#endif diff --git a/source/server.c b/source/server.c new file mode 100644 index 0000000..a6613d6 --- /dev/null +++ b/source/server.c @@ -0,0 +1,581 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Assertion macro */ +#ifndef Assert +#ifdef DEBUG +#define Assert(expr) if (!(expr)) { \ + raise(SIGTRAP); \ +} +#else +#define Assert(expr) if (!(expr)) { \ + raise(SIGTRAP); \ +} +#endif // DEBUG +#endif // Assert + +/* Dependencies */ +#define CHATTY_IMPL +#include "chatty.h" +#undef CHATTY_IMPL + +#define ARENA_IMPL +#include "arena.h" +#undef ARENA_IMPL +#include "protocol.h" + +/* Configuration options */ +// timeout on polling +#define TIMEOUT 60 * 1000 +// max pending connections +#define MAX_CONNECTIONS 1600 +// Get number of connections from arena position +// NOTE: this is somewhat wrong, because of when disconnections happen +#define FDS_SIZE (fdsArena.pos / sizeof(struct pollfd)) +#define CLIENTS_SIZE (clientsArena.pos / sizeof(Client)) + +// Where to save clients +#define CLIENTS_FILE "_clients" +// Where to write logs +#define LOGFILE "server.log" +// Log to LOGFILE instead of stderr +// #define LOGGING + +// enum for indexing the fds array +enum { FDS_STDIN = 0, + FDS_SERVER, + FDS_CLIENTS }; + +// Client information +typedef struct { + u8 author[AUTHOR_LEN]; // matches author property on other message types + ID id; + struct pollfd* bifd; // Index in fds array + struct pollfd* unifd; // Index in fds array +} Client; +#define CLIENT_FMT "[%s](%lu)" +#define CLIENT_ARG(client) client.author, client.id + +typedef enum { + BIFD = 0, + UNIFD, +} ClientFD; + +// TODO: remove global variable +// For handing out new ids to connections. +// Start at 1 because this makes 0 an invalid client id. +global_variable u32 nclients = 1; + +// Returns client matching id in clients nclients number of clients. +// Returns 0 if no client was found or if id was 0. +Client* +getClientByID(Client* clients, u32 nclients, ID id) +{ + if (!id) return 0; + + for (u32 i = 0; i < nclients; i++) + { + if (clients[i].id == id) + return clients + i; + } + return 0; +} + +// Returns client matching fd in clients nclients number of clients. +// Returns 0 if no clients was found or if fd was -1. +Client* +getClientByFD(Client* clients, u32 nclients, s32 fd) +{ + if (fd == -1) return 0; + + for (u32 i = 0; i < nclients; i++) + { + if ((clients[i].unifd && clients[i].unifd->fd == fd) || + (clients[i].bifd && clients[i].bifd->fd == fd)) + return clients + i; + } + return 0; +} + +// Print TextMessage prettily +void +printTextMessage(TextMessage* message, Client* client, u8 wide) +{ + u8 timestamp[TIMESTAMP_LEN] = {0}; + formatTimestamp(timestamp, message->timestamp); + + if (wide) + { + setlocale(LC_ALL, ""); + wprintf(L"TextMessage: %s [%s] %ls\n", timestamp, client->author, (wchar_t*)&message->text); + } else { + u8 str[message->len]; + wcstombs((char*)str, (wchar_t*)&message->text, message->len * sizeof(*message->text)); + LoggingF("TextMessage: %s [%s] (%d)%s\n", timestamp, client->author, message->len, str); + } +} + +// Send header and anyMessage to each connection in fds that is nfds number of connections except +// for connfd. +// Does not send if pollfd is not set or pollfd->fd is -1. +// Type will filter out only connections matching the type. +void +sendToOthers(Client* clients, u32 nclients, Client* client, ClientFD type, HeaderMessage* header, void* anyMessage) +{ + s32 nsend, fd; + for (u32 i = 0; i < nclients - 1; i ++) + { + if (clients + i == client) continue; + + if (type == UNIFD) + { + if (clients[i].unifd && clients[i].unifd->fd != -1) + fd = clients[i].unifd->fd; + else + continue; + } + else if (type == BIFD) + { + if (clients[i].bifd && clients[i].bifd->fd != -1) + fd = clients[i].bifd->fd; + else + continue; + } + nsend = sendAnyMessage(fd, *header, anyMessage); + + assert(nsend != -1); + LoggingF("sendToOthers "CLIENT_FMT"|%d<-%s %d bytes\n", CLIENT_ARG((clients[i])), fd, headerTypeString(header->type), nsend); + } +} + +// Send header and anyMessage to each connection in fds that is nfds number of connections. +// Does not send if pollfd is not set or pollfd->fd is -1. +// Type will filter out only connections matching the type. +void +sendToAll(Client* clients, u32 nclients, ClientFD type, HeaderMessage* header, void* anyMessage) +{ + s32 nsend; + for (u32 i = 0; i < nclients - 1; i++) + { + if (type == UNIFD) + { + if (clients[i].unifd && clients[i].unifd->fd != -1) + nsend = sendAnyMessage(clients[i].unifd->fd, *header, anyMessage); + else + continue; + } + else if (type == BIFD) + { + if (clients[i].bifd && clients[i].bifd->fd != -1) + nsend = sendAnyMessage(clients[i].bifd->fd, *header, anyMessage); + else + continue; + } + else + assert(0); + assert(nsend != -1); + LoggingF("sendToAll|[%s]->"CLIENT_FMT" %d bytes\n", headerTypeString(header->type), + CLIENT_ARG(clients[i]), + nsend); + } +} + +// Disconnect a client by closing the matching file descriptors +void +disconnect(Client* client) +{ + LoggingF("Disconnecting "CLIENT_FMT"\n", CLIENT_ARG((*client))); + if (client->unifd && client->unifd->fd != -1) + { + close(client->unifd->fd); + client->unifd->fd = -1; + client->unifd = 0; + } + if (client->bifd && client->bifd->fd != -1) + { + close(client->bifd->fd); + client->bifd->fd = -1; + client->bifd = 0; + } +} + +// Disconnects fds+conn from fds with nfds connections, then send a PresenceMessage to other +// clients about disconnection. +void +disconnectAndNotify(Client* clients, u32 nclients, Client* client) +{ + disconnect(client); + + local_persist HeaderMessage header = HEADER_INIT(HEADER_TYPE_PRESENCE); + header.id = client->id; + PresenceMessage message = {.type = PRESENCE_TYPE_DISCONNECTED}; + sendToAll(clients, nclients, UNIFD, &header, &message); +} + +// Receive authentication from pollfd->fd and create client out of it. Look in +// clientsArena if it already exists. Otherwise push a new onto the arena and write its information +// to clients_file. +// See "Authentication" in chatty.h +// Assumes that the client will send a IDMessage or IntroductionMessage +// Returns authenticated client +Client* +authenticate(Arena* clientsArena, s32 clients_file, struct pollfd* pollfd, HeaderMessage header) +{ + s32 nrecv = 0; + Client* client = 0; + + LoggingF("authenticate (%d)|" HEADER_FMT "\n", pollfd->fd, HEADER_ARG(header)); + + /* Scenario 1: Search for existing client */ + if (header.type == HEADER_TYPE_ID) + { + IDMessage message; + s32 nrecv = recv(pollfd->fd, &message, sizeof(message), 0); + assert(nrecv == sizeof(message)); + + client = getClientByID((Client*)clientsArena->addr, nclients, message.id); + if (!client) + { + LoggingF("authenticate (%d)|notfound\n", pollfd->fd); + header.type = HEADER_TYPE_ERROR; + ErrorMessage error_message = ERROR_INIT(ERROR_TYPE_NOTFOUND); + sendAnyMessage(pollfd->fd, header, &error_message); + return 0; + } + else + { + LoggingF("authenticate (%d)|found [%s](%lu)\n", pollfd->fd, client->author, client->id); + header.type = HEADER_TYPE_ERROR; + ErrorMessage error_message = ERROR_INIT(ERROR_TYPE_SUCCESS); + sendAnyMessage(pollfd->fd, header, &error_message); + } + + if (!client->bifd) + client->bifd = pollfd; + else if (!client->unifd) + client->unifd = pollfd; + else + assert(0); + + + return client; + } + /* Scenario 2: Create a new client */ + else if (header.type == HEADER_TYPE_INTRODUCTION) + { + IntroductionMessage message; + nrecv = recv(pollfd->fd, &message, sizeof(message), 0); + if (nrecv != sizeof(message)) + { + LoggingF("authenticate (%d)|err: %d/%lu bytes\n", pollfd->fd, nrecv, sizeof(message)); + return 0; + } + + // Copy metadata from IntroductionMessage + client = ArenaPush(clientsArena, sizeof(*client)); + memcpy(client->author, message.author, AUTHOR_LEN); + client->id = nclients; + + if (!client->bifd) + client->bifd = pollfd; + else if (!client->unifd) + client->unifd = pollfd; + else + assert(0); + + nclients++; + +#ifdef IMPORT_ID + write(clients_file, client, sizeof(*client)); +#endif + LoggingF("authenticate (%d)|Added [%s](%lu)\n", pollfd->fd, client->author, client->id); + + // Send ID to new client + HeaderMessage header = HEADER_INIT(HEADER_TYPE_ID); + IDMessage id_message; + id_message.id = client->id; + + s32 nsend = sendAnyMessage(pollfd->fd, header, &id_message); + assert(nsend != -1); + + return client; + } + + LoggingF("authenticate (%d)|Wrong header expected %s or %s\n", pollfd->fd, + headerTypeString(HEADER_TYPE_INTRODUCTION), + headerTypeString(HEADER_TYPE_ID)); + return 0; +} + +int +main(int argc, char** argv) +{ + signal(SIGPIPE, SIG_IGN); + + LogFD = 2; + // optional logging + if (argc > 1) + { + if (*argv[1] == '-') + if (argv[1][1] == 'l') + { + LogFD = open(LOGFILE, O_RDWR | O_CREAT | O_TRUNC, 0600); + assert(LogFD != -1); + } + } + + s32 serverfd; + // Start listening on the socket + { + s32 err; + u32 on = 1; + serverfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + assert(serverfd > 2); + + err = setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, (u8*)&on, sizeof(on)); + assert(!err); + + const struct sockaddr_in address = { + AF_INET, + htons(PORT), + {0}, + {0}, + }; + + err = bind(serverfd, (const struct sockaddr*)&address, sizeof(address)); + assert(!err); + + err = listen(serverfd, MAX_CONNECTIONS); + assert(!err); + LoggingF("Listening on :%d\n", PORT); + } + + Arena clientsArena; + Arena fdsArena; + Arena msgsArena; + ArenaAlloc(&clientsArena, MAX_CONNECTIONS * sizeof(Client)); + ArenaAlloc(&fdsArena, MAX_CONNECTIONS * 2 * sizeof(struct pollfd)); + ArenaAlloc(&msgsArena, Megabytes(128)); // storing received messages + struct pollfd* fds = fdsArena.addr; + Client* clients = clientsArena.addr; + + // Initializing fds + struct pollfd* fdsAddr; + struct pollfd newpollfd = {-1, POLLIN, 0}; // for copying with events already set + // initialize fds structure + newpollfd.fd = 0; + fdsAddr = ArenaPush(&fdsArena, sizeof(*fds)); + memcpy(fdsAddr, &newpollfd, sizeof(*fds)); + // add serverfd + newpollfd.fd = serverfd; + fdsAddr = ArenaPush(&fdsArena, sizeof(*fds)); + memcpy(fdsAddr, &newpollfd, sizeof(*fds)); + newpollfd.fd = -1; + + s32 clients_file; +#ifdef IMPORT_ID + clients_file = open(CLIENTS_FILE, O_RDWR | O_CREAT | O_APPEND, 0600); + assert(clients_file != -1); + struct stat statbuf; + assert(fstat(clients_file, &statbuf) != -1); + + read(clients_file, clients, statbuf.st_size); + if (statbuf.st_size > 0) + { + ArenaPush(&clientsArena, statbuf.st_size); + LoggingF("Imported %lu client(s)\n", statbuf.st_size / sizeof(*clients)); + nclients += statbuf.st_size / sizeof(*clients); + + // Reset pointers on imported clients + for (u32 i = 0; i < nclients - 1; i++) + { + clients[i].unifd = 0; + clients[i].bifd = 0; + } + } + for (u32 i = 0; i < nclients - 1; i++) + LoggingF("Imported: " CLIENT_FMT "\n", CLIENT_ARG(clients[i])); +#else + clients_file = 0; +#endif + + // Initialize the rest of the fds array + for (u32 i = FDS_CLIENTS; i < MAX_CONNECTIONS; i++) + fds[i] = newpollfd; + + while (1) + { + s32 err = poll(fds, FDS_SIZE, TIMEOUT); + assert(err != -1); + + if (fds[FDS_STDIN].revents & POLLIN) + { + u8 c; // exit on ctrl-d + if (!read(fds[FDS_STDIN].fd, &c, 1)) + break; + } + else if (fds[FDS_SERVER].revents & POLLIN) + { + // TODO: what if we are not aligned by 2 anymore? + s32 clientfd = accept(serverfd, 0, 0); + + if (clientfd == -1) + { + LoggingF("Error while accepting connection (%d)\n", clientfd); + continue; + } + else + LoggingF("New connection(%d)\n", clientfd); + + // TODO: find empty space in arena (fragmentation) + if (nclients + 1 == MAX_CONNECTIONS) + { + local_persist HeaderMessage header = HEADER_INIT(HEADER_TYPE_ERROR); + local_persist ErrorMessage message = ERROR_INIT(ERROR_TYPE_TOOMANYCONNECTIONS); + sendAnyMessage(clientfd, header, &message); + if (clientfd != -1) + close(clientfd); + LoggingF("Max clients reached. Rejected connection\n"); + } + else + { + // no more space, allocate + struct pollfd* pollfd = ArenaPush(&fdsArena, sizeof(*pollfd)); + pollfd->fd = clientfd; + LoggingF("Added pollfd(%d)\n", clientfd); + } + } + + for (u32 conn = FDS_CLIENTS; conn < FDS_SIZE; conn++) + { + if (!(fds[conn].revents & POLLIN)) continue; + if (fds[conn].fd == -1) continue; + LoggingF("Message(%d)\n", fds[conn].fd); + + // We received a message, try to parse the header + HeaderMessage header; + s32 nrecv = recv(fds[conn].fd, &header, sizeof(header), 0); + if(nrecv == -1) + { + LoggingF("Received error from fd: %d, errno: %d\n", fds[conn].fd, errno); + }; + + Client* client; + if (nrecv != sizeof(header)) + { + client = getClientByFD(clients, nclients, fds[conn].fd); + if (client) + { + LoggingF("Received %d/%lu bytes "CLIENT_FMT"\n", nrecv, sizeof(header), CLIENT_ARG((*client))); + disconnectAndNotify(clients, nclients, client); + } + else + { + LoggingF("Got error/disconnect from unauthenticated client\n"); + close(fds[conn].fd); + fds[conn].fd = -1; + } + continue; + } + LoggingF("Received(%d): " HEADER_FMT "\n", fds[conn].fd, HEADER_ARG(header)); + + // Authentication + if (!header.id) + { + LoggingF("No client for connection(%d)\n", fds[conn].fd); + + client = authenticate(&clientsArena, clients_file, fds + conn, header); + + if (!client) + { + LoggingF("Could not initialize client (%d)\n", fds[conn].fd); + close(fds[conn].fd); + fds[conn].fd = -1; + } + /* This is the first time a message is sent, because unifd is not yet set. */ + else if (!client->unifd) + { + LoggingF("Send connected message\n"); + local_persist HeaderMessage header = HEADER_INIT(HEADER_TYPE_PRESENCE); + header.id = client->id; + PresenceMessage message = {.type = PRESENCE_TYPE_CONNECTED}; + sendToOthers(clients, nclients, client, UNIFD, &header, &message); + } + continue; + } + + client = getClientByID(clients, nclients, header.id); + if (!client) + { + LoggingF("No client for id %d\n", fds[conn].fd); + + header.type = HEADER_TYPE_ERROR; + ErrorMessage message = ERROR_INIT(ERROR_TYPE_NOTFOUND); + + sendAnyMessage(fds[conn].fd, header, &message); + + // Reject connection + fds[conn].fd = -1; + close(fds[conn].fd); + continue; + } + + switch (header.type) { + /* Send text message to all other clients */ + case HEADER_TYPE_TEXT: + { + TextMessage* text_message = recvTextMessage(&msgsArena, fds[conn].fd); + LoggingF("Received(%d): ", fds[conn].fd); + printTextMessage(text_message, client, 0); + + sendToOthers(clients, nclients, client, UNIFD, &header, text_message); + } break; + /* Send back client information */ + case HEADER_TYPE_ID: + { + IDMessage id_message; + s32 nrecv = recv(fds[conn].fd, &id_message, sizeof(id_message), 0); + assert(nrecv == sizeof(id_message)); + + client = getClientByID(clients, nclients, id_message.id); + if (!client) + { + header.type = HEADER_TYPE_ERROR; + ErrorMessage message = ERROR_INIT(ERROR_TYPE_NOTFOUND); + s32 nsend = sendAnyMessage(fds[conn].fd, header, &message); + assert(nsend != -1); + break; + } + + HeaderMessage header = HEADER_INIT(HEADER_TYPE_INTRODUCTION); + IntroductionMessage introduction_message; + header.id = client->id; + memcpy(introduction_message.author, client->author, AUTHOR_LEN); + + nrecv = sendAnyMessage(fds[conn].fd, header, &introduction_message); + assert(nrecv != -1); + } break; + default: + LoggingF("Unhandled '%s' from "CLIENT_FMT"(%d)\n", headerTypeString(header.type), + CLIENT_ARG((*client)), + fds[conn].fd); + disconnectAndNotify(client, nclients, client); + continue; + } + } + } + +#ifdef IMPORT_ID + close(clients_file); +#endif + + return 0; +} diff --git a/source/termbox2.h b/source/termbox2.h new file mode 100644 index 0000000..30f9ad3 --- /dev/null +++ b/source/termbox2.h @@ -0,0 +1,3518 @@ +/* +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; +} + +#undef TB_IMPL +#endif // TB_IMPL diff --git a/source/ui.h b/source/ui.h new file mode 100644 index 0000000..83a1ab4 --- /dev/null +++ b/source/ui.h @@ -0,0 +1,840 @@ +#ifndef UI_H +#define UI_H + +/* Macro's */ + +#include +#include +#include "termbox2.h" +#include "arena.h" +#include "chatty.h" + +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; +typedef u32 b32; + +/* Types */ + +// Format option at a position in raw text, used when iterating to know when to toggle a color +// option. +typedef struct { + u32 Position; + u32 Color; +} format_option; + +// Array of format options and length of said array +typedef struct { + format_option* Options; + u32 Len; +} markdown_formatoptions; + +typedef struct { + u32* Text; + u32 Len; +} raw_result; + +// Rectangle +typedef struct { + s32 X, Y, W, H; +} rect; + +// Characters to use for drawing a box +// See DrawBox() for an example +typedef struct { + wchar_t ur, ru, rd, dr, lr, ud; +} box_characters; + +/* Functions */ + +bool IsInRect(rect Rect, s32 X, s32 Y); +bool is_whitespace(u32 ch); +bool is_markdown(u32 ch); +void tb_print_wrapped(u32 X, u32 Y, u32 XLimit, u32 YLimit, u32* Text, u32 Len); +void tb_print_markdown(u32 X, u32 Y, u32 fg, u32 bg, u32* Text, u32 Len); +u32 tb_print_wrapped_with_markdown(u32 XOffset, u32 YOffset, u32 fg, u32 bg, + u32* Text, u32 Len, + u32 XLimit, u32 YLimit, + markdown_formatoptions MDFormat); +raw_result markdown_to_raw(Arena* ScratchArena, wchar_t* Text, u32 Len); +markdown_formatoptions preprocess_markdown(Arena* ScratchArena, wchar_t* Text, u32 Len); + +/* Input Box UI */ + +// assumes TEXTBOX_MAX_INPUT to be set +// +// #define TEXTBOX_MAX_INPUT 128 + +#define TEXTBOX_PADDING_X 1 +#define TEXTBOX_BORDER_WIDTH 1 +#define TEXTBOX_MIN_WIDTH TEXTBOX_PADDING_X * 2 + TEXTBOX_BORDER_WIDTH * 2 + 1; +#define TEXTBOXFROMBOX(Box) \ + { \ + .X = Box.X + TEXTBOX_BORDER_WIDTH + TEXTBOX_PADDING_X, \ + .Y = Box.Y + TEXTBOX_BORDER_WIDTH, \ + .W = Box.W - TEXTBOX_BORDER_WIDTH * 2 - TEXTBOX_PADDING_X * 2, \ + .H = Box.H - TEXTBOX_BORDER_WIDTH * 2 \ + } + +void DrawBox(rect Rect, box_characters *Chars); +void DrawTextBox(rect TextR, wchar_t *Text, u32 TextLen); +void DrawTextBoxWrapped(rect TextR, wchar_t *Text, u32 TextLen); +void TextBoxScrollLeft(rect Text, u32 *TextOffset); +void TextBoxScrollRight(rect Text, u32 *TextOffset); +void TextBoxDelete(wchar_t* Text, u64 Pos); +void TextBoxInsert(wchar_t *Input, u32 InputPos, u32 InputLen, wchar_t ch); +u32 TextBoxKeypress(struct tb_event ev, + rect TextR, wchar_t *Text, u32 *TextLenPtr, u32 TextPos, u32 *TextOffsetPtr); + +// Draw box along boundaries in Rect with optional Chars. +void +DrawBox(rect Rect, box_characters *Chars) +{ + wchar_t ur, ru, rd, dr, lr, ud; + if (!Chars) + { + ur = L'╭'; + ru = L'╯'; + rd = L'╮'; + dr = L'╰'; + lr = L'─'; + ud = L'│'; + } + else + { + ur = Chars->ur; + ru = Chars->ru; + rd = Chars->rd; + dr = Chars->dr; + lr = Chars->lr; + ud = Chars->ud; + } + + Rect.H--; + Rect.W--; + + tb_printf(Rect.X, Rect.Y, 0, 0, "%lc", ur); + for (s32 X = 1; X < Rect.W; X++) + { + tb_printf(Rect.X + X, Rect.Y, 0, 0, "%lc", lr); + } + tb_printf(Rect.X + Rect.W, Rect.Y, 0, 0, "%lc", rd); + + // Draw vertical bars + for (s32 Y = 1; Y < Rect.H; Y++) + { + tb_printf(Rect.X, Rect.Y + Y, 0, 0, "%lc", ud); + tb_printf(Rect.X + Rect.W, Rect.Y + Y, 0, 0, "%lc", ud); + } + + tb_printf(Rect.X, Rect.Y + Rect.H, 0, 0, "%lc", dr); + for (s32 X = 1; X < Rect.W; X++) + { + tb_printf(Rect.X + X, Rect.Y + Rect.H, 0, 0, "%lc", lr); + } + tb_printf(Rect.X + Rect.W, Rect.Y + Rect.H, 0, 0, "%lc", ru); +} + +// SCROLLING +// ╭──────────╮ ╭──────────╮ Going Left on the first character scrolls up. +// │ █3 4 │ => │ 1 2█ │ Cursor on end of the top line. +// │ 5 6 │ │ 3 4 │ +// ╰──────────╯ ╰──────────╯ +// +// ╭──────────╮ ╭──────────╮ Going Right on the last character scrolls down. +// │ 1 3 │ => │ 2 4 │ Puts cursor on start of the bottom line. +// │ 2 4█ │ │ █ │ +// ╰──────────╯ ╰──────────╯ +// +// ╭──────────╮ ╭──────────╮ Going Down on bottom line scrolls down. +// │ 1 3 │ => │ 2 4 │ Cursor stays on bottom line. +// │ 2 █ 4 │ │ █ │ +// ╰──────────╯ ╰──────────╯ +// +// ╭──────────╮ ╭──────────╮ Going Up on top line scrolls up. +// │ 3 █ 4 │ => │ 1 █ 2 │ Cursor stays on top line. +// │ 5 6 │ │ 3 5 │ +// ╰──────────╯ ╰──────────╯ +// +// In code this translates to changing global.cursor_{x,y} and TextOffset accordingly. + +// Scroll one character to the left +void +TextBoxScrollLeft(rect Text, u32 *TextOffset) +{ + // If text is on the first character of the box scroll up + if (global.cursor_x == Text.X && + global.cursor_y == Text.Y) + { + global.cursor_x = Text.X + Text.W - 1; + global.cursor_y = Text.Y; + + *TextOffset -= Text.W; + } + else + { + if (global.cursor_x == Text.X) + { + // Got to previous line + global.cursor_x = Text.X + Text.W - 1; + global.cursor_y--; + } + else + { + global.cursor_x--; + } + } +} + +// Scroll one character to the right +void +TextBoxScrollRight(rect Text, u32 *TextOffset) +{ + // If cursor is on the last character scroll forwards + if (global.cursor_x == Text.X + Text.W - 1 && + global.cursor_y == Text.Y + Text.H - 1) + { + global.cursor_x = Text.X; + global.cursor_y = Text.Y + Text.H - 1; + + *TextOffset += Text.W; + } + else + { + global.cursor_x++; + if (global.cursor_x == Text.X + Text.W) + { + global.cursor_x = Text.X; + global.cursor_y++; + } + } +} + + +// Delete a character in Text at Pos +void +TextBoxDelete(wchar_t* Text, u64 Pos) +{ + memmove(Text + Pos, + Text + Pos + 1, + (TEXTBOX_MAX_INPUT - Pos - 1) * sizeof(*Text)); +} + +// Insert a ev.ch in Input at InputPos +void +TextBoxInsert(wchar_t *Input, u32 InputPos, u32 InputLen, wchar_t ch) +{ + if (InputPos < InputLen) + { + memmove(Input + InputPos, + Input + InputPos - 1, + (InputLen - InputPos + 1) * sizeof(*Input)); + } + Input[InputPos] = ch; +} + +// Handle the key event ev changing Text, TextLenPtr, and TextOffsetPtr accordingly. +// InputPos is the position in the Input relating to the cursor position. +// TextR is the bounding box for the text. +// +// Returns non-zero when a key event was handled. +// +// TODO: pass by value and return struct with updated values +u32 +TextBoxKeypress(struct tb_event ev, rect TextR, + wchar_t *Text, u32 *TextLenPtr, u32 TextPos, u32 *TextOffsetPtr) +{ + u32 Result = 1; + + u32 TextLen = *TextLenPtr; + u32 TextOffset = *TextOffsetPtr; + + switch (ev.key) + { + + // Delete character backwards + case TB_KEY_CTRL_8: + // case TB_KEY_BACKSPACE2: + { + if (TextPos == 0) break; + + TextBoxDelete(Text, TextPos - 1); + TextLen--; + + TextBoxScrollLeft(TextR, &TextOffset); + + } break; + + // Delete character forwards + case TB_KEY_CTRL_D: + { + if (TextPos == TextLen) break; + TextBoxDelete(Text, TextPos); + TextLen--; + // Delete(Text, Position) + } break; + + // Delete word backwards + case TB_KEY_CTRL_W: + { + u32 At = TextPos; + // Find character to stop on + while (At && is_whitespace(Text[At - 1])) At--; + while (At && !is_whitespace(Text[At - 1])) At--; + + s32 NDelete = TextPos - At; + memmove(Text + At, Text + TextPos, (TextLen - TextPos) * sizeof(Text[At])); + TextLen -= NDelete; +#ifdef DEBUG + Text[TextLen] = 0; +#endif + // NOTE: this could be calculated at once instead + while(NDelete--) TextBoxScrollLeft(TextR, &TextOffset); + + Assert(IsInRect(TextR, global.cursor_x, global.cursor_y)); + + } break; + + // Delete until start of Text + case TB_KEY_CTRL_U: + { + memmove(Text, Text + TextPos, (TextLen - TextPos) * sizeof(*Text)); + TextLen -= TextPos; +#ifdef DEBUG + Text[TextLen] = 0; +#endif + global.cursor_x = TextR.X; + global.cursor_y = TextR.Y; + TextOffset = 0; + } break; + + // Delete until end of Text + case TB_KEY_CTRL_K: + { + TextLen = TextPos; + Text[TextPos] = 0; + } break; + + // Move to start of line + case TB_KEY_CTRL_A: global.cursor_x = TextR.X; break; + + // Move to end of line + case TB_KEY_CTRL_E: + { + if (global.cursor_x == TextR.X + TextR.W - 1) break; + + if (TextPos + TextR.W > TextLen) + { + // Put the cursor on the last character + global.cursor_x = TextR.X + (TextLen - TextOffset) % TextR.W; + } + else + { + global.cursor_x = TextR.X + TextR.W - 1; + } + } break; + + // Move backwards + case TB_KEY_CTRL_B: + case TB_KEY_ARROW_LEFT: + { + // Move forward by word + if (ev.mod == TB_MOD_CTRL) + { + u32 At = TextPos; + while(At && is_whitespace(Text[At])) At--; + while(At && !is_whitespace(Text[At])) At--; + while(TextPos - At++) TextBoxScrollLeft(TextR, &TextOffset); + } + // Move forward by character + else + { + if (TextPos == 0) break; + TextBoxScrollLeft(TextR, &TextOffset); + } + } break; + + // Move forwards + case TB_KEY_CTRL_F: + case TB_KEY_ARROW_RIGHT: + { + // Move forward by word + if (ev.mod == TB_MOD_CTRL) + { + u32 At = TextPos; + while(At < TextLen && is_whitespace(Text[At])) At++; + while(At < TextLen && !is_whitespace(Text[At])) At++; + while(At-- - TextPos) TextBoxScrollRight(TextR, &TextOffset); + } + // Move forward by character + else + { + if (TextPos == TextLen) break; + TextBoxScrollRight(TextR, &TextOffset); + } + } break; + + // Move up + case TB_KEY_CTRL_P: + case TB_KEY_ARROW_UP: + { + if (global.cursor_y == TextR.Y) + { + if (TextOffset == 0) + { + global.cursor_x = TextR.X; + + break; + } + + TextOffset -= TextR.W; + global.cursor_y = TextR.Y; + } + else + { + global.cursor_y--; + } + } break; + + // Move down + case TB_KEY_CTRL_N: + case TB_KEY_ARROW_DOWN: + { + if (TextPos + TextR.W > TextLen) + { + // Put the cursor on the last character + global.cursor_x = TextR.X + (TextLen - TextOffset) % (TextR.W); + global.cursor_y = TextR.Y + (TextLen - TextOffset) / TextR.W; + + // If cursor ended 1 line under the bottom line this means that the text + // needs to be scrolled. + if (global.cursor_y == TextR.Y + TextR.H) + { + TextOffset += TextR.W; + global.cursor_y--; + } + + break; + } + + if (global.cursor_y == TextR.Y + TextR.H - 1) + { + TextOffset += TextR.W; + } + else + { + global.cursor_y++; + } + } break; + default: + { + Result = 0; + } + } + + *TextLenPtr = TextLen; + *TextOffsetPtr = TextOffset; + + return Result; +} + +// Draws characters from Text fitting in the TextR rectangle. +// InputLen is the amount of characters in Text. +// +// NOTE: TextR is always filled, when not enough characters in Input it will uses spaces instead. +// This makes it easy to update the textbox by recalling this function. +void +DrawTextBox(rect TextR, wchar_t *Text, u32 TextLen) +{ + // Draw the text right of the cursor + // NOTE: the cursor is assumed to be in the box + Assert(IsInRect(TextR, global.cursor_x, global.cursor_y)); + s32 AtX = TextR.X, AtY = TextR.Y; + u32 At = 0; + while (AtY < TextR.Y + TextR.H) + { + if (At < TextLen) + { + tb_printf(AtX++, AtY, 0, 0, "%lc", Text[At++]); + global.cursor_x = AtX; + } + else + { + tb_printf(AtX++, AtY, 0, 0, " "); + } + + if (AtX == TextR.X + TextR.W) + { + AtY++; + AtX = TextR.X; + global.cursor_y = AtY - 1; + } + } + +} + +// NOTE: To ensure that the text looks the same even when scrolling it you must provide the whole text, +// wrap the whole text and the only show the portion that can fit in the Text Rectangle. + +// When line will exceed width break the word on the next line. This is done by looking backwards +// for whitespace from TextR.W width. When a whitespace is found text is wrapped on the next line. +// TODO: this does not work yet. +void +DrawTextBoxWrapped(rect TextR, wchar_t *Text, u32 TextLen) +{ + if (TextLen <= TextR.W) + { + tb_printf(TextR.X, TextR.Y, 0, 0, "%ls", Text); + tb_present(); + global.cursor_x = TextR.X + TextLen; + global.cursor_y = TextR.Y; + return; + } + + u32 SearchIndex = TextR.W; + u32 PrevIndex = 0; + u32 Y = TextR.Y; + + while (SearchIndex < TextLen) + { + while (Text[SearchIndex] != ' ') + { + SearchIndex--; + if (SearchIndex == PrevIndex) + { + SearchIndex += TextR.W; + break; + } + } + + // Wrap + wchar_t BreakChar = Text[SearchIndex]; + Text[SearchIndex] = 0; + tb_printf(TextR.X, Y, 0, 0, "%ls", Text + PrevIndex); + tb_present(); + Text[SearchIndex] = BreakChar; + + if (Y + 1 == TextR.Y + TextR.H) + { + global.cursor_y = Y; + global.cursor_x = TextR.X + (SearchIndex - PrevIndex); + return; + } + Y++; + + if (BreakChar == L' ') + { + SearchIndex++; + } + + PrevIndex = SearchIndex; + SearchIndex += TextR.W; + } + + // This happens when SearchIndex exceeds TextLen but there is still some + // text left to print. We can assume that the text will fit because otherwise it would have + // been wrapped a second time and the loop would have returned. + tb_printf(TextR.X, Y, 0, 0, "%ls", Text + PrevIndex); + // NOTE: this sets the cursor position correctly + + global.cursor_y = Y; + global.cursor_x = TextR.X + TextLen - PrevIndex; +} + +#endif // UI_H + +#ifdef UI_IMPL +// Check if coordinate (X,Y) is in rect boundaries +bool +IsInRect(rect Rect, s32 X, s32 Y) +{ + if ((X >= Rect.X && X <= Rect.X + Rect.W) && + (Y >= Rect.Y && Y <= Rect.Y + Rect.H)) return true; + return false; +} + +// Return True if ch is whitespace +bool +is_whitespace(u32 ch) +{ + if (ch == L' ') + return true; + return false; +} + +// Return True if ch is a supported markdown markup character +// TODO: tilde +bool +is_markdown(u32 ch) +{ + if (ch == L'_' || + ch == L'*') + return true; + return false; +} + +// Print `Text`, `Len` characters long with markdown +// NOTE: This function has no wrapping support +void +tb_print_markdown(u32 X, u32 Y, u32 fg, u32 bg, u32* Text, u32 Len) +{ + for (u32 ch = 0; ch < Len; ch++) + { + if (Text[ch] == L'_') + { + if (ch < Len - 1 && Text[ch + 1] == L'_') + { + fg ^= TB_UNDERLINE; + ch++; + } + else + { + fg ^= TB_ITALIC; + } + } + else if (Text[ch] == L'*') + { + if (ch < Len - 1 && Text[ch + 1] == L'*') + { + fg ^= TB_BOLD; + ch++; + } + else + { + fg ^= TB_ITALIC; + } + } + else + { + tb_printf(X, Y, fg, bg, "%lc", Text[ch]); +#ifdef DEBUG + tb_present(); +#endif + X++; + } + } +} + +// Print `Text`, `Len` characters long as a string wrapped at `XLimit` width and `YLimit` height. +void +tb_print_wrapped(u32 X, u32 Y, u32 XLimit, u32 YLimit, u32* Text, u32 Len) +{ + // Iterator in text + Assert(XLimit > 0); + Assert(YLimit > 0); + u32 i = XLimit; + + u32 PrevI = 0; + + // For printing + u32 t = 0; + + while(i < Len) + { + // Search backwards for whitespace + while (!is_whitespace(Text[i])) + { + i--; + + // Failed to find whitespace, break on limit at character + if (i == PrevI) + { + i += XLimit; + break; + } + } + + t = Text[i]; + Text[i] = 0; + tb_printf(X, Y++, 0, 0, "%ls", Text + PrevI); +#ifdef DEBUG + tb_present(); +#endif + + Text[i] = t; + + if (is_whitespace(Text[i])) i++; + + PrevI = i; + i += XLimit; + + if (Y >= YLimit - 1) + { + break; + } + } + tb_printf(X, Y++, 0, 0, "%ls", Text + PrevI); +} + +// Print raw string with markdown format options in `MDFormat`, wrapped at +// `XLimit` and `YLimit`. The string is offset by `XOffset` and `YOffset`. +// `fg` and `bg` are passed to `tb_printf`. +// `Len` is the length of the string not including a null terminator +// The wrapping algorithm searches for a whitespace backwards and if none are found it wraps at +// `XLimit`. +// This function first builds an array of positions where to wrap and then prints `Text` by +// character using the array in `MDFormat.Options` and `WrapPositions` to know when to act. +// Returns how many times wrapped +u32 +tb_print_wrapped_with_markdown(u32 XOffset, u32 YOffset, u32 fg, u32 bg, + u32* Text, u32 Len, + u32 XLimit, u32 YLimit, + markdown_formatoptions MDFormat) +{ + XLimit -= XOffset; + YLimit -= YOffset; + Assert(YLimit > 0); + Assert(XLimit > 0); + + u32 TextIndex = XLimit; + u32 PrevTextIndex = 0; + + u32 WrapPositions[Len/XLimit + 1]; + u32 WrapPositionsLen = 0; + + // Get wrap positions + while (TextIndex < Len) + { + while (!is_whitespace(Text[TextIndex])) + { + TextIndex--; + + if (TextIndex == PrevTextIndex) + { + TextIndex += XLimit; + break; + } + } + + WrapPositions[WrapPositionsLen] = TextIndex; + WrapPositionsLen++; + + PrevTextIndex = TextIndex; + TextIndex += XLimit; + } + + u32 MDFormatOptionsIndex = 0; + u32 WrapPositionsIndex = 0; + u32 X = XOffset, Y = YOffset; + + for (u32 TextIndex = 0; TextIndex < Len; TextIndex++) + { + if (MDFormatOptionsIndex < MDFormat.Len && + TextIndex == MDFormat.Options[MDFormatOptionsIndex].Position) + { + fg ^= MDFormat.Options[MDFormatOptionsIndex].Color; + MDFormatOptionsIndex++; + } + if (WrapPositionsIndex < WrapPositionsLen && + TextIndex == WrapPositions[WrapPositionsIndex]) + { + Y++; + if (Y == YLimit) return WrapPositionsIndex + 1; + WrapPositionsIndex++; + X = XOffset; + if (is_whitespace(Text[TextIndex])) continue; + } + tb_printf(X++, Y, fg, bg, "%lc", Text[TextIndex]); + } + Assert(WrapPositionsIndex == WrapPositionsLen); + Assert(MDFormat.Len == MDFormatOptionsIndex); + + return WrapPositionsLen + 1; +} + +// Return string without markdown markup characters using `is_markdown()` +// ScratchArena is used to allocate space for the raw text +// If ScratchArena is null then it will only return then length of the raw string +// Len should be characters + null terminator +// Copies the null terminator as well +raw_result +markdown_to_raw(Arena* ScratchArena, wchar_t* Text, u32 Len) +{ + raw_result Result = {0}; + if (ScratchArena) + { + Result.Text = ScratchArena->addr; + } + + for (u32 i = 0; i < Len; i++) + { + if (!is_markdown(Text[i])) + { + if (ScratchArena) + { + u32* ch = ArenaPush(ScratchArena, sizeof(*ch)); + *ch = Text[i]; + } + Result.Len++; + } + } + + return Result; +} + +// Get a string with markdown in it and fill array in makrdown_formtoptions with position and colors +// Use Scratcharena to make allocations on that buffer, The Maximimum space needed is Len, eg. when +// the string is only markup characters. +markdown_formatoptions +preprocess_markdown(Arena* ScratchArena, wchar_t* Text, u32 Len) +{ + markdown_formatoptions Result = {0}; + Result.Options = (format_option*)((u8*)ScratchArena->addr + ScratchArena->pos); + + format_option* FormatOpt; + + // raw char iterator + u32 rawch = 0; + + for (u32 i = 0; i < Len; i++) + { + switch (Text[i]) + { + case L'_': + { + FormatOpt = ArenaPush(ScratchArena, sizeof(*FormatOpt)); + Result.Len++; + + FormatOpt->Position = rawch; + if (i < Len - 1 && Text[i + 1] == '_') + { + FormatOpt->Color = TB_UNDERLINE; + i++; + } + else + { + FormatOpt->Color = TB_ITALIC; + } + } break; + case L'*': + { + FormatOpt = ArenaPush(ScratchArena, sizeof(*FormatOpt)); + Result.Len++; + + FormatOpt->Position = rawch; + if (i < Len - 1 && Text[i + 1] == '*') + { + FormatOpt->Color = TB_BOLD; + i++; + } + else + { + FormatOpt->Color = TB_ITALIC; + } + } break; + default: + { + rawch++; + } break; + } + } + + return Result; +} + +#endif diff --git a/test.h b/test.h deleted file mode 100644 index febdabd..0000000 --- a/test.h +++ /dev/null @@ -1,217 +0,0 @@ -// Library used for writing tests, must be included once for definitions and -// once for the `main()` function. -// -// DOCUMENTATION -// Expect(expr) -// Macro that will return early if the expression is false, calling -// `tb_shutdown()` to save the terminal. It will also print the expression -// and line on which it occurred for convenience -// -// test_functions TestFunctions -// Global variable containing the function (test). This array is -// 0-terminated. -// -// TESTFUNC() -// Macro for adding functions to TestFunctions array conveniently. -// -// tb_get_cell(x, y) -// Function -// -// EXAMPLE -// #define TEST_IMPL -// #include "test.h" -// -// bool FooTest() -// { -// Expect(0 == 1); -// return true; -// } -// -// test_functions TestFunctions[] = { -// TESTFUNC(FooTest), -// {0} -// } -// -// int main(int Argc, int Argv) -// { -// Test(Argc, Argv); -// } - -#ifndef TEST_H -#define TEST_H - -#include -#include - -#include "chatty.h" - -/* Macro's */ -#define Expect(expr) if (!(expr)) \ - { \ - tb_shutdown(); \ - printf("\n %d: %s\n", __LINE__, #expr); \ - return false; \ - } - -#define TESTFUNC(func) { func, #func } - -/* Types */ -typedef struct { - bool (*Func)(void); - const char * Name; -} test_functions; - -typedef struct { - s32 X, Y, W, H; -} rect; - -/* Declarations */ -struct tb_cell tb_get_cell(s32 x, s32 y); -void RectCopy(rect Dest, rect Source); -bool RectCmp(rect Dest, rect Source); -bool TextCmp(s32 X, s32 Y, wchar_t *Text, s32 TextLen); -bool TextCmpWithColor(s32 X, s32 Y, wchar_t *Text, s32 TextLen, s32 fg, s32 bg); - -/* Global variables */ -void (*Before)(void); -void (*After)(void); - -/* Functions*/ -struct tb_cell -tb_get_cell(s32 x, s32 y) -{ - return global.back.cells[global.width * y + x]; -} - -void -RectCopy(rect Dest, rect Source) -{ - for (u32 Y = 0; Y < Source.H; Y++) - { - for (u32 X = 0; X < Source.W; X++) - { - struct tb_cell Cell = tb_get_cell(Source.X + X, Source.Y + Y); - tb_set_cell(Dest.X + X, Dest.Y + Y, Cell.ch, Cell.fg, Cell.bg); - } - } -} - -bool -RectCmp(rect Dest, rect Source) -{ - for (u32 Y = 0; Y < Source.H; Y++) - { - for (u32 X = 0; X < Source.W; X++) - { - struct tb_cell SourceCell = tb_get_cell(Source.X + X, Source.Y + Y); - struct tb_cell DestCell = tb_get_cell(Dest.X + X, Dest.Y + Y); - if (!(SourceCell.fg == DestCell.fg && - SourceCell.bg == DestCell.bg && - SourceCell.ch == DestCell.ch)) - { - return false; - } - } - } - - return true; -} - -bool -TextCmp(s32 X, s32 Y, wchar_t *Text, s32 TextLen) -{ - for (u32 At = 0; - At < TextLen; - At++) - { - struct tb_cell DestCell = tb_get_cell(X++, Y); - if (DestCell.ch != Text[At]) return false; - } - - return true; -} - -bool -TextCmpWithColor(s32 X, s32 Y, wchar_t *Text, s32 TextLen, s32 fg, s32 bg) -{ - for (u32 At = 0; - At < TextLen; - At++) - { - struct tb_cell DestCell = tb_get_cell(X++, Y); - if (DestCell.ch != Text[At] || - DestCell.fg != fg || - DestCell.bg != bg) - { - return false; - } - } - - return true; -} - -int -Test(test_functions *TestFunctions, int Argc, char *Argv[]) -{ - u32 TestFunctionsLen = 0; - while (TestFunctions[TestFunctionsLen].Func) TestFunctionsLen++; - - if (Argc > 1) - { - for (u32 Arg = 0; - Arg < Argc; - Arg++) - { - char *Function = Argv[Arg]; - - for (u32 TestFunc = 0; - TestFunc < TestFunctionsLen; - TestFunc++) - { - if(!strcmp(Function, TestFunctions[TestFunc].Name)) - { - printf("%s ", TestFunctions[TestFunc].Name); - - if (Before) Before(); - bool Ret = TestFunctions[TestFunc].Func(); - if (After) After(); - - if (Ret) - { - printf("\033[32mPASSED\033[0m\n"); - } - else - { - printf("\033[31mFAILED\033[0m\n"); \ - } - } - } - } - } - else - { - for (int At = 0; - TestFunctions[At].Func; - At++) - { - printf("%s ", TestFunctions[At].Name); - - if (Before) Before(); - bool Ret = TestFunctions[At].Func(); - if (After) After(); - - if (Ret) - { - printf("\033[32mPASSED\033[0m\n"); - } - else - { - printf("\033[31mFAILED\033[0m\n"); \ - } - } - } - - return 0; -} - -#endif // TEST_H diff --git a/tests.c b/tests.c deleted file mode 100644 index a9a73a1..0000000 --- a/tests.c +++ /dev/null @@ -1,37 +0,0 @@ -#define MAX_INPUT_LEN 255 - -#define TB_IMPL -#include "external/termbox2.h" - -#include "chatty.h" - -#define TEST_IMPL -#include "test.h" - -#define Assert(expr) if (!(expr)) *(u8*)0 = 0 - -bool -DrawingTest(void) -{ - struct tb_event ev = {0}; - - - - return true; -} - -int -main(int Argc, char* Argv[]) -{ - test_functions TestFunctions[] = { - TESTFUNC(DrawingTest), - { 0 } - }; - - Assert(setlocale(LC_ALL, "")); - - Before = (void(*)(void))tb_init; - After = (void(*)(void))tb_shutdown; - - return(Test(TestFunctions, Argc, Argv)); -} diff --git a/tests/a.out b/tests/a.out new file mode 100755 index 0000000..4e8f3c3 Binary files /dev/null and b/tests/a.out differ diff --git a/tests/build.sh b/tests/build.sh new file mode 100755 index 0000000..2bab03e --- /dev/null +++ b/tests/build.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +ScriptDir="$(dirname "$(readlink -f "$0")")" +cd "$ScriptDir" +WarningFlags="-Wno-unused-variable" + +printf 'tests.c\n' +gcc -ggdb -Wall $WarningFlags -o tests tests.c +./tests + +# printf 'archived/input_box.c\n' +# gcc -DDEBUG -ggdb -Wall -pedantic -std=c11 -I external -I . -o "$BuildDir"/input_box archived/input_box.c diff --git a/tests/test.h b/tests/test.h new file mode 100644 index 0000000..23fb5cd --- /dev/null +++ b/tests/test.h @@ -0,0 +1,217 @@ +// Library used for writing tests, must be included once for definitions and +// once for the `main()` function. +// +// DOCUMENTATION +// Expect(expr) +// Macro that will return early if the expression is false, calling +// `tb_shutdown()` to save the terminal. It will also print the expression +// and line on which it occurred for convenience +// +// test_functions TestFunctions +// Global variable containing the function (test). This array is +// 0-terminated. +// +// TESTFUNC() +// Macro for adding functions to TestFunctions array conveniently. +// +// tb_get_cell(x, y) +// Function +// +// EXAMPLE +// #define TEST_IMPL +// #include "test.h" +// +// bool FooTest() +// { +// Expect(0 == 1); +// return true; +// } +// +// test_functions TestFunctions[] = { +// TESTFUNC(FooTest), +// {0} +// } +// +// int main(int Argc, int Argv) +// { +// Test(Argc, Argv); +// } + +#ifndef TEST_H +#define TEST_H + +#include +#include + +#include "../source/chatty.h" + +/* Macro's */ +#define Expect(expr) if (!(expr)) \ + { \ + tb_shutdown(); \ + printf("\n %d: %s\n", __LINE__, #expr); \ + return false; \ + } + +#define TESTFUNC(func) { func, #func } + +/* Types */ +typedef struct { + bool (*Func)(void); + const char * Name; +} test_functions; + +typedef struct { + s32 X, Y, W, H; +} rect; + +/* Declarations */ +struct tb_cell tb_get_cell(s32 x, s32 y); +void RectCopy(rect Dest, rect Source); +bool RectCmp(rect Dest, rect Source); +bool TextCmp(s32 X, s32 Y, wchar_t *Text, s32 TextLen); +bool TextCmpWithColor(s32 X, s32 Y, wchar_t *Text, s32 TextLen, s32 fg, s32 bg); + +/* Global variables */ +void (*Before)(void); +void (*After)(void); + +/* Functions*/ +struct tb_cell +tb_get_cell(s32 x, s32 y) +{ + return global.back.cells[global.width * y + x]; +} + +void +RectCopy(rect Dest, rect Source) +{ + for (u32 Y = 0; Y < Source.H; Y++) + { + for (u32 X = 0; X < Source.W; X++) + { + struct tb_cell Cell = tb_get_cell(Source.X + X, Source.Y + Y); + tb_set_cell(Dest.X + X, Dest.Y + Y, Cell.ch, Cell.fg, Cell.bg); + } + } +} + +bool +RectCmp(rect Dest, rect Source) +{ + for (u32 Y = 0; Y < Source.H; Y++) + { + for (u32 X = 0; X < Source.W; X++) + { + struct tb_cell SourceCell = tb_get_cell(Source.X + X, Source.Y + Y); + struct tb_cell DestCell = tb_get_cell(Dest.X + X, Dest.Y + Y); + if (!(SourceCell.fg == DestCell.fg && + SourceCell.bg == DestCell.bg && + SourceCell.ch == DestCell.ch)) + { + return false; + } + } + } + + return true; +} + +bool +TextCmp(s32 X, s32 Y, wchar_t *Text, s32 TextLen) +{ + for (u32 At = 0; + At < TextLen; + At++) + { + struct tb_cell DestCell = tb_get_cell(X++, Y); + if (DestCell.ch != Text[At]) return false; + } + + return true; +} + +bool +TextCmpWithColor(s32 X, s32 Y, wchar_t *Text, s32 TextLen, s32 fg, s32 bg) +{ + for (u32 At = 0; + At < TextLen; + At++) + { + struct tb_cell DestCell = tb_get_cell(X++, Y); + if (DestCell.ch != Text[At] || + DestCell.fg != fg || + DestCell.bg != bg) + { + return false; + } + } + + return true; +} + +int +Test(test_functions *TestFunctions, int Argc, char *Argv[]) +{ + u32 TestFunctionsLen = 0; + while (TestFunctions[TestFunctionsLen].Func) TestFunctionsLen++; + + if (Argc > 1) + { + for (u32 Arg = 0; + Arg < Argc; + Arg++) + { + char *Function = Argv[Arg]; + + for (u32 TestFunc = 0; + TestFunc < TestFunctionsLen; + TestFunc++) + { + if(!strcmp(Function, TestFunctions[TestFunc].Name)) + { + printf("%s ", TestFunctions[TestFunc].Name); + + if (Before) Before(); + bool Ret = TestFunctions[TestFunc].Func(); + if (After) After(); + + if (Ret) + { + printf("\033[32mPASSED\033[0m\n"); + } + else + { + printf("\033[31mFAILED\033[0m\n"); \ + } + } + } + } + } + else + { + for (int At = 0; + TestFunctions[At].Func; + At++) + { + printf("%s ", TestFunctions[At].Name); + + if (Before) Before(); + bool Ret = TestFunctions[At].Func(); + if (After) After(); + + if (Ret) + { + printf("\033[32mPASSED\033[0m\n"); + } + else + { + printf("\033[31mFAILED\033[0m\n"); \ + } + } + } + + return 0; +} + +#endif // TEST_H diff --git a/tests/tests b/tests/tests new file mode 100755 index 0000000..1ee83c7 Binary files /dev/null and b/tests/tests differ diff --git a/tests/tests.c b/tests/tests.c new file mode 100644 index 0000000..93ba4cc --- /dev/null +++ b/tests/tests.c @@ -0,0 +1,34 @@ +#define MAX_INPUT_LEN 255 + +#define TB_IMPL +#include "../source/termbox2.h" +#include "../source/chatty.h" + +#define TEST_IMPL +#include "test.h" + +#define Assert(expr) if (!(expr)) *(u8*)0 = 0 + +bool +DrawingTest(void) +{ + struct tb_event ev = {0}; + + return true; +} + +int +main(int Argc, char* Argv[]) +{ + test_functions TestFunctions[] = { + TESTFUNC(DrawingTest), + { 0 } + }; + + Assert(setlocale(LC_ALL, "")); + + Before = (void(*)(void))tb_init; + After = (void(*)(void))tb_shutdown; + + return(Test(TestFunctions, Argc, Argv)); +} diff --git a/types.h b/types.h deleted file mode 100644 index 7bd04c5..0000000 --- a/types.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef TYPES_H -#define TYPES_H - -#include -#include -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; - -#endif // TYPES_H diff --git a/ui.h b/ui.h deleted file mode 100644 index 078a456..0000000 --- a/ui.h +++ /dev/null @@ -1,819 +0,0 @@ -#ifndef UI_H -#define UI_H - -/* Macro's */ - -#include -#include -#include "termbox2.h" -#include "arena.h" - -/* Types */ - -// Format option at a position in raw text, used when iterating to know when to toggle a color -// option. -typedef struct { - u32 Position; - u32 Color; -} format_option; - -// Array of format options and length of said array -typedef struct { - format_option* Options; - u32 Len; -} markdown_formatoptions; - -typedef struct { - u32* Text; - u32 Len; -} raw_result; - -// Rectangle -typedef struct { - s32 X, Y, W, H; -} rect; - -// Characters to use for drawing a box -// See DrawBox() for an example -typedef struct { - wchar_t ur, ru, rd, dr, lr, ud; -} box_characters; - -/* Functions */ - -bool IsInRect(rect Rect, s32 X, s32 Y); -bool is_whitespace(u32 ch); -bool is_markdown(u32 ch); -void tb_print_wrapped(u32 X, u32 Y, u32 XLimit, u32 YLimit, u32* Text, u32 Len); -void tb_print_markdown(u32 X, u32 Y, u32 fg, u32 bg, u32* Text, u32 Len); -u32 tb_print_wrapped_with_markdown(u32 XOffset, u32 YOffset, u32 fg, u32 bg, - u32* Text, u32 Len, - u32 XLimit, u32 YLimit, - markdown_formatoptions MDFormat); -raw_result markdown_to_raw(Arena* ScratchArena, wchar_t* Text, u32 Len); -markdown_formatoptions preprocess_markdown(Arena* ScratchArena, wchar_t* Text, u32 Len); - -/* Input Box UI */ - -// assumes TEXTBOX_MAX_INPUT to be set -// -// #define TEXTBOX_MAX_INPUT 128 - -#define TEXTBOX_PADDING_X 1 -#define TEXTBOX_BORDER_WIDTH 1 -#define TEXTBOX_MIN_WIDTH TEXTBOX_PADDING_X * 2 + TEXTBOX_BORDER_WIDTH * 2 + 1; -#define TEXTBOXFROMBOX(Box) \ - { \ - .X = Box.X + TEXTBOX_BORDER_WIDTH + TEXTBOX_PADDING_X, \ - .Y = Box.Y + TEXTBOX_BORDER_WIDTH, \ - .W = Box.W - TEXTBOX_BORDER_WIDTH * 2 - TEXTBOX_PADDING_X * 2, \ - .H = Box.H - TEXTBOX_BORDER_WIDTH * 2 \ - } - -void DrawBox(rect Rect, box_characters *Chars); -void DrawTextBox(rect TextR, wchar_t *Text, u32 TextLen); -void DrawTextBoxWrapped(rect TextR, wchar_t *Text, u32 TextLen); -void TextBoxScrollLeft(rect Text, u32 *TextOffset); -void TextBoxScrollRight(rect Text, u32 *TextOffset); -void TextBoxDelete(wchar_t* Text, u64 Pos); -void TextBoxInsert(wchar_t *Input, u32 InputPos, u32 InputLen, wchar_t ch); -u32 TextBoxKeypress(struct tb_event ev, - rect TextR, wchar_t *Text, u32 *TextLenPtr, u32 TextPos, u32 *TextOffsetPtr); - -// Draw box along boundaries in Rect with optional Chars. -void -DrawBox(rect Rect, box_characters *Chars) -{ - wchar_t ur, ru, rd, dr, lr, ud; - if (!Chars) - { - ur = L'╭'; - ru = L'╯'; - rd = L'╮'; - dr = L'╰'; - lr = L'─'; - ud = L'│'; - } - else - { - ur = Chars->ur; - ru = Chars->ru; - rd = Chars->rd; - dr = Chars->dr; - lr = Chars->lr; - ud = Chars->ud; - } - - Rect.H--; - Rect.W--; - - tb_printf(Rect.X, Rect.Y, 0, 0, "%lc", ur); - for (s32 X = 1; X < Rect.W; X++) - { - tb_printf(Rect.X + X, Rect.Y, 0, 0, "%lc", lr); - } - tb_printf(Rect.X + Rect.W, Rect.Y, 0, 0, "%lc", rd); - - // Draw vertical bars - for (s32 Y = 1; Y < Rect.H; Y++) - { - tb_printf(Rect.X, Rect.Y + Y, 0, 0, "%lc", ud); - tb_printf(Rect.X + Rect.W, Rect.Y + Y, 0, 0, "%lc", ud); - } - - tb_printf(Rect.X, Rect.Y + Rect.H, 0, 0, "%lc", dr); - for (s32 X = 1; X < Rect.W; X++) - { - tb_printf(Rect.X + X, Rect.Y + Rect.H, 0, 0, "%lc", lr); - } - tb_printf(Rect.X + Rect.W, Rect.Y + Rect.H, 0, 0, "%lc", ru); -} - -// SCROLLING -// ╭──────────╮ ╭──────────╮ Going Left on the first character scrolls up. -// │ █3 4 │ => │ 1 2█ │ Cursor on end of the top line. -// │ 5 6 │ │ 3 4 │ -// ╰──────────╯ ╰──────────╯ -// -// ╭──────────╮ ╭──────────╮ Going Right on the last character scrolls down. -// │ 1 3 │ => │ 2 4 │ Puts cursor on start of the bottom line. -// │ 2 4█ │ │ █ │ -// ╰──────────╯ ╰──────────╯ -// -// ╭──────────╮ ╭──────────╮ Going Down on bottom line scrolls down. -// │ 1 3 │ => │ 2 4 │ Cursor stays on bottom line. -// │ 2 █ 4 │ │ █ │ -// ╰──────────╯ ╰──────────╯ -// -// ╭──────────╮ ╭──────────╮ Going Up on top line scrolls up. -// │ 3 █ 4 │ => │ 1 █ 2 │ Cursor stays on top line. -// │ 5 6 │ │ 3 5 │ -// ╰──────────╯ ╰──────────╯ -// -// In code this translates to changing global.cursor_{x,y} and TextOffset accordingly. - -// Scroll one character to the left -void -TextBoxScrollLeft(rect Text, u32 *TextOffset) -{ - // If text is on the first character of the box scroll up - if (global.cursor_x == Text.X && - global.cursor_y == Text.Y) - { - global.cursor_x = Text.X + Text.W - 1; - global.cursor_y = Text.Y; - - *TextOffset -= Text.W; - } - else - { - if (global.cursor_x == Text.X) - { - // Got to previous line - global.cursor_x = Text.X + Text.W - 1; - global.cursor_y--; - } - else - { - global.cursor_x--; - } - } -} - -// Scroll one character to the right -void -TextBoxScrollRight(rect Text, u32 *TextOffset) -{ - // If cursor is on the last character scroll forwards - if (global.cursor_x == Text.X + Text.W - 1 && - global.cursor_y == Text.Y + Text.H - 1) - { - global.cursor_x = Text.X; - global.cursor_y = Text.Y + Text.H - 1; - - *TextOffset += Text.W; - } - else - { - global.cursor_x++; - if (global.cursor_x == Text.X + Text.W) - { - global.cursor_x = Text.X; - global.cursor_y++; - } - } -} - - -// Delete a character in Text at Pos -void -TextBoxDelete(wchar_t* Text, u64 Pos) -{ - memmove(Text + Pos, - Text + Pos + 1, - (TEXTBOX_MAX_INPUT - Pos - 1) * sizeof(*Text)); -} - -// Insert a ev.ch in Input at InputPos -void -TextBoxInsert(wchar_t *Input, u32 InputPos, u32 InputLen, wchar_t ch) -{ - if (InputPos < InputLen) - { - memmove(Input + InputPos, - Input + InputPos - 1, - (InputLen - InputPos + 1) * sizeof(*Input)); - } - Input[InputPos] = ch; -} - -// Handle the key event ev changing Text, TextLenPtr, and TextOffsetPtr accordingly. -// InputPos is the position in the Input relating to the cursor position. -// TextR is the bounding box for the text. -// -// Returns non-zero when a key event was handled. -// -// TODO: pass by value and return struct with updated values -u32 -TextBoxKeypress(struct tb_event ev, rect TextR, - wchar_t *Text, u32 *TextLenPtr, u32 TextPos, u32 *TextOffsetPtr) -{ - u32 Result = 1; - - u32 TextLen = *TextLenPtr; - u32 TextOffset = *TextOffsetPtr; - - switch (ev.key) - { - - // Delete character backwards - case TB_KEY_CTRL_8: - // case TB_KEY_BACKSPACE2: - { - if (TextPos == 0) break; - - TextBoxDelete(Text, TextPos - 1); - TextLen--; - - TextBoxScrollLeft(TextR, &TextOffset); - - } break; - - // Delete character forwards - case TB_KEY_CTRL_D: - { - if (TextPos == TextLen) break; - TextBoxDelete(Text, TextPos); - TextLen--; - // Delete(Text, Position) - } break; - - // Delete word backwards - case TB_KEY_CTRL_W: - { - u32 At = TextPos; - // Find character to stop on - while (At && is_whitespace(Text[At - 1])) At--; - while (At && !is_whitespace(Text[At - 1])) At--; - - s32 NDelete = TextPos - At; - memmove(Text + At, Text + TextPos, (TextLen - TextPos) * sizeof(Text[At])); - TextLen -= NDelete; -#ifdef DEBUG - Text[TextLen] = 0; -#endif - // NOTE: this could be calculated at once instead - while(NDelete--) TextBoxScrollLeft(TextR, &TextOffset); - - Assert(IsInRect(TextR, global.cursor_x, global.cursor_y)); - - } break; - - // Delete until start of Text - case TB_KEY_CTRL_U: - { - memmove(Text, Text + TextPos, (TextLen - TextPos) * sizeof(*Text)); - TextLen -= TextPos; -#ifdef DEBUG - Text[TextLen] = 0; -#endif - global.cursor_x = TextR.X; - global.cursor_y = TextR.Y; - TextOffset = 0; - } break; - - // Delete until end of Text - case TB_KEY_CTRL_K: - { - TextLen = TextPos; - Text[TextPos] = 0; - } break; - - // Move to start of line - case TB_KEY_CTRL_A: global.cursor_x = TextR.X; break; - - // Move to end of line - case TB_KEY_CTRL_E: - { - if (global.cursor_x == TextR.X + TextR.W - 1) break; - - if (TextPos + TextR.W > TextLen) - { - // Put the cursor on the last character - global.cursor_x = TextR.X + (TextLen - TextOffset) % TextR.W; - } - else - { - global.cursor_x = TextR.X + TextR.W - 1; - } - } break; - - // Move backwards - case TB_KEY_CTRL_B: - case TB_KEY_ARROW_LEFT: - { - // Move forward by word - if (ev.mod == TB_MOD_CTRL) - { - u32 At = TextPos; - while(At && is_whitespace(Text[At])) At--; - while(At && !is_whitespace(Text[At])) At--; - while(TextPos - At++) TextBoxScrollLeft(TextR, &TextOffset); - } - // Move forward by character - else - { - if (TextPos == 0) break; - TextBoxScrollLeft(TextR, &TextOffset); - } - } break; - - // Move forwards - case TB_KEY_CTRL_F: - case TB_KEY_ARROW_RIGHT: - { - // Move forward by word - if (ev.mod == TB_MOD_CTRL) - { - u32 At = TextPos; - while(At < TextLen && is_whitespace(Text[At])) At++; - while(At < TextLen && !is_whitespace(Text[At])) At++; - while(At-- - TextPos) TextBoxScrollRight(TextR, &TextOffset); - } - // Move forward by character - else - { - if (TextPos == TextLen) break; - TextBoxScrollRight(TextR, &TextOffset); - } - } break; - - // Move up - case TB_KEY_CTRL_P: - case TB_KEY_ARROW_UP: - { - if (global.cursor_y == TextR.Y) - { - if (TextOffset == 0) - { - global.cursor_x = TextR.X; - - break; - } - - TextOffset -= TextR.W; - global.cursor_y = TextR.Y; - } - else - { - global.cursor_y--; - } - } break; - - // Move down - case TB_KEY_CTRL_N: - case TB_KEY_ARROW_DOWN: - { - if (TextPos + TextR.W > TextLen) - { - // Put the cursor on the last character - global.cursor_x = TextR.X + (TextLen - TextOffset) % (TextR.W); - global.cursor_y = TextR.Y + (TextLen - TextOffset) / TextR.W; - - // If cursor ended 1 line under the bottom line this means that the text - // needs to be scrolled. - if (global.cursor_y == TextR.Y + TextR.H) - { - TextOffset += TextR.W; - global.cursor_y--; - } - - break; - } - - if (global.cursor_y == TextR.Y + TextR.H - 1) - { - TextOffset += TextR.W; - } - else - { - global.cursor_y++; - } - } break; - default: - { - Result = 0; - } - } - - *TextLenPtr = TextLen; - *TextOffsetPtr = TextOffset; - - return Result; -} - -// Draws characters from Text fitting in the TextR rectangle. -// InputLen is the amount of characters in Text. -// -// NOTE: TextR is always filled, when not enough characters in Input it will uses spaces instead. -// This makes it easy to update the textbox by recalling this function. -void -DrawTextBox(rect TextR, wchar_t *Text, u32 TextLen) -{ - // Draw the text right of the cursor - // NOTE: the cursor is assumed to be in the box - Assert(IsInRect(TextR, global.cursor_x, global.cursor_y)); - s32 AtX = TextR.X, AtY = TextR.Y; - u32 At = 0; - while (AtY < TextR.Y + TextR.H) - { - if (At < TextLen) - { - tb_printf(AtX++, AtY, 0, 0, "%lc", Text[At++]); - } - else - { - tb_printf(AtX++, AtY, 0, 0, " "); - } - - if (AtX == TextR.X + TextR.W) { AtY++; AtX = TextR.X; } - } -} - -// NOTE: To ensure that the text looks the same even when scrolling it you must provide the whole text, -// wrap the whole text and the only show the portion that can fit in the Text Rectangle. - -// When line will exceed width break the word on the next line. This is done by looking backwards -// for whitespace from TextR.W width. When a whitespace is found text is wrapped on the next line. -// TODO: this does not work yet. -void -DrawTextBoxWrapped(rect TextR, wchar_t *Text, u32 TextLen) -{ - if (TextLen <= TextR.W) - { - tb_printf(TextR.X, TextR.Y, 0, 0, "%ls", Text); - tb_present(); - global.cursor_x = TextR.X + TextLen; - global.cursor_y = TextR.Y; - return; - } - - u32 SearchIndex = TextR.W; - u32 PrevIndex = 0; - u32 Y = TextR.Y; - - while (SearchIndex < TextLen) - { - while (Text[SearchIndex] != ' ') - { - SearchIndex--; - if (SearchIndex == PrevIndex) - { - SearchIndex += TextR.W; - break; - } - } - - // Wrap - wchar_t BreakChar = Text[SearchIndex]; - Text[SearchIndex] = 0; - tb_printf(TextR.X, Y, 0, 0, "%ls", Text + PrevIndex); - tb_present(); - Text[SearchIndex] = BreakChar; - - if (Y + 1 == TextR.Y + TextR.H) - { - global.cursor_y = Y; - global.cursor_x = TextR.X + (SearchIndex - PrevIndex); - return; - } - Y++; - - if (BreakChar == L' ') - { - SearchIndex++; - } - - PrevIndex = SearchIndex; - SearchIndex += TextR.W; - } - - // This happens when SearchIndex exceeds TextLen but there is still some - // text left to print. We can assume that the text will fit because otherwise it would have - // been wrapped a second time and the loop would have returned. - tb_printf(TextR.X, Y, 0, 0, "%ls", Text + PrevIndex); - // NOTE: this sets the cursor position correctly - - global.cursor_y = Y; - global.cursor_x = TextR.X + TextLen - PrevIndex; -} - -#endif // UI_H - -// Check if coordinate (X,Y) is in rect boundaries -bool -IsInRect(rect Rect, s32 X, s32 Y) -{ - if ((X >= Rect.X && X <= Rect.X + Rect.W) && - (Y >= Rect.Y && Y <= Rect.Y + Rect.H)) return true; - return false; -} - -// Return True if ch is whitespace -bool -is_whitespace(u32 ch) -{ - if (ch == L' ') - return true; - return false; -} - -// Return True if ch is a supported markdown markup character -// TODO: tilde -bool -is_markdown(u32 ch) -{ - if (ch == L'_' || - ch == L'*') - return true; - return false; -} - -// Print `Text`, `Len` characters long with markdown -// NOTE: This function has no wrapping support -void -tb_print_markdown(u32 X, u32 Y, u32 fg, u32 bg, u32* Text, u32 Len) -{ - for (u32 ch = 0; ch < Len; ch++) - { - if (Text[ch] == L'_') - { - if (ch < Len - 1 && Text[ch + 1] == L'_') - { - fg ^= TB_UNDERLINE; - ch++; - } - else - { - fg ^= TB_ITALIC; - } - } - else if (Text[ch] == L'*') - { - if (ch < Len - 1 && Text[ch + 1] == L'*') - { - fg ^= TB_BOLD; - ch++; - } - else - { - fg ^= TB_ITALIC; - } - } - else - { - tb_printf(X, Y, fg, bg, "%lc", Text[ch]); -#ifdef DEBUG - tb_present(); -#endif - X++; - } - } -} - -// Print `Text`, `Len` characters long as a string wrapped at `XLimit` width and `YLimit` height. -void -tb_print_wrapped(u32 X, u32 Y, u32 XLimit, u32 YLimit, u32* Text, u32 Len) -{ - // Iterator in text - Assert(XLimit > 0); - Assert(YLimit > 0); - u32 i = XLimit; - - u32 PrevI = 0; - - // For printing - u32 t = 0; - - while(i < Len) - { - // Search backwards for whitespace - while (!is_whitespace(Text[i])) - { - i--; - - // Failed to find whitespace, break on limit at character - if (i == PrevI) - { - i += XLimit; - break; - } - } - - t = Text[i]; - Text[i] = 0; - tb_printf(X, Y++, 0, 0, "%ls", Text + PrevI); -#ifdef DEBUG - tb_present(); -#endif - - Text[i] = t; - - if (is_whitespace(Text[i])) i++; - - PrevI = i; - i += XLimit; - - if (Y >= YLimit - 1) - { - break; - } - } - tb_printf(X, Y++, 0, 0, "%ls", Text + PrevI); -} - -// Print raw string with markdown format options in `MDFormat`, wrapped at -// `XLimit` and `YLimit`. The string is offset by `XOffset` and `YOffset`. -// `fg` and `bg` are passed to `tb_printf`. -// `Len` is the length of the string not including a null terminator -// The wrapping algorithm searches for a whitespace backwards and if none are found it wraps at -// `XLimit`. -// This function first builds an array of positions where to wrap and then prints `Text` by -// character using the array in `MDFormat.Options` and `WrapPositions` to know when to act. -// Returns how many times wrapped -u32 -tb_print_wrapped_with_markdown(u32 XOffset, u32 YOffset, u32 fg, u32 bg, - u32* Text, u32 Len, - u32 XLimit, u32 YLimit, - markdown_formatoptions MDFormat) -{ - XLimit -= XOffset; - YLimit -= YOffset; - Assert(YLimit > 0); - Assert(XLimit > 0); - - u32 TextIndex = XLimit; - u32 PrevTextIndex = 0; - - u32 WrapPositions[Len/XLimit + 1]; - u32 WrapPositionsLen = 0; - - // Get wrap positions - while (TextIndex < Len) - { - while (!is_whitespace(Text[TextIndex])) - { - TextIndex--; - - if (TextIndex == PrevTextIndex) - { - TextIndex += XLimit; - break; - } - } - - WrapPositions[WrapPositionsLen] = TextIndex; - WrapPositionsLen++; - - PrevTextIndex = TextIndex; - TextIndex += XLimit; - } - - u32 MDFormatOptionsIndex = 0; - u32 WrapPositionsIndex = 0; - u32 X = XOffset, Y = YOffset; - - for (u32 TextIndex = 0; TextIndex < Len; TextIndex++) - { - if (MDFormatOptionsIndex < MDFormat.Len && - TextIndex == MDFormat.Options[MDFormatOptionsIndex].Position) - { - fg ^= MDFormat.Options[MDFormatOptionsIndex].Color; - MDFormatOptionsIndex++; - } - if (WrapPositionsIndex < WrapPositionsLen && - TextIndex == WrapPositions[WrapPositionsIndex]) - { - Y++; - if (Y == YLimit) return WrapPositionsIndex + 1; - WrapPositionsIndex++; - X = XOffset; - if (is_whitespace(Text[TextIndex])) continue; - } - tb_printf(X++, Y, fg, bg, "%lc", Text[TextIndex]); - } - Assert(WrapPositionsIndex == WrapPositionsLen); - Assert(MDFormat.Len == MDFormatOptionsIndex); - - return WrapPositionsLen + 1; -} - -// Return string without markdown markup characters using `is_markdown()` -// ScratchArena is used to allocate space for the raw text -// If ScratchArena is null then it will only return then length of the raw string -// Len should be characters + null terminator -// Copies the null terminator as well -raw_result -markdown_to_raw(Arena* ScratchArena, wchar_t* Text, u32 Len) -{ - raw_result Result = {0}; - if (ScratchArena) - { - Result.Text = ScratchArena->addr; - } - - for (u32 i = 0; i < Len; i++) - { - if (!is_markdown(Text[i])) - { - if (ScratchArena) - { - u32* ch = ArenaPush(ScratchArena, sizeof(*ch)); - *ch = Text[i]; - } - Result.Len++; - } - } - - return Result; -} - -// Get a string with markdown in it and fill array in makrdown_formtoptions with position and colors -// Use Scratcharena to make allocations on that buffer, The Maximimum space needed is Len, eg. when -// the string is only markup characters. -markdown_formatoptions -preprocess_markdown(Arena* ScratchArena, wchar_t* Text, u32 Len) -{ - markdown_formatoptions Result = {0}; - Result.Options = (format_option*)((u8*)ScratchArena->addr + ScratchArena->pos); - - format_option* FormatOpt; - - // raw char iterator - u32 rawch = 0; - - for (u32 i = 0; i < Len; i++) - { - switch (Text[i]) - { - case L'_': - { - FormatOpt = ArenaPush(ScratchArena, sizeof(*FormatOpt)); - Result.Len++; - - FormatOpt->Position = rawch; - if (i < Len - 1 && Text[i + 1] == '_') - { - FormatOpt->Color = TB_UNDERLINE; - i++; - } - else - { - FormatOpt->Color = TB_ITALIC; - } - } break; - case L'*': - { - FormatOpt = ArenaPush(ScratchArena, sizeof(*FormatOpt)); - Result.Len++; - - FormatOpt->Position = rawch; - if (i < Len - 1 && Text[i + 1] == '*') - { - FormatOpt->Color = TB_BOLD; - i++; - } - else - { - FormatOpt->Color = TB_ITALIC; - } - } break; - default: - { - rawch++; - } break; - } - } - - return Result; -} diff --git a/ui_box.h b/ui_box.h deleted file mode 100644 index c19b9dd..0000000 --- a/ui_box.h +++ /dev/null @@ -1,472 +0,0 @@ -#ifndef BOX_H -#define BOX_H - -// assumes MAX_INPUT_LEN to be set -// -// #define MAX_INPUT_LEN 128 - -#define TEXTBOX_PADDING_X 1 -#define TEXTBOX_BORDER_WIDTH 1 -#define TEXTBOX_MIN_WIDTH TEXTBOX_PADDING_X * 2 + TEXTBOX_BORDER_WIDTH * 2 + 1; -#define TEXTBOXFROMBOX(Box) \ - { \ - .X = Box.X + TEXTBOX_BORDER_WIDTH + TEXTBOX_PADDING_X, \ - .Y = Box.Y + TEXTBOX_BORDER_WIDTH, \ - .W = Box.W - TEXTBOX_BORDER_WIDTH * 2 - TEXTBOX_PADDING_X * 2, \ - .H = Box.H - TEXTBOX_BORDER_WIDTH * 2 \ - } - -void DrawBox(rect Rect, box_characters *Chars); -void ScrollLeft(rect Text, u32 *TextOffset); -void ScrollRight(rect Text, u32 *TextOffset); -void TextBoxDelete(wchar_t* Text, u64 Pos); -void TextBoxInsert(wchar_t *Input, u32 InputPos, u32 InputLen, wchar_t ch); -u32 TextBoxKeypress(struct tb_event ev, rect TextR, wchar_t *Text, u32 *TextLenPtr, u32 TextPos, u32 *TextOffsetPtr); -void TextBoxDrawText(rect TextR, wchar_t *Input, u32 InputLen, u32 TextOffset); - -// Draw box along boundaries in Rect with optional Chars. -void -DrawBox(rect Rect, box_characters *Chars) -{ - wchar_t ur, ru, rd, dr, lr, ud; - if (!Chars) - { - ur = L'╭'; - ru = L'╯'; - rd = L'╮'; - dr = L'╰'; - lr = L'─'; - ud = L'│'; - } - else - { - ur = Chars->ur; - ru = Chars->ru; - rd = Chars->rd; - dr = Chars->dr; - lr = Chars->lr; - ud = Chars->ud; - } - - Rect.H--; - Rect.W--; - - tb_printf(Rect.X, Rect.Y, 0, 0, "%lc", ur); - for (s32 X = 1; X < Rect.W; X++) - { - tb_printf(Rect.X + X, Rect.Y, 0, 0, "%lc", lr); - } - tb_printf(Rect.X + Rect.W, Rect.Y, 0, 0, "%lc", rd); - - // Draw vertical bars - for (s32 Y = 1; Y < Rect.H; Y++) - { - tb_printf(Rect.X, Rect.Y + Y, 0, 0, "%lc", ud); - tb_printf(Rect.X + Rect.W, Rect.Y + Y, 0, 0, "%lc", ud); - } - - tb_printf(Rect.X, Rect.Y + Rect.H, 0, 0, "%lc", dr); - for (s32 X = 1; X < Rect.W; X++) - { - tb_printf(Rect.X + X, Rect.Y + Rect.H, 0, 0, "%lc", lr); - } - tb_printf(Rect.X + Rect.W, Rect.Y + Rect.H, 0, 0, "%lc", ru); -} - -// SCROLLING -// ╭──────────╮ ╭──────────╮ Going Left on the first character scrolls up. -// │ █3 4 │ => │ 1 2█ │ Cursor on end of the top line. -// │ 5 6 │ │ 3 4 │ -// ╰──────────╯ ╰──────────╯ -// -// ╭──────────╮ ╭──────────╮ Going Right on the last character scrolls down. -// │ 1 3 │ => │ 2 4 │ Puts cursor on start of the bottom line. -// │ 2 4█ │ │ █ │ -// ╰──────────╯ ╰──────────╯ -// -// ╭──────────╮ ╭──────────╮ Going Down on bottom line scrolls down. -// │ 1 3 │ => │ 2 4 │ Cursor stays on bottom line. -// │ 2 █ 4 │ │ █ │ -// ╰──────────╯ ╰──────────╯ -// -// ╭──────────╮ ╭──────────╮ Going Up on top line scrolls up. -// │ 3 █ 4 │ => │ 1 █ 2 │ Cursor stays on top line. -// │ 5 6 │ │ 3 5 │ -// ╰──────────╯ ╰──────────╯ -// -// In code this translates to changing global.cursor_{x,y} and TextOffset accordingly. - -// Scroll one character to the left -void -TextBoxScrollLeft(rect Text, u32 *TextOffset) -{ - // If text is on the first character of the box scroll up - if (global.cursor_x == Text.X && - global.cursor_y == Text.Y) - { - global.cursor_x = Text.X + Text.W - 1; - global.cursor_y = Text.Y; - - *TextOffset -= Text.W; - } - else - { - if (global.cursor_x == Text.X) - { - // Got to previous line - global.cursor_x = Text.X + Text.W - 1; - global.cursor_y--; - } - else - { - global.cursor_x--; - } - } -} - -// Scroll one character to the right -void -TextBoxScrollRight(rect Text, u32 *TextOffset) -{ - // If cursor is on the last character scroll forwards - if (global.cursor_x == Text.X + Text.W - 1 && - global.cursor_y == Text.Y + Text.H - 1) - { - global.cursor_x = Text.X; - global.cursor_y = Text.Y + Text.H - 1; - - *TextOffset += Text.W; - } - else - { - global.cursor_x++; - if (global.cursor_x == Text.X + Text.W) - { - global.cursor_x = Text.X; - global.cursor_y++; - } - } -} - - -// Delete a character in Text at Pos -void -TextBoxDelete(wchar_t* Text, u64 Pos) -{ - memmove(Text + Pos, - Text + Pos + 1, - (MAX_INPUT_LEN - Pos - 1) * sizeof(*Text)); -} - -// Insert a ev.ch in Input at InputPos -void -TextBoxInsert(wchar_t *Input, u32 InputPos, u32 InputLen, wchar_t ch) -{ - if (InputPos < InputLen) - { - memmove(Input + InputPos, - Input + InputPos - 1, - (InputLen - InputPos + 1) * sizeof(*Input)); - } - Input[InputPos] = ch; -} - -// Handle the key event and edit Input and updates TextLenPtr and TextOffsetPtr accordingly. -// InputPos is the position in the Input relating to the cursor position. -// TextR is the bounding box for the text. -// Returns 0 no key event was handled -u32 -TextBoxKeypress(struct tb_event ev, rect TextR, - wchar_t *Text, u32 *TextLenPtr, u32 TextPos, u32 *TextOffsetPtr) -{ - u32 Result = 1; - - u32 TextLen = *TextLenPtr; - u32 TextOffset = *TextOffsetPtr; - - switch (ev.key) - { - - // Delete character backwards - case TB_KEY_CTRL_8: - // case TB_KEY_BACKSPACE2: - { - if (TextPos == 0) break; - - TextBoxDelete(Text, TextPos - 1); - TextLen--; - - TextBoxScrollLeft(TextR, &TextOffset); - - } break; - - // Delete character forwards - case TB_KEY_CTRL_D: - { - if (TextPos == TextLen) break; - TextBoxDelete(Text, TextPos); - TextLen--; - // Delete(Text, Position) - } break; - - // Delete word backwards - case TB_KEY_CTRL_W: - { - u32 At = TextPos; - // Find character to stop on - while (At && is_whitespace(Text[At - 1])) At--; - while (At && !is_whitespace(Text[At - 1])) At--; - - s32 NDelete = TextPos - At; - memmove(Text + At, Text + TextPos, (TextLen - TextPos) * sizeof(Text[At])); - TextLen -= NDelete; -#ifdef DEBUG - Text[TextLen] = 0; -#endif - // NOTE: this could be calculated at once instead - while(NDelete--) TextBoxScrollLeft(TextR, &TextOffset); - - Assert(IsInRect(TextR, global.cursor_x, global.cursor_y)); - - } break; - - // Delete until start of Text - case TB_KEY_CTRL_U: - { - memmove(Text, Text + TextPos, (TextLen - TextPos) * sizeof(*Text)); - TextLen -= TextPos; -#ifdef DEBUG - Text[TextLen] = 0; -#endif - global.cursor_x = TextR.X; - global.cursor_y = TextR.Y; - TextOffset = 0; - } break; - - // Delete until end of Text - case TB_KEY_CTRL_K: - { - TextLen = TextPos; - Text[TextPos] = 0; - } break; - - // Move to start of line - case TB_KEY_CTRL_A: global.cursor_x = TextR.X; break; - - // Move to end of line - case TB_KEY_CTRL_E: - { - if (global.cursor_x == TextR.X + TextR.W - 1) break; - - if (TextPos + TextR.W > TextLen) - { - // Put the cursor on the last character - global.cursor_x = TextR.X + (TextLen - TextOffset) % TextR.W; - } - else - { - global.cursor_x = TextR.X + TextR.W - 1; - } - } break; - - // Move backwards - case TB_KEY_CTRL_B: - case TB_KEY_ARROW_LEFT: - { - // Move forward by word - if (ev.mod == TB_MOD_CTRL) - { - u32 At = TextPos; - while(At && is_whitespace(Text[At])) At--; - while(At && !is_whitespace(Text[At])) At--; - while(TextPos - At++) TextBoxScrollLeft(TextR, &TextOffset); - } - // Move forward by character - else - { - if (TextPos == 0) break; - TextBoxScrollLeft(TextR, &TextOffset); - } - } break; - - // Move forwards - case TB_KEY_CTRL_F: - case TB_KEY_ARROW_RIGHT: - { - // Move forward by word - if (ev.mod == TB_MOD_CTRL) - { - u32 At = TextPos; - while(At < TextLen && is_whitespace(Text[At])) At++; - while(At < TextLen && !is_whitespace(Text[At])) At++; - while(At-- - TextPos) TextBoxScrollRight(TextR, &TextOffset); - } - // Move forward by character - else - { - if (TextPos == TextLen) break; - TextBoxScrollRight(TextR, &TextOffset); - } - } break; - - // Move up - case TB_KEY_CTRL_P: - case TB_KEY_ARROW_UP: - { - if (global.cursor_y == TextR.Y) - { - if (TextOffset == 0) - { - global.cursor_x = TextR.X; - - break; - } - - TextOffset -= TextR.W; - global.cursor_y = TextR.Y; - } - else - { - global.cursor_y--; - } - } break; - - // Move down - case TB_KEY_CTRL_N: - case TB_KEY_ARROW_DOWN: - { - if (TextPos + TextR.W > TextLen) - { - // Put the cursor on the last character - global.cursor_x = TextR.X + (TextLen - TextOffset) % (TextR.W); - global.cursor_y = TextR.Y + (TextLen - TextOffset) / TextR.W; - - // If cursor ended 1 line under the bottom line this means that the text - // needs to be scrolled. - if (global.cursor_y == TextR.Y + TextR.H) - { - TextOffset += TextR.W; - global.cursor_y--; - } - - break; - } - - if (global.cursor_y == TextR.Y + TextR.H - 1) - { - TextOffset += TextR.W; - } - else - { - global.cursor_y++; - } - } break; - default: - { - Result = 0; - } - } - - *TextLenPtr = TextLen; - *TextOffsetPtr = TextOffset; - - return Result; -} - -// Draws characters in Input in the TextR rectangle. -// Skip printing TextOffset amount of characters. -// InputLen is the amount of characters in Input. -// -// NOTE: TextR is always filled, when not enough characters in Input it will uses spaces instead. -// This makes it easy to update the textbox by recalling this function. -void -TextBoxDraw(rect TextR, wchar_t *Text, u32 TextLen) -{ - // Draw the text right of the cursor - // NOTE: the cursor is assumed to be in the box - Assert(IsInRect(TextR, global.cursor_x, global.cursor_y)); - s32 AtX = TextR.X, AtY = TextR.Y; - u32 At = 0; - while (AtY < TextR.Y + TextR.H) - { - if (At < TextLen) - { - tb_printf(AtX++, AtY, 0, 0, "%lc", Text[At++]); - } - else - { - tb_printf(AtX++, AtY, 0, 0, " "); - } - - if (AtX == TextR.X + TextR.W) { AtY++; AtX = TextR.X; } - } -} - -// NOTE: To ensure that the text looks the same even when scrolling it you must provide the whole text, -// wrap the whole text and the only show the portion that can fit in the Text Rectangle. - -// When line will exceed width break the word on the next line. This is done by looking backwards -// for whitespace from TextR.W width. When a whitespace is found text is wrapped on the next line. -void -TextBoxDrawWrapped(rect TextR, wchar_t *Text, u32 TextLen) -{ - if (TextLen <= TextR.W) - { - tb_printf(TextR.X, TextR.Y, 0, 0, "%ls", Text); - tb_present(); - global.cursor_x = TextR.X + TextLen; - global.cursor_y = TextR.Y; - return; - } - - u32 SearchIndex = TextR.W; - u32 PrevIndex = 0; - u32 Y = TextR.Y; - - while (SearchIndex < TextLen) - { - while (Text[SearchIndex] != ' ') - { - SearchIndex--; - if (SearchIndex == PrevIndex) - { - SearchIndex += TextR.W; - break; - } - } - - // Wrap - wchar_t BreakChar = Text[SearchIndex]; - Text[SearchIndex] = 0; - tb_printf(TextR.X, Y, 0, 0, "%ls", Text + PrevIndex); - tb_present(); - Text[SearchIndex] = BreakChar; - - if (Y + 1 == TextR.Y + TextR.H) - { - global.cursor_y = Y; - global.cursor_x = TextR.X + (SearchIndex - PrevIndex); - return; - } - Y++; - - if (BreakChar == L' ') - { - SearchIndex++; - } - - PrevIndex = SearchIndex; - SearchIndex += TextR.W; - } - - // This happens when SearchIndex exceeds TextLen but there is still some - // text left to print. We can assume that the text will fit because otherwise it would have - // been wrapped a second time and the loop would have returned. - tb_printf(TextR.X, Y, 0, 0, "%ls", Text + PrevIndex); - // NOTE: this sets the cursor position correctly - - global.cursor_y = Y; - global.cursor_x = TextR.X + TextLen - PrevIndex; -} - -#endif // BOX_H diff --git a/ui_wrapped.c b/ui_wrapped.c deleted file mode 100644 index 67ddffe..0000000 --- a/ui_wrapped.c +++ /dev/null @@ -1,82 +0,0 @@ -#define MAX_INPUT_LEN 255 - -#define TB_IMPL -#include "external/termbox2.h" -#undef TB_IMPL -#define CHATTY_IMPL -#include "ui.h" -#undef CHATTY_IMPL - -void -DrawBoxTest(rect Box, wchar_t *DummyText) -{ - u32 TextLen = wcslen(DummyText); - - wchar_t Text[TextLen]; - bzero(Text, sizeof(Text)); - wcsncpy(Text, DummyText, TextLen + 1); // copy n*ull terminator - - rect TextR = TEXTBOXFROMBOX(Box); - // fill the cursor for reference - for (s32 Y = 0; Y < TextR.H; Y++) - for (s32 X = 0; X < TextR.W; X++) - tb_printf(TextR.X + X, TextR.Y + Y, 0, TB_BLUE, " "); - - DrawBox(Box, 0); - tb_present(); - TextBoxDrawWrapped(TextR, Text, TextLen); - tb_printf(0, TextR.Y - 1, 0, 0, - "%d (%d, %d) ~(%d, %d)", - TextLen, - global.cursor_x, global.cursor_y, - global.cursor_x - TextR.X, global.cursor_y - TextR.Y); - tb_printf(global.cursor_x, global.cursor_y, 0, TB_RED, " "); -} - -struct tb_cell -tb_get_cell(s32 X, s32 Y) -{ - return global.back.cells[global.width * Y + X]; -} - - -int -main(int Argc, char* Argv[]) -{ - struct tb_event ev; - rect Box = {0, 0, 24, 4}; - - setlocale(LC_ALL, ""); - - tb_init(); - bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); - - /* Text that does not fit */ - DrawBoxTest(Box, L"This is some dummy text meant to be wrapped multiple times."); - Box.Y += Box.H; - - /* Text that fits */ - DrawBoxTest(Box, L"That is some."); - Box.Y += Box.H; - - /* Text ending on a space */ - DrawBoxTest(Box, L"There is something. "); - Box.Y += Box.H; - - /* Text that fits the surface */ - DrawBoxTest(Box, L"This is something. This is something."); - Box.Y += Box.H; - - /* Text that wraps once */ - DrawBoxTest(Box, L"This is something I do not."); - - rect NewBox = Box; - NewBox.X += Box.W + 2; - DrawBox(NewBox, 0); - - tb_present(); - tb_poll_event(&ev); - - tb_shutdown(); - return 0; -} diff --git a/wrap.c b/wrap.c deleted file mode 100644 index 57506b8..0000000 --- a/wrap.c +++ /dev/null @@ -1,52 +0,0 @@ -// 1. Search backwards for whitespace -// - found? -// y) wrap -// n) break at limit -// - end? -// y) terminate -// n) goto 1. with offset += limit -void -wrap(u8* Text, u32 Len, u32 XLimit, u32 YLimit) -{ - u32 SearchingOffset = XLimit; - u32 X = SearchingOffset; - u32 Y = 0; - u8 t; - u32 PrevX = 0; - - while (X < Len) - { - // Search for whitespace to break on - while (1) - { - if (is_whitespace(Text[X])) break; - - X--; - - // if we got back to the previous position break on Text[SearchingOffset] - if (X == PrevX) - { - X = XLimit; - break; - } - } - - // break - t = Text[X]; - *(Text + X) = '\0'; - tb_printf(0, Y, 0, 0, "%s", Text + PrevX); - Text[X] = t; - Y++; - if (Y >= YLimit) break; - - // consume leading whitespace - while (is_whitespace(Text[X])) X++; - - PrevX = X; - X += XLimit; - } - - tb_printf(0, Y, 0, 0, "%s", Text + PrevX); - - return; -} -- cgit v1.2.3