aboutsummaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorRaymaekers Luca <luca@keyfried.com>2025-10-12 15:24:32 +0200
committerRaymaekers Luca <luca@spacehb.net>2025-10-12 15:24:32 +0200
commit90099147cf34336ffd621f35f550e32977b97e2f (patch)
tree4eb713e78748fe5b31514886e822c245a6e9e52e /source
parent376eadd28320e21b12488c9cbb3f154f2d573778 (diff)
checkpoint
Diffstat (limited to 'source')
-rwxr-xr-xsource/archived/v1/.gitignore6
-rwxr-xr-xsource/build.sh40
-rw-r--r--source/chatty.c528
-rw-r--r--source/chatty.h8
-rw-r--r--source/server.c197
5 files changed, 411 insertions, 368 deletions
diff --git a/source/archived/v1/.gitignore b/source/archived/v1/.gitignore
new file mode 100755
index 0000000..6ecdb26
--- /dev/null
+++ b/source/archived/v1/.gitignore
@@ -0,0 +1,6 @@
+chatty
+server
+recv
+send
+client
+tags
diff --git a/source/build.sh b/source/build.sh
index 317e9a4..e8c89bd 100755
--- a/source/build.sh
+++ b/source/build.sh
@@ -4,11 +4,47 @@ ScriptDir="$(dirname "$(readlink -f "$0")")"
cd "$ScriptDir"
BuildDir="$ScriptDir"/../build
+CompilerFlags="
+-DIMPORT_ID=1
+"
+
+WarningFlags="
+-Wall
+-Wextra
+-Wno-unused-variable
+-Wno-unused-parameter
+-Wno-unused-but-set-variable
+-Wno-maybe-uninitialized
+-Wno-sign-compare
+"
+
+Mode="$1"
+if [ "$Mode" != "release" ]
+then
+ Mode="debug"
+fi
+printf '[Mode %s]\n' "$Mode"
+
+if [ "$Mode" = "debug" ]
+then
+ CompilerFlags="$CompilerFlags
+ -DDEBUG=1
+ -ggdb -g3
+ "
+elif [ "$Mode" = "release" ]
+then
+ CompilerFlags="$CompilerFlags
+ -O3
+ "
+fi
+
mkdir -p "$BuildDir"
+
printf 'chatty.c\n'
-gcc -DDEBUG -ggdb -Wall -pedantic -std=c11 -I external -o "$BuildDir"/chatty chatty.c
+gcc $CompilerFlags $WarningFlags -I external -o "$BuildDir"/chatty chatty.c
+
printf 'server.c\n'
-gcc -DDEBUG -ggdb -Wall -pedantic -std=c99 -o "$BuildDir"/server server.c
+gcc $CompilerFlags $WarningFlags -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
index 32a4431..01d450b 100644
--- a/source/chatty.c
+++ b/source/chatty.c
@@ -15,7 +15,7 @@
#define TIMEOUT_RECONNECT 1
#define MAX_INPUT_LEN 512
// Filepath where user ID is stored
-#define ID_FILE "_id"
+#define ID_FILE ".chatty_id"
// Filepath where logged
#define LOGFILE "chatty.log"
// enable logging
@@ -26,10 +26,10 @@
#ifndef Assert
#ifdef DEBUG
#define Assert(expr) if (!(expr)) \
- { \
- tb_shutdown(); \
- raise(SIGTRAP); \
- }
+{ \
+tb_shutdown(); \
+raise(SIGTRAP); \
+}
#else
#define Assert(expr) ;
#endif // DEBUG
@@ -49,10 +49,10 @@
#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 };
+ FDS_UNI, // For two-way communication with the server (eg. IDMessage)
+ FDS_TTY,
+ FDS_RESIZE,
+ FDS_MAX };
typedef struct {
u8 Author[AUTHOR_LEN];
@@ -96,7 +96,7 @@ 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++)
{
@@ -117,16 +117,16 @@ add_user_info(Arena* clientsArena, s32 fd, u64 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;
}
@@ -137,10 +137,10 @@ 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;
}
@@ -157,14 +157,14 @@ authenticate(User* user, s32 fd)
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
@@ -178,7 +178,7 @@ authenticate(User* user, s32 fd)
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);
@@ -205,7 +205,7 @@ thread_reconnect(void* fds_ptr)
{
// timeout
nanosleep(&t, &t);
-
+
bifd = get_connection(&address);
if (bifd == -1)
{
@@ -219,27 +219,27 @@ thread_reconnect(void* fds_ptr)
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;
}
@@ -247,31 +247,31 @@ 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);
@@ -285,12 +285,12 @@ run_command_get_output(char *Command, char *Argv[], u8 *OutputBuffer, int Len)
Result.Error = 1;
return Result;
}
-
+
close(CommandPipe[1]);
-
+
Result.NumRead = read(CommandPipe[0], OutputBuffer, Len);
Assert(Result.NumRead != -1);
-
+
return Result;
}
@@ -308,49 +308,49 @@ DisplayChat(Arena* ScratchArena,
};
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
- );
+ (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
@@ -358,53 +358,53 @@ DisplayChat(Arena* ScratchArena,
// 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:
+ 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:
+ case HEADER_TYPE_HISTORY:
MessageAddress += sizeof(HistoryMessage);
break;
- default:
+ default:
// unhandled message type
Assert(0);
}
}
-
+
u32 MessageY = 0;
-
+
for (u32 i = MessagesOffset;
- i < MessagesNum;
- i++)
+ 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)
{
@@ -412,84 +412,84 @@ DisplayChat(Arena* ScratchArena,
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 [<author>] ", 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)
+ case HEADER_TYPE_TEXT:
{
- 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
+ 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 [<author>] ", 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:
{
- // We still displayed the timestamp so we need to increment the Y.
+ 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++;
- }
-
- 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:
+ 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;
@@ -507,40 +507,40 @@ main(int argc, char** argv)
fprintf(stderr, "usage: chatty <username>\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
@@ -548,18 +548,18 @@ main(int argc, char** argv)
{-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));
+ s32 nread = read(idfile, &user.ID, sizeof(user.ID));
Assert(nread != -1);
#endif
/* Authentication */
@@ -591,43 +591,43 @@ main(int argc, char** argv)
fds[FDS_BI].fd = bifd;
fds[FDS_UNI].fd = unifd;
}
-
+
#ifdef IMPORT_ID
// Save id
- write(idfile, &user.id, sizeof(user.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)
{
@@ -646,39 +646,39 @@ main(int argc, char** argv)
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:
+ case HEADER_TYPE_TEXT:
recvTextMessage(&MessagesArena, fds[FDS_UNI].fd);
MessagesNum++;
break;
- case HEADER_TYPE_PRESENCE:;
+ 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:
+ 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:
+ case TB_KEY_CTRL_W:
// delete consecutive whitespace
while (InputIndex)
{
@@ -700,111 +700,111 @@ main(int argc, char** argv)
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'))
+ case TB_KEY_CTRL_Z:
{
- OutputBuffer[BufferIndex] = 0;
- BufferIndex--;
- }
-
- // Append to output
- for (s32 BufferIndex = 0; BufferIndex < Output.NumRead; BufferIndex++)
+ pid_t pid = getpid();
+ tb_shutdown();
+ kill(pid, SIGSTOP);
+ tb_init();
+ } break;
+ case TB_KEY_CTRL_Y: // Paste clipboard contents to input
{
- // 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++)
+ 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:
{
- Input[InputIndex] = L' ';
- InputIndex++;
- }
- } break;
- case TB_KEY_BACKSPACE2:
+ 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:
+ 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
+ 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:
+ 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++;
@@ -812,23 +812,23 @@ main(int argc, char** argv)
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
index f7525fa..04e2a85 100644
--- a/source/chatty.h
+++ b/source/chatty.h
@@ -58,19 +58,19 @@ 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);
}
diff --git a/source/server.c b/source/server.c
index a6613d6..689537e 100644
--- a/source/server.c
+++ b/source/server.c
@@ -13,11 +13,11 @@
#ifndef Assert
#ifdef DEBUG
#define Assert(expr) if (!(expr)) { \
- raise(SIGTRAP); \
+raise(SIGTRAP); \
}
#else
#define Assert(expr) if (!(expr)) { \
- raise(SIGTRAP); \
+raise(SIGTRAP); \
}
#endif // DEBUG
#endif // Assert
@@ -42,8 +42,9 @@
#define FDS_SIZE (fdsArena.pos / sizeof(struct pollfd))
#define CLIENTS_SIZE (clientsArena.pos / sizeof(Client))
+#define IMPORT_ID 1
// Where to save clients
-#define CLIENTS_FILE "_clients"
+#define CLIENTS_FILE ".chatty_clients"
// Where to write logs
#define LOGFILE "server.log"
// Log to LOGFILE instead of stderr
@@ -51,8 +52,8 @@
// enum for indexing the fds array
enum { FDS_STDIN = 0,
- FDS_SERVER,
- FDS_CLIENTS };
+ FDS_SERVER,
+ FDS_CLIENTS };
// Client information
typedef struct {
@@ -80,7 +81,7 @@ Client*
getClientByID(Client* clients, u32 nclients, ID id)
{
if (!id) return 0;
-
+
for (u32 i = 0; i < nclients; i++)
{
if (clients[i].id == id)
@@ -95,7 +96,7 @@ 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) ||
@@ -111,7 +112,7 @@ printTextMessage(TextMessage* message, Client* client, u8 wide)
{
u8 timestamp[TIMESTAMP_LEN] = {0};
formatTimestamp(timestamp, message->timestamp);
-
+
if (wide)
{
setlocale(LC_ALL, "");
@@ -134,7 +135,7 @@ sendToOthers(Client* clients, u32 nclients, Client* client, ClientFD type, Heade
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)
@@ -150,7 +151,7 @@ sendToOthers(Client* clients, u32 nclients, Client* client, ClientFD type, Heade
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);
}
@@ -183,8 +184,8 @@ sendToAll(Client* clients, u32 nclients, ClientFD type, HeaderMessage* header, v
assert(0);
assert(nsend != -1);
LoggingF("sendToAll|[%s]->"CLIENT_FMT" %d bytes\n", headerTypeString(header->type),
- CLIENT_ARG(clients[i]),
- nsend);
+ CLIENT_ARG(clients[i]),
+ nsend);
}
}
@@ -213,7 +214,7 @@ 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};
@@ -231,16 +232,16 @@ authenticate(Arena* clientsArena, s32 clients_file, struct pollfd* pollfd, Heade
{
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)
{
@@ -257,15 +258,15 @@ authenticate(Arena* clientsArena, s32 clients_file, struct pollfd* pollfd, Heade
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 */
@@ -278,40 +279,40 @@ authenticate(Arena* clientsArena, s32 clients_file, struct pollfd* pollfd, Heade
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));
+ headerTypeString(HEADER_TYPE_INTRODUCTION),
+ headerTypeString(HEADER_TYPE_ID));
return 0;
}
@@ -319,19 +320,19 @@ 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);
- }
+ {
+ LogFD = open(LOGFILE, O_RDWR | O_CREAT | O_TRUNC, 0600);
+ assert(LogFD != -1);
+ }
}
-
+
s32 serverfd;
// Start listening on the socket
{
@@ -339,25 +340,25 @@ main(int argc, char** argv)
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;
@@ -366,7 +367,7 @@ main(int argc, char** argv)
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
@@ -379,21 +380,21 @@ main(int argc, char** argv)
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++)
{
@@ -406,16 +407,16 @@ main(int argc, char** argv)
#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
@@ -426,7 +427,7 @@ main(int argc, char** argv)
{
// 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);
@@ -434,7 +435,7 @@ main(int argc, char** argv)
}
else
LoggingF("New connection(%d)\n", clientfd);
-
+
// TODO: find empty space in arena (fragmentation)
if (nclients + 1 == MAX_CONNECTIONS)
{
@@ -453,13 +454,13 @@ main(int argc, char** argv)
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);
@@ -467,7 +468,7 @@ main(int argc, char** argv)
{
LoggingF("Received error from fd: %d, errno: %d\n", fds[conn].fd, errno);
};
-
+
Client* client;
if (nrecv != sizeof(header))
{
@@ -486,14 +487,14 @@ main(int argc, char** argv)
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);
@@ -511,71 +512,71 @@ main(int argc, char** argv)
}
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)
+ /* Send text message to all other clients */
+ case HEADER_TYPE_TEXT:
{
- 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:
+ 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);
+ CLIENT_ARG((*client)),
+ fds[conn].fd);
disconnectAndNotify(client, nclients, client);
continue;
}
}
}
-
+
#ifdef IMPORT_ID
close(clients_file);
#endif
-
+
return 0;
}