diff options
Diffstat (limited to 'v2/chatty.c')
-rw-r--r-- | v2/chatty.c | 490 |
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."); - } - } -} - |