357 lines
8.2 KiB
C
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();
|
|
}
|