// Client for chatty // initial size for the messages array #define MESSAGES_SIZE 5 // clang-format off #define TB_IMPL #include "termbox2.h" // clang-format on #include "common.h" #include #include #include #include #include #include #include enum { FD_SERVER = 0, FD_TTY, FD_RESIZE, FD_MAX }; // offset of the input prompt int curs_offs_x = 2; int prompt_offs_y = 3; // filedescriptor for server static int serverfd; // Input message to be send Message input = { .author = USERNAME, .timestamp = {0}, .text_len = 0, }; // current amount of messages int nmessages = 0; // length of messages array int messages_size = MESSAGES_SIZE; // All messages sent and received in order Message messages[MESSAGES_SIZE] = {0}; // incremented each time a new message is printed int msg_y = 0; // Cleans up resources, should called before exiting. void cleanup(void); // Displays an error message msg, followed by the errno variable and exits exeuction. void err_exit(const char *msg); // Display the welcome ui screen containing the prompt and messages array. void scren_welcome(void); // Append msg to the messages array. Returns -1 if there was no space in the messages array // otherwise returns 0 on success. u8 messages_add(Message *msg); void cleanup(void) { tb_shutdown(); if (serverfd) if (close(serverfd)) writef("Error while closing server socket. errno: %d\n", errno); } // panic void err_exit(const char *msg) { cleanup(); writef("%s errno: %d\n", msg, errno); _exit(1); } void screen_welcome(void) { tb_set_cursor(curs_offs_x, global.height - prompt_offs_y); tb_print(0, global.height - prompt_offs_y, 0, 0, ">"); // if there is not enough space to fit all messages, skip the n first messages of the array. int skip = 0; int lines_available = global.height - prompt_offs_y - 1; // pad by 1 from prompt if (lines_available - nmessages < 0) skip = nmessages - lines_available; for (msg_y = skip; msg_y < nmessages; msg_y++) { tb_printf(0, msg_y - skip, 0, 0, "%s [%s]: %s", messages[msg_y].timestamp, messages[msg_y].author, messages[msg_y].text); } } u8 messages_add(Message *msg) { if (nmessages == messages_size) { return -1; } memcpy(messages[nmessages].author, msg->author, MESSAGE_AUTHOR_LEN); memcpy(messages[nmessages].timestamp, msg->timestamp, MESSAGE_TIMESTAMP_LEN); messages[nmessages].text_len = msg->text_len; messages[nmessages].text = msg->text; nmessages++; msg_y++; return 0; } int main(void) { // current event struct tb_event ev; // time for a new entered message time_t now; // localtime of new sent message struct tm *ltime; char buf[MESSAGE_MAX]; input.text = buf; int serverfd, ttyfd, resizefd; Message msg_recv = {0}; const struct sockaddr_in address = { AF_INET, htons(PORT), {0}, }; tb_init(); bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); screen_welcome(); tb_present(); tb_get_fds(&ttyfd, &resizefd); serverfd = socket(AF_INET, SOCK_STREAM, 0); struct pollfd fds[FD_MAX] = { {serverfd, POLLIN, 0}, // FD_SERVER { ttyfd, POLLIN, 0}, // FD_TTY {resizefd, POLLIN, 0}, // FD_RESIZE }; if (connect(serverfd, (struct sockaddr *)&address, sizeof(address))) err_exit("Error while connecting."); for (;;) { if (poll(fds, FD_MAX, 50000) == -1) { // check if it was a resize event that interrupted the system call if (errno == EINTR) { tb_peek_event(&ev, 80); if (ev.type != TB_EVENT_RESIZE) err_exit("Error while polling."); else { tb_clear(); screen_welcome(); } } } if (fds[FD_TTY].revents & POLLIN) { tb_poll_event(&ev); switch (ev.key) { // exit case TB_KEY_CTRL_C: case TB_KEY_CTRL_D: case TB_KEY_ESC: goto exit_loop; // remove line till cursor case TB_KEY_CTRL_U: while (global.cursor_x > curs_offs_x) { global.cursor_x--; tb_print(global.cursor_x, global.cursor_y, 0, 0, " "); } tb_set_cursor(curs_offs_x, global.cursor_y); input.text_len = 0; break; // send message case TB_KEY_CTRL_M: if (input.text_len <= 0) break; while (global.cursor_x > curs_offs_x) { global.cursor_x--; tb_print(global.cursor_x, global.cursor_y, 0, 0, " "); } tb_set_cursor(curs_offs_x, global.cursor_y); // zero terminate input.text[input.text_len] = 0; // print new message time(&now); ltime = localtime(&now); strftime((char*)input.timestamp, sizeof(input.timestamp), "%H:%M:%S", ltime); messages_add(&input); if (message_send(&input, serverfd) == -1) err_exit("Error while sending message."); // reset buffer input.text_len = 0; // update the screen // NOTE: kind of wasteful cause we should only display new message tb_clear(); screen_welcome(); break; // remove word case TB_KEY_CTRL_W: // Delete consecutive space while (input.text[input.text_len - 1] == ' ' && global.cursor_x > curs_offs_x) { global.cursor_x--; input.text_len--; tb_print(global.cursor_x, global.cursor_y, 0, 0, " "); } // Delete until next non-space while (input.text[input.text_len - 1] != ' ' && global.cursor_x > curs_offs_x) { global.cursor_x--; input.text_len--; tb_print(global.cursor_x, global.cursor_y, 0, 0, " "); } input.text[input.text_len] = 0; break; } // append pressed character to input.text // TODO: wrap instead, allocate more ram for the message instead if (ev.ch > 0 && input.text_len < MESSAGE_MAX && input.text_len < global.width - 3 - 1) { tb_printf(global.cursor_x, global.cursor_y, 0, 0, "%c", ev.ch); global.cursor_x++; input.text[input.text_len++] = ev.ch; } } else if (fds[FD_SERVER].revents & POLLIN) { int nrecv = message_receive(&msg_recv, serverfd); if (nrecv == 0) { // Server closed // TODO: error message like (disconnected) break; } else if (nrecv == -1) { err_exit("Error while receiveiving from server."); } messages_add(&msg_recv); tb_clear(); screen_welcome(); } else if (fds[FD_RESIZE].revents & POLLIN) { tb_poll_event(&ev); if (ev.type == TB_EVENT_RESIZE) { tb_clear(); screen_welcome(); } } tb_present(); } exit_loop:; cleanup(); return 0; }