moontalk/client/moontalk-cli.c
2024-02-06 03:17:23 +00:00

304 lines
6.4 KiB
C

/* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ncurses.h>
#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;
}