aboutsummaryrefslogtreecommitdiff
path: root/v2/chatty.c
diff options
context:
space:
mode:
Diffstat (limited to 'v2/chatty.c')
-rw-r--r--v2/chatty.c490
1 files changed, 0 insertions, 490 deletions
diff --git a/v2/chatty.c b/v2/chatty.c
deleted file mode 100644
index 817d421..0000000
--- a/v2/chatty.c
+++ /dev/null
@@ -1,490 +0,0 @@
-#define TB_IMPL
-#include "termbox2.h"
-
-#include "arena.h"
-#include "common.h"
-
-#include <arpa/inet.h>
-#include <assert.h>
-#include <locale.h>
-#include <poll.h>
-#include <pthread.h>
-#include <sys/socket.h>
-
-#define TIMEOUT_POLL 60 * 1000
-// time to reconnect in seconds
-#define TIMEOUT_RECONNECT 1
-
-// must be of AUTHOR_LEN -1
-static u8 username[AUTHOR_LEN] = "(null)";
-// file descriptros for polling
-static struct pollfd *fds = NULL;
-// mutex for locking fds when in thread_reconnect()
-static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-
-enum { FDS_SERVER = 0,
- FDS_TTY,
- FDS_RESIZE,
- FDS_MAX };
-
-void *thread_reconnect(void *address_ptr);
-void fillstr(wchar_t *str, wchar_t ch, u32 len);
-void popup(u32 fg, u32 bg, char *text);
-u32 tb_printf_wrap(u32 x, u32 y, u32 fg, u32 bg, wchar_t *text, u32 fg_pfx, u32 bg_pfx, char *pfx, s32 limit);
-void screen_home(Arena *msgsArena, wchar_t input[]);
-
-int main(int argc, char **argv)
-{
- // Use first argument as username
- if (argc > 1) {
- u32 arg_len = strlen(argv[1]);
- assert(arg_len <= AUTHOR_LEN - 1);
- memcpy(username, argv[1], arg_len);
- username[arg_len] = '\0';
- }
-
- s32 err, serverfd, ttyfd, resizefd, nsend;
- setlocale(LC_ALL, ""); /* Fix unicode handling */
-
- const struct sockaddr_in address = {
- AF_INET,
- htons(PORT),
- {0},
- };
-
- serverfd = socket(AF_INET, SOCK_STREAM, 0);
- assert(serverfd > 2); // greater than STDERR
-
- err = connect(serverfd, (struct sockaddr *)&address, sizeof(address));
- if (err != 0) {
- perror("Server");
- return 1;
- }
-
- tb_init();
- tb_get_fds(&ttyfd, &resizefd);
-
- // poopoo C cannot infer type
- fds = (struct pollfd[FDS_MAX]){
- {serverfd, POLLIN, 0},
- { ttyfd, POLLIN, 0},
- {resizefd, POLLIN, 0},
- };
-
- Arena *msgsArena = ArenaAlloc();
- // Message *messages = msgsArena->memory; // helper pointer, for indexing memory
- Arena *msgTextArena = ArenaAlloc();
- u32 nrecv = 0;
- // buffer used for receiving and sending messages
- u8 buf[STREAM_BUF] = {0};
- Message *mbuf = (Message *)buf;
-
- wchar_t input[256] = {0};
- u32 input_len = 0;
- struct tb_event ev;
- char *errmsg = NULL;
-
- // Display loop
- screen_home(msgsArena, input);
- tb_present();
-
- u8 quit = 0;
- 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_SERVER].revents & POLLIN) {
- // got data from server
- u8 timestamp[TIMESTAMP_LEN];
- message_timestamp(timestamp);
-
- nrecv = recv(fds[FDS_SERVER].fd, buf, STREAM_LIMIT, 0);
- assert(nrecv != -1);
-
- // TODO: Handle this in a thread, the best way would be
- // -> server disconnect info (somewhere, for now popup)
- // -> user can still view messages, exit & type but not send
- // -> try to reconnect in background
- if (nrecv == 0) {
- // close diconnected server's socket
- err = close(fds[FDS_SERVER].fd);
- assert(err == 0);
- fds[FDS_SERVER].fd = -1; // ignore
- // start trying to reconnect in a thread
- pthread_t t;
- err = pthread_create(&t, NULL, &thread_reconnect, (void*)&address);
- assert(err == 0);
-
- } else {
- Message *buf_msg = (Message *)buf; // helper for indexing memory
- Message *recvmsg = ArenaPush(msgsArena, sizeof(Message));
- // copy everything but the text
- memcpy(recvmsg, buf, AUTHOR_LEN + TIMESTAMP_LEN + sizeof(buf_msg->text_len));
- // allocate memeory for text
- recvmsg->text = ArenaPush(msgTextArena, recvmsg->text_len * sizeof(wchar_t));
- // copy the text to the allocated space
- memcpy(recvmsg->text, buf + TIMESTAMP_LEN + AUTHOR_LEN + sizeof(recvmsg->text_len), recvmsg->text_len * sizeof(wchar_t));
- }
- }
-
- 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 (input_len) {
- if (input[input_len - 1] == L' ') {
- input[input_len - 1] = 0;
- input_len--;
- continue;
- }
- break;
- }
- // delete until whitespace
- while (input_len) {
- if (input[input_len - 1] == L' ')
- break;
- // erase
- input[input_len - 1] = 0;
- input_len--;
- }
- break;
- case TB_KEY_CTRL_D:
- case TB_KEY_CTRL_C:
- quit = 1;
- break;
- case TB_KEY_CTRL_M: // send message
- if (input_len == 0)
- // do not send empty message
- break;
- if (fds[FDS_SERVER].fd == -1)
- // do not send message to disconnected server
- break;
-
- // null terminate
- input[input_len] = 0;
- input_len++;
- // TODO: check size does not exceed buffer
-
- // add to msgsArena
- Message *sendmsg = ArenaPush(msgsArena, sizeof(Message));
- memcpy(sendmsg->author, username, AUTHOR_LEN);
- message_timestamp(sendmsg->timestamp);
- sendmsg->text_len = input_len;
- sendmsg->text = ArenaPush(msgTextArena, input_len * sizeof(wchar_t));
- // copy the text to the allocated space
- memcpy(sendmsg->text, input, input_len * sizeof(wchar_t));
-
- // Send the message
- // copy everything but the text
- memcpy(buf, sendmsg, AUTHOR_LEN + TIMESTAMP_LEN + sizeof(wchar_t));
- memcpy(&mbuf->text, input, input_len * sizeof(wchar_t));
- nsend = send(fds[FDS_SERVER].fd, buf, AUTHOR_LEN + TIMESTAMP_LEN + input_len * sizeof(wchar_t), 0);
- assert(nsend > 0);
-
- case TB_KEY_CTRL_U: // clear input
- bzero(input, input_len * sizeof(wchar_t));
- input_len = 0;
- break;
- default:
- assert(ev.ch >= 0);
- if (ev.ch == 0)
- break;
- // append key to input buffer
- // TODO: check size does not exceed buffer
- input[input_len] = ev.ch;
- input_len++;
-
- break;
- }
- if (quit)
- break;
- }
-
- // These are used to redraw the screen from threads
- if (fds[FDS_RESIZE].revents & POLLIN) {
- // ignore
- tb_poll_event(&ev);
- }
-
- screen_home(msgsArena, input);
-
- tb_present();
- }
-
- tb_shutdown();
-
- if (errmsg != NULL)
- printf("%s\n", errmsg);
-
- ArenaRelease(msgTextArena);
- ArenaRelease(msgsArena);
-
- return 0;
-}
-
-// Takes as paramter `struct sockaddr_in*` and uses it to connect to the server.
-// When the server sends a disconnect message this function must be called with the fds struct as
-// paramter. To indicate that the server is offline the fds[FDS_SERVER] is set to -1. When online
-// it is set to a non-zero value.
-// Returns NULL.
-void *thread_reconnect(void *address_ptr)
-{
- u32 serverfd, err;
- struct sockaddr_in *address = address_ptr;
-
- while (1) {
- serverfd = socket(AF_INET, SOCK_STREAM, 0);
- assert(serverfd > 2); // greater than STDERR
- err = connect(serverfd, (struct sockaddr *)address, sizeof(*address));
- if (err == 0)
- break;
- assert(errno == ECONNREFUSED);
- sleep(TIMEOUT_RECONNECT);
- }
-
- // if the server would send a disconnect again and the polling catches up there could be two
- // threads accessing fds.
- pthread_mutex_lock(&mutex);
- fds[FDS_SERVER].fd = serverfd;
- pthread_mutex_unlock(&mutex);
-
- // ask to redraw screen
- raise(SIGWINCH);
-
- return NULL;
-}
-
-// fill str array with char
-void fillstr(wchar_t *str, wchar_t 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, char *text)
-{
- u32 len = strlen(text);
- assert(len > 0);
- tb_print(global.width / 2 - len / 2, global.height / 2, fg, bg, text);
-}
-
-// Print `text` of text_len` wide characters wrapped to limit. x, y, fg and
-// bg will be passed to the tb_printf() function calls.
-// pfx is a string that will be printed first and will not be wrapped on characters like msg->text,
-// this is useful when for example: printing messages and wanting to have consistent
-// timestamp+author name.
-// Returns the number of lines printed.
-// TODO: remove text_len and calculate it in the function
-// TODO: add y limit
-// TODO:(bug) text after pfx is wrapped one too soon
-// TODO: text == NULL to know how many lines *would* be printed
-// TODO: check if text[i] goes out of bounds
-u32 tb_printf_wrap(u32 x, u32 y, u32 fg, u32 bg, wchar_t *text, u32 fg_pfx, u32 bg_pfx, char *pfx, s32 limit)
-{
- assert(limit > 0);
-
- /// Algorithm
- // 1. Advance by limit
- // 2. Look backwards for whitespace
- // 3. split the string at the whitespace
- // 4. print the string
- // 5. restore the string (optional)
- // 6. set the offset
- // 7. repeat step 1. until i > len
- // 8. print remaining part of the string
-
- // lines y, incremented after each wrap
- s32 ly = y;
- // character the text is split on
- wchar_t t = 0;
- // index used for searching in string
- s32 i = limit;
- // previous i for windowing through the text
- s32 offset = 0;
- // used when retrying to get a longer limit
- u32 failed = 0;
-
- u32 text_len = 0;
- while (text[text_len] != 0)
- text_len++;
-
- // NOTE: We can assume that we need to wrap, therefore print a newline after the prefix string
- if (pfx != NULL) {
- tb_printf(x, ly, fg_pfx, bg_pfx, "%s", pfx);
-
- s32 pfx_len = strlen(pfx);
- if (limit > pfx_len + text_len) {
- // everything fits on one line
- tb_printf(pfx_len, y, fg, bg, "%ls", text);
- return 1;
- } else {
- ly++;
- }
- }
-
- while (i < text_len) {
- // search backwards for whitespace
- while (i > offset && text[i] != L' ')
- i--;
-
- // retry with bigger limit
- if (i == offset) {
- offset = i;
- failed++;
- i += limit + failed * limit;
- continue;
- } else {
- failed = 0;
- }
-
- t = text[i];
- text[i] = 0;
- tb_printf(x, ly, fg, bg, "%ls", text + offset);
- text[i] = t;
-
- i++; // after the space
- ly++;
-
- offset = i;
- i += limit;
- }
- tb_printf(x, ly, fg, bg, "%ls", text + offset);
- ly++;
-
- return ly - y;
-}
-
-// home screen, the first screen the user sees
-// it displays a prompt for user input and the received messages from msgsArena
-void screen_home(Arena *msgsArena, wchar_t input[])
-{
- // config options
- const u32 box_max_len = 80;
- const u32 box_min_len = 3;
- const u32 box_x = 0, box_y = global.height - 3, box_pad_x = 1, box_mar_x = 1, box_bwith = 1, box_height = 3;
- u32 input_len = 0;
- while (input[input_len] != 0)
- input_len++;
- const u32 prompt_x = box_x + box_pad_x + box_mar_x + box_bwith + input_len;
-
- // the minimum height required is the hight for the box prompt
- // the minimum width required is that one character should fit in the box prompt
- if (global.height < box_height ||
- global.width < (box_x + box_mar_x * 2 + box_pad_x * 2 + box_bwith * 2 + 1)) {
- // + 1 for cursor
- tb_hide_cursor();
- return;
- } else {
- // show cursor
- // TODO: show cursor as block character instead of using the real cursor
- bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]);
- }
-
- // 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
- {
- u32 freesp = global.height - box_height;
- if (freesp <= 0)
- goto draw_prompt;
-
- Message *messages = msgsArena->memory;
- assert(messages != NULL);
- // on what line to print the current message, used for scrolling
- u32 msg_y = 0;
-
- u32 nmessages = (msgsArena->pos / sizeof(Message));
- u32 offs = (nmessages > freesp) ? nmessages - freesp : 0;
-
- for (u32 i = offs; i < nmessages; i++) {
- // Color user's own messages
- u32 fg = 0;
- if (strncmp((char *)username, (char *)messages[i].author, AUTHOR_LEN) == 0) {
- fg = TB_CYAN;
- } else {
- fg = TB_MAGENTA;
- }
-
- u32 ty = 0;
- char pfx[AUTHOR_LEN + TIMESTAMP_LEN - 2 + 5] = {0};
- sprintf(pfx, "%s [%s] ", messages[i].timestamp, messages[i].author);
- ty = tb_printf_wrap(0, msg_y, TB_WHITE, 0, messages[i].text, fg, 0, pfx, global.width);
- msg_y += ty;
- }
-
- draw_prompt:
- // Draw prompt box which is a box made out of
- // should look like this: ╭───────╮
- // │ text█ │
- // ╰───────╯
- // the text is padded to the left and right by box_pad_x
- // the middle/inner part is opaque
- // TODO: wrapping when the text is bigger & alternated with scrolling when there is not
- // enough space.
- {
- u32 box_len = 0;
- if (global.width >= box_max_len + 2 * box_mar_x)
- box_len = box_max_len;
- else
- box_len = global.width - box_mar_x * 2;
-
- // +2 for corners and null terminator
- wchar_t box_up[box_len + 1];
- wchar_t box_in[box_len + 1];
- wchar_t box_down[box_len + 1];
- wchar_t lr = L'─', ur = L'╭', rd = L'╮', dr = L'╰', ru = L'╯', ud = L'│';
-
- // top bar
- box_up[0] = ur;
- fillstr(box_up + 1, lr, box_len - 1);
- box_up[box_len - 1] = rd;
- box_up[box_len] = 0;
- // inner part
- fillstr(box_in + 1, L' ', box_len - 1);
- box_in[0] = ud;
- box_in[box_len - 1] = ud;
- box_in[box_len] = 0;
- // bottom bar
- box_down[0] = dr;
- fillstr(box_down + 1, lr, box_len - 1);
- box_down[box_len - 1] = ru;
- box_down[box_len] = 0;
-
- tb_printf(box_x + box_mar_x, box_y, 0, 0, "%ls", box_up);
- tb_printf(box_x + box_mar_x, box_y + 1, 0, 0, "%ls", box_in);
- tb_printf(box_x + box_mar_x, box_y + 2, 0, 0, "%ls", box_down);
-
- global.cursor_y = box_y + 1;
-
- // NOTE: wrapping would be better.
- // Scroll the text when it exceeds the prompt's box length
- u32 freesp = box_len - box_pad_x * 2 - box_bwith * 2;
- if (freesp <= 0)
- return;
-
- if (input_len > freesp) {
- wchar_t *text_offs = input + (input_len - freesp);
- tb_printf(box_x + box_mar_x + box_pad_x + box_bwith, box_y + 1, 0, 0, "%ls", text_offs);
- global.cursor_x = box_x + box_pad_x + box_mar_x + box_bwith + freesp;
- } else {
- global.cursor_x = prompt_x;
- tb_printf(box_x + box_mar_x + box_pad_x + box_bwith, box_y + 1, 0, 0, "%ls", input);
- }
- }
-
- if (fds[FDS_SERVER].fd == -1) {
- // show error popup
- popup(TB_RED, TB_BLACK, "Server disconnected.");
- }
- }
-}
-