moontalk/client/moontalk-cli.c
2024-02-04 17:46:12 +00:00

169 lines
3.6 KiB
C

/* moontalk.c - @BAKE cc -O2 -std=gnu99 -Wall -Wextra -pedantic $@ -o $* -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 <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 streq(a,b) (!strcmp(a,b))
int g_row, g_col;
void free_screen(void) {
endwin();
}
void init_screen(void) {
initscr();
cbreak();
noecho();
keypad(stdscr, ERR);
nodelay(stdscr, TRUE);
}
void handle_winch(int x) {
(void)x;
struct winsize w;
signal(SIGWINCH, SIG_IGN);
endwin();
init_screen();
refresh();
clear();
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
g_row = w.ws_row;
g_col = w.ws_col;
signal(SIGWINCH, handle_winch);
}
int g_sockfd = -1;
void free_connection(void) {
close(g_sockfd);
g_sockfd = -1;
/* the program should be at an end. If we're reconnecting, then at
the top level (main) we'd free, even though the main's sockfd is now
out-of-date, we can simply ignore that and take the result of socket
init */
}
/* 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; // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
if ((status = getaddrinfo(serv, port, &hints, &res)) != 0) {
fprintf(stderr, "init_connection: %s\n", gai_strerror(status));
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:
perror("init_connection");
exit(1);
__builtin_unreachable();
}
int main (int argc, char ** argv) {
char * argv0 = argv[0];
char * serv = SERV, * port = PORT, * name = "anonymous";
#define PREFIX_MAX 21
char raw[(2 << 9) + (2 << 13)];
char * sendbuf = raw + PREFIX_MAX, * recvbuf = raw + (2 << 9);
size_t sendlen = 0, sendmax = (2 << 9) - PREFIX_MAX,
recvlen = 0, recvmax = 2 << 13;
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")) {
printf("serv: %s\n", *argv);
serv = *argv;
}
else if (streq(*(argv-1), "-port")) {
printf("port: %s\n", *argv);
}
else if (streq(*(argv-1), "-name")) {
printf("name: %s\n", *argv);
}
}
atexit(free_screen);
init_screen();
signal(SIGWINCH, handle_winch);
printw("Connecting to %s:%s as %s\n", serv, port, name);
refresh();
sockfd = init_connection(serv, port);
float interval = 1 / 30;
int ch;
time_t t;
struct tm * tm;
while (1) {
/* send */
ch = getch();
if (ch != -1 && ch != '\n') {
sendbuf[sendlen++] = ch;
} else if (ch == '\n') {
t = time(NULL);
tm = gmtime(&t);
strftime(sendbuf - PREFIX_MAX, PREFIX_MAX, "<%Y/%m/%d %H:%M:%S ", tm);
sendbuf[sendlen++] = '\n';
send(sockfd, sendbuf - PREFIX_MAX, sendlen + PREFIX_MAX, 0);
sendlen = 0;
memset(sendbuf, 0, sendlen);
}
/* recv */
recvlen = recv(sockfd, recvbuf, recvmax, MSG_DONTWAIT);
/* render */
clear();
mvaddnstr(0, 0, recvbuf, recvlen);
mvaddnstr(g_row - 1, 0, sendbuf, sendlen);
refresh();
/* sleep */
sleep(interval);
}
}