/* moontalk.c - @BAKE cc -O2 -std=gnu99 -Wall -Wextra -Wpedantic -Wno-format-truncation $@ -o $* \ -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600 -lncurses -ltinfo $+ * Written by Emil. * Licensed under the GPLv3 only. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SERV "7ks473deh6ggtwqsvbqdurepv5i6iblpbkx33b6cydon3ajph73sssad.onion" #define PORT "50000" #define NAME "anonymous" #define streq(a,b) (!strcmp(a,b)) #define strneq(a,b,c) (!memcmp(a,b,c)) int g_sockfd; #define g_y LINES #define g_x COLS #define HELP \ "%s [options ...]\n" \ "\n-serv SERVER Sets the server to connect to [default: " SERV "]" \ "\n-port PORT Sets the port [default: " PORT "]" \ "\n-name NAME Sets your display name [default: " NAME "]\n" \ "\nControls\n" \ "\nC-l Refreshes the screen" \ "\nC-w Delete the previous word" \ "\nC-c Close the client" \ "\nUp/Down Scrolls all the way up/Scrolls down by a line\n" /* I know, and I don't care */ #define TAB 9 /* */ #define BACKSPACE 263 /* ^G */ #define C_C 4 /* ... */ #define C_D 4 /* quit */ #define C_A 1 /* BOL */ #define C_B 2 /* BOL */ #define C_E 5 /* EOL */ #define C_H 8 /* BACKSPACE */ #define C_L 12 /* REFRESH */ #define C_U 21 /* CLR TO BOL */ #define C_W 23 /* DELETE PREVWORD */ #define C_L 12 /* Signal full refresh */ /****/ void disconnect(void) { int sockfd = g_sockfd; if (sockfd > -1) { close(sockfd); } g_sockfd = -1; } /* always returns an accessible socket */ int init_connection(char * serv, char * port) { int status, sockfd; struct addrinfo hints, * res; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if ((status = getaddrinfo(serv, port, &hints, &res)) != 0) { perror("init_connection"); exit(1); } if ((sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) { goto error; } if (connect(sockfd, res->ai_addr, res->ai_addrlen)) { goto error; } freeaddrinfo(res); g_sockfd = sockfd; return sockfd; error: if (sockfd > -1) { close(sockfd); } perror("init_connection"); exit(1); __builtin_unreachable(); } // void fillline(WINDOW * w, int y, int xstart, char c) { int i = xstart, x = g_x; for (; i < x; ++i) { mvwaddch(w,y,i,c); } } void clearline(WINDOW * w, int y) { wmove(w, y, 0); wclrtoeol(w); } void sanitize(char * buf, size_t rem) { char * base = buf; buf += rem; while (*buf && buf - base) { if (*buf < ' ' || *buf > '~') { if (*buf != '\n') { *buf = '!'; } } --buf; } } int main (int argc, char ** argv) { char * serv = SERV, * port = PORT, name[32] = NAME; int sockfd; { char * argv0 = argv[0]; while (++argv, --argc) { if (streq(*argv, "-help")) { printf(HELP, argv0); exit(1); } if (argc - 1) { --argc; ++argv; } else { printf("%s: %s requires argument\n", argv0, *argv); return 1; } if (streq(*(argv-1), "-serv")) { serv = *argv; } else if (streq(*(argv-1), "-port")) { port = *argv; } else if (streq(*(argv-1), "-name")) { memset(name, 0, 31); strncpy(name, *argv, 31); } } printf("Connecting to %s:%s as %s\n", serv, port, name); atexit(disconnect); sockfd = init_connection(serv, port); } initscr(); noecho(); keypad(stdscr, ERR); nodelay(stdscr, TRUE); ESCDELAY = 0; curs_set(0); if (has_colors() && can_change_color()) { short bg, fg; /* leaks memory :( */ start_color(); for (bg = 0; bg < 8; ++bg) { for (fg = 0; fg < 8; ++fg) { init_pair(16 + fg + (bg * 8), fg, bg); } } } clear(); #define WINCOUNT 3 WINDOW * w[WINCOUNT]; #define header w[0] #define body w[1] #define input w[2] #define SENDMAX (1 << 8) - 1 #define RECVMAX 17663 /* (1 << 11) */ char raw[SENDMAX + RECVMAX]; char * sendbuf = raw, * recvbuf = raw + SENDMAX, * off = recvbuf; size_t sendminlen, sendlen, recvlen = 0, offlen = recvlen; size_t edit; #define TIMESTR "<%Y/%m/%d %H:%M:%S " #define TIMESTRMAX 21 #define UPDATE_TIME()\ do { \ t = time(NULL); \ tm = gmtime(&t); \ strftime(sendbuf, TIMESTRMAX, TIMESTR, tm); \ sendbuf[TIMESTRMAX - 1] = ' '; \ } while (0) time_t t; struct tm * tm; useconds_t frame = 30; useconds_t interval = 1000000. / frame; int32_t ct; int ch, ret; size_t i; size_t namelen = strlen(name); edit = sendlen = sendminlen = TIMESTRMAX + namelen + 2; /* fill in the name */ memcpy(sendbuf + TIMESTRMAX, name, namelen); memcpy(sendbuf + TIMESTRMAX + namelen, "> ", 2); int inputrefresh, bodyrefresh; hardrefresh: ct = 0; inputrefresh = bodyrefresh = 1; header = newwin(1, g_x, 0, 0); body = newwin(g_y - 4, g_x, 1, 0); input = newwin(3, g_x, g_y - 3, 0); fillline(header, 0, 0, '-'); mvwprintw(header, 0, 2, " moontalk "); fillline(input, 0, 0, '-'); while (1) { /* input */ while ((ch = getch()) != -1) { if (ch == KEY_RESIZE || ch == C_L) { for (i = 0; i < WINCOUNT; ++i) { delwin(w[i]); } endwin(); erase(); refresh(); clear(); flushinp(); goto hardrefresh; } else if ((ch > 31 && ch < 127)) { if (sendlen + 1 < SENDMAX) { memmove(sendbuf + edit + 1, sendbuf + edit, sendlen - edit); sendbuf[edit++] = ch; ++sendlen; } inputrefresh = 1; } else if (ch == '\n') { if (sendlen == sendminlen) { continue; } if (sendlen + 1 < SENDMAX) { sendbuf[sendlen++] = '\n'; } if (send(sockfd, sendbuf, sendlen, 0) > 0) { memcpy(recvbuf + recvlen, sendbuf, (sendlen + recvlen < RECVMAX) * sendlen); recvlen += sendlen; offlen += sendlen; } else { mvwprintw(input, 1, 0, "message failed: %s", strerror(errno)); } bodyrefresh = inputrefresh = 1; edit = sendlen = sendminlen; } else if (ch == BACKSPACE || ch == C_H) { inputrefresh = 1; if (sendlen - 1 >= sendminlen && edit - 1 >= sendminlen) { memmove(sendbuf + edit - 1, sendbuf + edit, sendlen - edit); --sendlen; --edit; } inputrefresh = 1; } else if (ch == KEY_LEFT) { if (edit > sendminlen) { --edit; } } else if (ch == KEY_RIGHT) { if (edit < sendlen) { ++edit; } } else if (ch == KEY_DOWN) { mvwprintw(input, 1, 150, "scroll down %ld", offlen); while ((size_t)(off - recvbuf) < recvlen && *off != '\n') { ++off; } if (*off == '\n') { ++off; } wclear(body); bodyrefresh = 1; } else if (ch == KEY_UP) { mvwprintw(input, 1, 150, "scroll up %ld", offlen); if (off - 2 - recvbuf > 0) { off -= 2; } while (off - recvbuf > 0 && *off != '\n') { --off; } if (*off == '\n') { ++off; } bodyrefresh = 1; } else if (ch == C_W) { i = edit; while (i > sendminlen && isspace(sendbuf[i - 1])) { --i; } while (i > sendminlen && !isspace(sendbuf[i - 1])) { --i; } if (i == edit) { continue; } mvwprintw(input, 1, 200, "diff:%ld", sendlen - edit); /* memmove(sendbuf + i, sendbuf + edit, sendlen - edit); */ /* sendlen -= edit; */ /* edit = i; */ /* mvwprintw(input, 1, 200, "i:%ld:%ld:sendl:%3ld", */ /* i - sendminlen, (sendbuf + edit) - (sendbuf + i), sendlen - sendminlen); */ inputrefresh = 1; } } /* update and rendering */ if (inputrefresh) { clearline(input, 2); mvwaddnstr(input, 2, 0, sendbuf, sendlen); mvwchgat(input, 2, edit, 1, A_REVERSE, 0, NULL); } if (ct % frame == 0) { UPDATE_TIME(); } if (ct % frame == 0 || bodyrefresh) { ret = recv(sockfd, recvbuf + recvlen, RECVMAX - recvlen, MSG_DONTWAIT); if (errno && errno != EAGAIN) { mvwaddstr(input, 1, 0, strerror(errno)); } if (bodyrefresh) { bodyrefresh = 0; if (!(ret > 0)) goto _bodyrefresh; } if (ret > 0) { sanitize(recvbuf + recvlen, ret); if (ret + recvlen < RECVMAX) { recvlen += ret; offlen += ret; } _bodyrefresh: mvwprintw(input, 1, 50, "render ct:%d ret:%d", ct, ret); mvwaddnstr(body, 0,0, off, offlen); } } refresh(); for (i = 0; i < WINCOUNT; ++i) { wnoutrefresh(w[i]); } doupdate(); usleep(interval); ++ct; } endwin(); }