/* 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 #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_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 SIGWINCH */ #define C_R 18 /* hah not going to implement that */ #define C_T 20 /* scroll up */ #define C_N 14 /* scroll down */ #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 decoration(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(); decoration(); g_hardrefresh = 1; signal(SIGWINCH, init_screen); } void stop(void) { int sockfd = g_sockfd; endwin(); printf("stopping now.\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 oldy, oldx; getyx(stdscr, oldy, oldx); move(y, 0); 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, * off = recvbuf; size_t sendminlen; size_t sendlen = sendminlen, recvlen = 1, offlen = (g_y - 4) * 255; time_t t; struct tm * tm; useconds_t frame = 30; useconds_t interval = 1000000 / frame; int32_t ct = 0; /* int lc, off; */ int hardrefresh = 0, inputrefresh = 1; int ch; int ret; sendminlen = TIMESTRMAX; memcpy(sendbuf + sendminlen, name, strlen(name)); sendminlen += strlen(name); memcpy(sendbuf + sendminlen, "> ", 2); sendminlen += 2; sendlen = sendminlen; decoration(); mvaddnstr(g_y - 1, 0, sendbuf, sendlen); while (1) { nextloop: hardrefresh |= g_hardrefresh; /* update */ if (ct % (frame) == 0 || inputrefresh) { UPDATE_TIME(); mvaddnstr(g_y - 1, 0, sendbuf, sendlen); } /* recv */ if (ct % (frame * 2) == 0) { ret = recv(sockfd, recvbuf + recvlen - 1, RECVMAX - recvlen + 1, MSG_DONTWAIT); mvprintw(g_y - 2, 150, "ct:%d:%d:called", ct, ret); if (ret > -1) { recvlen += ret; mvprintw(g_y - 2, 50, "rendstat ct:%d:%d:%ld", ct, ret, recvlen); if (ret > 0 && recvbuf[recvlen-1] == '\n') { mvprintw(g_y - 2, 0, "newline at %ld", recvlen-1); } mvprintw(g_y - 2, 130, "ct:%d:rendered", ct); hardrefresh = 1; } } if (hardrefresh) { mvprintw(g_y - 2, 100, "ct:%d:REFRESH INVOKED", ct); mvaddnstr(1, 0, off, offlen); hardrefresh = 0; g_hardrefresh = 0; } /* send */ while ((ch = getch()) != -1) { if (ch == '\n') { if (sendlen == sendminlen) { continue; } if (sendlen + 1 < SENDMAX) { sendbuf[sendlen++] = '\n'; } if (send(sockfd, sendbuf, sendlen, 0) > 0) { memcpy(recvbuf + recvlen - 1, sendbuf, (sendlen + recvlen - 1 < RECVMAX) * sendlen); recvlen += sendlen; hardrefresh = 1; } else { mvprintw(g_y - 2, 0, "message failed: %s", strerror(errno)); } mvaddch(g_y - 1, sendminlen, ' '); inputrefresh = 1; clearline(g_y - 1); sendlen = sendminlen; } else if ((ch > 31 && ch < 127)) { if (sendlen + 1 < SENDMAX) { sendbuf[sendlen++] = ch; } mvaddnstr(g_y - 1, 0, sendbuf, sendlen); } else if (ch == BACKSPACE || ch == C_H) { inputrefresh = 1; 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); goto nextloop; } else if (ch == C_T) { while (*off != '\n') { --off; } if (*off == '\n') { ++off; } hardrefresh = 1; } else if (ch == C_N) { while (*off != '\n') { ++off; } if (*off == '\n') { ++off; } hardrefresh = 1; } else if (ch == C_W) { while (sendlen > sendminlen && ispunct(sendbuf[sendlen - 1])) { --sendlen; } while (sendlen > sendminlen && isspace(sendbuf[sendlen - 1])) { --sendlen; } while (sendlen > sendminlen && isalnum(sendbuf[sendlen - 1])) { --sendlen; } inputrefresh = 1; clearline(g_y - 1); goto nextloop; } else if (ch == C_L) { clear(); decoration(); hardrefresh = 1; ct = 0; goto nextloop; } else if (ch == C_D) { exit(0); } } /* frame update */ move(g_y - 1, sendlen); ++ct; refresh(); usleep(interval); } return 0; }