aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaymaekers Luca <raymaekers.luca@gmail.com>2024-11-26 16:15:52 +0100
committerRaymaekers Luca <raymaekers.luca@gmail.com>2024-11-26 16:15:52 +0100
commit51de26be505c781dd17e4c1ff90dd1f4c46899b9 (patch)
tree28b0c596c866253a9ff8e1ef64d62ac6f7d41057
parent3060303b36dc23f08d923a6bcb15129ff7602864 (diff)
parent45ab190f9e618bd24c6e498e31da3f7e9d3f03b9 (diff)
Merge branch 'main' of db:chatty
-rw-r--r--README.md5
-rw-r--r--archived/utf8toASCII.c163
-rwxr-xr-xbuild.sh21
-rw-r--r--chatty.c65
-rw-r--r--ui.h110
5 files changed, 286 insertions, 78 deletions
diff --git a/README.md b/README.md
index 74554de..fa561b6 100644
--- a/README.md
+++ b/README.md
@@ -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;
+}
diff --git a/build.sh b/build.sh
index 4548af8..9d69974 100755
--- a/build.sh
+++ b/build.sh
@@ -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
diff --git a/chatty.c b/chatty.c
index 9dcc2b0..ae7929f 100644
--- a/chatty.c
+++ b/chatty.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();
}
diff --git a/ui.h b/ui.h
index ff98598..21a4a80 100644
--- a/ui.h
+++ b/ui.h
@@ -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;