moontalk/client/moontalk-cli.c
2024-02-11 01:02:02 +00:00

357 lines
8.2 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <ctype.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 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();
}