/* 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. * * TODO Add proper editing facilities */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SERV "7ks473deh6ggtwqsvbqdurepv5i6iblpbkx33b6cydon3ajph73sssad.onion" #define PORT "50000" #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_U 21 /* CLR TO BOL */ #define C_H 8 /* BACKSPACE */ #define C_W 23 /* DELETE PREVWORD */ #define C_L 12 /* Signal SIGWINCH */ #define C_R 18 /* hah not going to implement that */ #define UP 258 #define DOWN 259 #define LEFT 260 #define RIGHT 261 #define TIMESTR "<%Y/%m/%d %H:%M:%S " #define TIMESTRMAX 21 #define SENDMAX (1 << 8) - 1 #define RECVMAX 17663 /* (1 << 11) */ #define streq(a,b) (!strcmp(a,b)) #define strneq(a,b,c) (!memcmp(a,b,c)) int g_y, g_x; int g_sockfd = -1; void fillline(int y, int xstart, char c) { int i = xstart, x = g_x; for (; i < x; ++i) { mvaddch(y,i,c); } } #define TITLE(str) do { mvprintw(0,0,str); fillline(0,strlen(str),'-'); } while (0) void hardrefresh(void) { mvprintw(g_y - 2, 0, "max screen %d; max ours %d", (g_y - 3) * SENDMAX, RECVMAX); TITLE("-- MOONTALK "); fillline(g_y - 3, 0, '-'); } #undef TITLE int g_hardrefresh = 0; void init_screen(int x) { (void)x; signal(SIGWINCH, SIG_IGN); endwin(); struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); g_y = w.ws_row; g_x = w.ws_col; initscr(); raw(); noecho(); keypad(stdscr, ERR); nodelay(stdscr, TRUE); ESCDELAY = 0; clear(); hardrefresh(); g_hardrefresh = 1; signal(SIGWINCH, init_screen); } void stop(void) { int sockfd = g_sockfd; endwin(); printf("stopping now. maybe you forgot torify?\n"); 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(); } #if 0 int getanonval(char * buf, size_t len) { return 0; } #endif int countlines(char * buf, size_t len) { size_t i = 0; for (buf += len - 1; *buf; --buf) { i += (*buf == '\n'); } return i; } size_t getoff(char * buf, size_t len, size_t count, char c) { size_t i; for (i = 0; count && i < len; ++i) { count -= (buf[i] == c) * 1; ++buf; } return i; } size_t lastline(char * buf, size_t len) { size_t ret; char * start = buf; for (buf += len - 1; *buf; --buf) { ret = (*buf == '\n') ? buf - start : 0; if (ret) { return ret; } } return 0; } void clearline(int y) { int x = g_x; int oldy, oldx; getyx(stdscr, oldy, oldx); move(y, x); clrtoeol(); move(oldy, oldx); } #define UPDATE_TIME() do { \ t = time(NULL); \ tm = gmtime(&t); \ strftime(sendbuf, TIMESTRMAX, TIMESTR, tm); \ sendbuf[TIMESTRMAX - 1] = ' '; } while (0) int main (int argc, char ** argv) { char * argv0 = argv[0]; char * serv = SERV, * port = PORT, name[32] = "anonymous"; int sockfd; while (++argv, --argc) { if (streq(*argv, "-help")) { printf("%s: HELP\n", argv0); return 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); } } init_screen(-1); move(1,0); printw("Connecting to %s:%s as %s\n", serv, port, name); printw("g_y: %d; g_x: %d\n", g_y, g_x); refresh(); clear(); atexit(stop); sockfd = init_connection(serv, port); char raw[SENDMAX + RECVMAX]; char * sendbuf = raw, * recvbuf = raw + SENDMAX; size_t sendminlen; size_t sendlen = sendminlen, recvlen = 1; time_t t; struct tm * tm; useconds_t frame = 25; useconds_t interval = 1000000 / frame; int32_t ct = 0; int lc, off; int ch; int ret; sendminlen = TIMESTRMAX; UPDATE_TIME(); memcpy(sendbuf + sendminlen, name, strlen(name)); sendminlen += strlen(name); memcpy(sendbuf + sendminlen, "> ", 2); sendminlen += 2; sendlen = sendminlen; hardrefresh(); mvaddnstr(g_y - 1, 0, sendbuf, sendlen); while (1) { /* update */ if (ct % (frame) == 0) { UPDATE_TIME(); mvaddnstr(g_y - 1, 0, sendbuf, sendlen); } if (ct % (frame * 2) == 0) { ret = recv(sockfd, recvbuf + recvlen, RECVMAX - recvlen, MSG_DONTWAIT); if (ret > 0 || g_hardrefresh) { recvlen += ret; lc = countlines(recvbuf, recvlen); off = lc - g_y - 4; off = getoff(recvbuf, recvlen, (off > 0) * off, '\n'); mvaddnstr(1, 0, recvbuf + off, recvlen - off); g_hardrefresh = 0; } } /* send */ while ((ch = getch()) != -1) { if (ch == '\n') { if (sendlen == sendminlen) { continue; } UPDATE_TIME(); sendbuf[sendlen++] = '\n'; /* terminator */ send(sockfd, sendbuf, sendlen, 0); memcpy(recvbuf + recvlen, sendbuf, (sendlen + recvlen < RECVMAX) * sendlen); recvlen += sendlen; mvaddnstr(1, 0, recvbuf, recvlen); mvaddch(g_y - 1, sendminlen, ' '); clearline(g_y - 1); sendlen = sendminlen; } else if (ch == C_D) { exit(0); } else if (ch == BACKSPACE) { clearline(g_y - 1); if (sendlen - 1 >= sendminlen) { mvaddch(g_y - 1, --sendlen, ' '); } mvaddnstr(g_y - 1, 0, sendbuf, sendlen); move(g_y - 1, sendlen); } else if (ch > 31 && ch < 127) { if (sendlen + 1 < SENDMAX) { sendbuf[sendlen++] = ch; } mvaddnstr(g_y - 1, 0, sendbuf, sendlen); } } move(g_y - 1, sendlen); ++ct; refresh(); usleep(interval); } return 0; }