diff options
author | Raymaekers Luca <raymaekers.luca@gmail.com> | 2024-11-26 16:15:52 +0100 |
---|---|---|
committer | Raymaekers Luca <raymaekers.luca@gmail.com> | 2024-11-26 16:15:52 +0100 |
commit | 51de26be505c781dd17e4c1ff90dd1f4c46899b9 (patch) | |
tree | 28b0c596c866253a9ff8e1ef64d62ac6f7d41057 | |
parent | 3060303b36dc23f08d923a6bcb15129ff7602864 (diff) | |
parent | 45ab190f9e618bd24c6e498e31da3f7e9d3f03b9 (diff) |
Merge branch 'main' of db:chatty
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | archived/utf8toASCII.c | 163 | ||||
-rwxr-xr-x | build.sh | 21 | ||||
-rw-r--r-- | chatty.c | 65 | ||||
-rw-r--r-- | ui.h | 110 |
5 files changed, 286 insertions, 78 deletions
@@ -60,3 +60,8 @@ The idea is the following: - *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` + +- 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/ diff --git a/archived/utf8toASCII.c b/archived/utf8toASCII.c new file mode 100644 index 0000000..988a69b --- /dev/null +++ b/archived/utf8toASCII.c @@ -0,0 +1,163 @@ +#include <stdlib.h> +#include <stdio.h> +#include <stdio.h> +#include <locale.h> +#include <assert.h> +#include <wchar.h> +#include <stdint.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; + +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; +} @@ -1,17 +1,6 @@ #!/bin/sh -build () { - ( - set -x - gcc -ggdb -Wall -pedantic -std=c99 -I./external -o ${1%.c} $@ - ) -} - -if [ "$1" ]; then - build "$1" - exit -fi - -[ -x ./external/keyboard ] || build external/keyboard.c -build chatty.c -build server.c -build send.c +set -x +gcc external/keyboard.c +gcc -DDEBUG -ggdb -Wall -pedantic -std=c99 -o chatty chatty.c +gcc -DDEBUG -ggdb -Wall -pedantic -std=c99 -o server server.c +gcc -DDEBUG -ggdb -Wall -pedantic -std=c99 -o send send.c @@ -22,8 +22,6 @@ // Number of spaces inserted when pressing Tab/Ctrl+I #define TAB_WIDTH 4 -#define DEBUG - #include "chatty.h" #include "protocol.h" #include "ui.h" @@ -278,21 +276,49 @@ run_command_get_output(char *Command, char *Argv[], u8 *OutputBuffer, int Len) // it displays a prompt with the user input of input_len wide characters // and the received messages from msgsArena void -screen_home(Arena* ScratchArena, +DisplayChat(Arena* ScratchArena, Arena* MessagesArena, u32 MessagesNum, Arena* ClientsArena, struct pollfd* fds, wchar_t Input[], u32 InputLen) { - u32 BoxHeight = 3; +#define MIN_TEXT_WIDTH_FOR_WRAPPING 20 + u32 BoxHeight = GetInputBoxMinimumHeight(); + u32 MinBoxWidth = GetInputBoxMinimumWidth(); + u32 BoxWidth = global.width - 1; + u32 InputBoxTextWidth = BoxWidth - MinBoxWidth + 2; - if (global.height < BoxHeight || - global.width < 8) + if (InputLen >= InputBoxTextWidth && + InputBoxTextWidth > MIN_TEXT_WIDTH_FOR_WRAPPING) + { + BoxHeight++; + } + + u32 FreeHeight = global.height - BoxHeight; + +#undef MIN_TEXT_WIDTH_FOR_WRAPPING + + if (global.height < BoxHeight || global.width < MinBoxWidth) { tb_hide_cursor(); return; } + bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); + InputBox(0, FreeHeight, BoxWidth, BoxHeight, + 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: @@ -301,11 +327,9 @@ screen_home(Arena* ScratchArena, // 03:24:33 [TlasT] │ I am fine // 03:24:33 [Fin] │ I am too { - s32 VerticalBarOffset = TIMESTAMP_LEN + AUTHOR_LEN + 2; - u32 FreeHeight = global.height - BoxHeight; - if (FreeHeight <= 0) - goto draw_prompt; + // 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; @@ -441,22 +465,6 @@ screen_home(Arena* ScratchArena, } } - // Print vertical bar - for (u32 Y = 0; Y < FreeHeight; Y++) - tb_print(VerticalBarOffset, Y, 0, 0, "│"); - - draw_prompt: - InputBox(0, FreeHeight, BoxWidth, BoxHeight, - Input, InputLen, - True); - bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); - - - if (fds[FDS_UNI].fd == -1 || fds[FDS_BI].fd == -1) - { - // show error popup - popup(TB_RED, TB_BLACK, (u8*)"Server disconnected."); - } } } @@ -567,7 +575,7 @@ main(int argc, char** argv) tb_init(); tb_get_fds(&fds[FDS_TTY].fd, &fds[FDS_RESIZE].fd); - screen_home(&ScratchArena, + DisplayChat(&ScratchArena, &MessagesArena, MessagesNum, &ClientsArena, fds, Input, InputIndex); @@ -713,6 +721,7 @@ main(int argc, char** argv) } break; case TB_KEY_BACKSPACE2: if (InputIndex) InputIndex--; + Input[InputIndex] = 0; break; case TB_KEY_CTRL_D: case TB_KEY_CTRL_C: @@ -780,7 +789,7 @@ main(int argc, char** argv) tb_poll_event(&ev); } - screen_home(&ScratchArena, &MessagesArena, MessagesNum, &ClientsArena, fds, Input, InputIndex); + DisplayChat(&ScratchArena, &MessagesArena, MessagesNum, &ClientsArena, fds, Input, InputIndex); tb_present(); } @@ -295,6 +295,28 @@ preprocess_markdown(Arena* ScratchArena, wchar_t* Text, u32 Len) return Result; } +u32 InputBoxMarginX = 1; +u32 InputBoxPaddingX = 1; +#define INPUT_BOX_BORDER_WIDTH 1 +#define INPUT_BOX_MIN_TEXT_WIDTH 1 +#define INPUT_BOX_MIN_TEXT_HEIGHT 1 + +u32 +GetInputBoxMinimumWidth() +{ + return InputBoxPaddingX * 2 + + InputBoxMarginX * 2 + + INPUT_BOX_BORDER_WIDTH * 2 + + INPUT_BOX_MIN_TEXT_WIDTH; +} + +u32 +GetInputBoxMinimumHeight() +{ + return INPUT_BOX_BORDER_WIDTH * 2 + + INPUT_BOX_MIN_TEXT_HEIGHT; +} + void InputBox(u32 BoxX, u32 BoxY, u32 BoxWidth, u32 BoxHeight, wchar_t *Text, u32 TextLen, @@ -308,17 +330,9 @@ InputBox(u32 BoxX, u32 BoxY, u32 BoxWidth, u32 BoxHeight, // P -> padding (symmetric) // M -> margin (symmetric) - // Configuration options - u32 MarginX = 1; - u32 PaddingX = 1; - u32 MinTextSpace = 3; - u32 BorderWidth = 1; - - // TODO: return early if there is not enough space - if (BoxHeight < 3) - return; - if (BoxWidth < MarginX * 2 + PaddingX * 2 + MinTextSpace) - return; + u32 MarginX = InputBoxMarginX; + u32 PaddingX = InputBoxPaddingX; + u32 BorderWidth = INPUT_BOX_BORDER_WIDTH; // Get 0-based coordinate BoxWidth -= 2* MarginX; @@ -370,35 +384,63 @@ InputBox(u32 BoxX, u32 BoxY, u32 BoxWidth, u32 BoxHeight, u32 TextHeight = BoxHeight - BorderWidth * 2 + 1; u32 TextDisplaySize = TextWidth * TextHeight; - u32 At = 0; - // If there is not enough space to fit the text scroll one line by advancing by textwidth. - if (TextLen >= TextDisplaySize) - { - // TextHeight - 1 : scroll by one line - At = (TextLen / TextWidth - (TextHeight - 1)) * TextWidth; - } - -#ifdef DEBUG - tb_printf(BoxX + 1, BoxY, 0, 0, "%d/%d %dx%d %d", TextLen, MAX_INPUT_LEN, TextWidth, TextHeight, At); -#endif - - // Keep if needed for cursor position + // XOffset and YOffset are needed for setting the cursor position u32 XOffset = 0, YOffset = 0; - while (At < TextLen) + u32 TextOffset = 0; + + // If there is more than one line, implement vertical wrapping otherwise scroll the text + // horizontally. + if (TextHeight > 1) { - for (YOffset = 0; - YOffset < TextHeight && At < TextLen; - YOffset++) + // If there is not enough space to fit the text scroll one line by advancing by textwidth. + if (TextLen >= TextDisplaySize) + { + // TextHeight - 1 : scroll by one line + TextOffset = (TextLen / TextWidth - (TextHeight - 1)) * TextWidth; + } + + // Print the text + while (TextOffset < TextLen) { - for (XOffset = 0; - XOffset < TextWidth && At < TextLen; - XOffset++) + for (YOffset = 0; + YOffset < TextHeight && TextOffset < TextLen; + YOffset++) { - tb_printf(TextX + XOffset, TextY + YOffset, 0, 0, "%lc", Text[At]); - At++; + for (XOffset = 0; + XOffset < TextWidth && TextOffset < TextLen; + XOffset++) + { + tb_printf(TextX + XOffset, TextY + YOffset, 0, 0, "%lc", Text[TextOffset]); + TextOffset++; + } } } } + else + { + // Scrooll the text horizontally + if (TextLen >= TextDisplaySize) + { + TextOffset = TextLen - TextWidth; + XOffset = TextWidth; + } + else + { + XOffset = TextLen; + } + YOffset = 1; + tb_printf(TextX, TextY, 0, 0, "%ls", Text + TextOffset); + } + +#ifdef DEBUG + tb_printf(BoxX + 1, BoxY, 0, 0, "%d/%d [%dx%d] %dx%d %d (%d,%d)+(%d,%d)", + TextLen, MAX_INPUT_LEN, + BoxWidth, BoxHeight, + TextWidth, TextHeight, + TextOffset, + TextX, TextY, + XOffset, YOffset); +#endif // Set the cursor if (Focused) @@ -409,7 +451,7 @@ InputBox(u32 BoxX, u32 BoxY, u32 BoxWidth, u32 BoxHeight, global.cursor_x = TextX; global.cursor_y = TextY; } - else if (TextLen % TextWidth == 0) + else if (TextLen % TextWidth == 0 && TextHeight > 1) { // When at the end of width put the cursor on the next line global.cursor_x = TextX; |