aboutsummaryrefslogtreecommitdiff
path: root/v2/chatty.c
diff options
context:
space:
mode:
Diffstat (limited to 'v2/chatty.c')
-rw-r--r--v2/chatty.c260
1 files changed, 260 insertions, 0 deletions
diff --git a/v2/chatty.c b/v2/chatty.c
new file mode 100644
index 0000000..256cc22
--- /dev/null
+++ b/v2/chatty.c
@@ -0,0 +1,260 @@
+#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 <sys/socket.h>
+
+#define TIMEOUT_POLL 60 * 1000
+// time to reconnect in seconds
+#define TIMEOUT_RECONNECT 1
+
+// must be of AUTHOR_LEN -1
+static char username[AUTHOR_LEN] = "(null)";
+
+enum { FDS_SERVER = 0,
+ FDS_TTY,
+ FDS_RESIZE,
+ FDS_MAX };
+
+// fill str array with char
+void fillstr(u8 *str, u8 ch, u8 len)
+{
+ for (int i = 0; i < len; i++) {
+ str[i] = ch;
+ }
+}
+
+// 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[], u32 input_len)
+{
+ Message *messages = msgsArena->memory;
+ assert(messages != NULL);
+ for (int i = 0; i < (msgsArena->pos / sizeof(Message)); i++) {
+ // Color user's own messages
+ u32 fg = 0;
+ if (strncmp(username, (char *)messages[i].author, AUTHOR_LEN) == 0) {
+ fg = TB_CYAN;
+ } else {
+ fg = TB_WHITE;
+ }
+
+ // TODO: wrap when exceeding prompt size
+ tb_printf(0, i, fg, 0, "%s [%s] %ls", messages[i].timestamp, messages[i].author, messages[i].text);
+ }
+
+ int len = global.width * 80 / 100;
+ wchar_t su[len + 2];
+ wchar_t sd[len + 2];
+ wchar_t lr = L'─', ur = L'╭', rd = L'╮', dr = L'╰', ru = L'╯', ud = L'│';
+ {
+ // top bar for prompt
+ su[0] = ur;
+ for (int i = 1; i < len; i++) {
+ su[i] = lr;
+ }
+ su[len] = rd;
+ su[len + 1] = 0;
+
+ // bottom bar for prompt
+ sd[0] = dr;
+ for (int i = 1; i < len; i++) {
+ sd[i] = lr;
+ }
+ sd[len] = ru;
+ sd[len + 1] = 0;
+ }
+
+ tb_printf(1, global.height - 3, 0, 0, "%ls", su);
+ tb_printf(1, global.height - 2, 0, 0, "%lc", ud);
+ global.cursor_x = 1 + 2 + input_len;
+ global.cursor_y = global.height - 2;
+ tb_printf(1 + 2, global.height - 2, 0, 0, "%ls", input);
+ tb_printf(1 + len, global.height - 2, 0, 0, "%lc", ud);
+ tb_printf(1, global.height - 1, 0, 0, "%ls", sd);
+}
+
+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));
+ assert(err == 0);
+
+ tb_init();
+ bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]);
+ tb_get_fds(&ttyfd, &resizefd);
+
+ struct pollfd fds[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, input_len);
+ tb_present();
+ while (1) {
+ err = poll(fds, FDS_MAX, TIMEOUT_POLL);
+ // ignore resize events because we redraw the whole screen anyways.
+ 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(serverfd, buf, STREAM_LIMIT, 0);
+ assert(nrecv != -1);
+ if (nrecv == 0) {
+ // TODO: Handle disconnection, aka wait for server to reconnect.
+ // Try to reconnect with 1 second timeout
+ // TODO: still listen for events
+ // NOTE: we want to display a popup, but still give the user the chance to do
+ // ctrl+c
+ u8 once = 0;
+ while (1) {
+ err = close(serverfd);
+ assert(err == 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) {
+ bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]);
+ tb_clear();
+ break;
+ }
+ assert(errno == ECONNREFUSED);
+
+ // Popup error message
+ if (!once) {
+ screen_home(msgsArena, input, input_len);
+ tb_hide_cursor();
+ tb_print(global.width / 2 - 10, global.height / 2, TB_RED, TB_BLACK, "Server disconnected!");
+ tb_present();
+ once = 1;
+ }
+
+ sleep(TIMEOUT_RECONNECT);
+ }
+ } 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));
+ }
+ } else if (fds[FDS_TTY].revents & POLLIN) {
+ // got a key event
+ tb_poll_event(&ev);
+
+ u8 exit = 0;
+ switch (ev.key) {
+ case TB_KEY_CTRL_D:
+ case TB_KEY_CTRL_C:
+ exit = 1;
+ break;
+ case TB_KEY_CTRL_M: // send message
+ // 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(serverfd, 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 (exit)
+ break;
+
+ } else if (fds[FDS_RESIZE].revents & POLLIN) {
+ tb_poll_event(&ev);
+ }
+
+ screen_home(msgsArena, input, input_len);
+
+ tb_present();
+ }
+
+ tb_shutdown();
+
+ if (errmsg != NULL)
+ printf("%s\n", errmsg);
+
+ ArenaRelease(msgTextArena);
+ ArenaRelease(msgsArena);
+
+ return 0;
+}