diff options
| -rw-r--r-- | chat.c | 286 | 
1 files changed, 240 insertions, 46 deletions
| @@ -21,6 +21,7 @@  #include <netinet/in.h>  #include <poll.h>  #include <stdarg.h> +#include <stdbool.h>  #include <stdio.h>  #include <stdlib.h>  #include <string.h> @@ -29,6 +30,9 @@  #include <tls.h>  #include <unistd.h> +#define err(...) do { endwin(); err(__VA_ARGS__); } while (0) +#define errx(...) do { endwin(); errx(__VA_ARGS__); } while (0) +  static void curse(void) {  	setlocale(LC_CTYPE, "");  	initscr(); @@ -37,46 +41,209 @@ static void curse(void) {  	assume_default_colors(-1, -1);  } -static void writeAll(struct tls *client, const char *ptr, size_t len) { +static const int CHAT_LINES = 100; +static struct { +	WINDOW *topic; +	WINDOW *chat; +	WINDOW *input; +} ui; + +static void uiInit(void) { +	ui.topic = newwin(2, COLS, 0, 0); +	mvwhline(ui.topic, 1, 0, ACS_HLINE, COLS); + +	ui.chat = newpad(CHAT_LINES, COLS); +	wsetscrreg(ui.chat, 0, CHAT_LINES - 1); +	scrollok(ui.chat, true); +	wmove(ui.chat, CHAT_LINES - (LINES - 4) - 1, 0); + +	ui.input = newwin(2, COLS, LINES - 2, 0); +	mvwhline(ui.input, 0, 0, ACS_HLINE, COLS); +	wmove(ui.input, 1, 0); +} + +static void uiDraw(void) { +	wnoutrefresh(ui.topic); +	pnoutrefresh( +		ui.chat, +		CHAT_LINES - (LINES - 4), 0, +		2, 0, LINES - 1, COLS - 1 +	); +	wnoutrefresh(ui.input); +	doupdate(); +} + +static void uiTopic(const char *topic) { +	wmove(ui.topic, 0, 0); +	wclrtoeol(ui.topic); +	waddnstr(ui.topic, topic, COLS); +} +static void uiChat(const char *line) { +	waddch(ui.chat, '\n'); +	waddstr(ui.chat, line); +} +static void uiFmt(const char *format, ...) { +	char *buf; +	va_list ap; +	va_start(ap, format); +	vasprintf(&buf, format, ap); +	va_end(ap); +	if (!buf) err(EX_OSERR, "vasprintf"); +	uiChat(buf); +	free(buf); +} + +static struct { +	int sock; +	struct tls *tls; +	bool verbose; +	char *nick; +	char *chan; +} client; + +static void clientWrite(const char *ptr, size_t len) {  	while (len) { -		ssize_t ret = tls_write(client, ptr, len); +		ssize_t ret = tls_write(client.tls, ptr, len);  		if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) continue; -		if (ret < 0) errx(EX_IOERR, "tls_write: %s", tls_error(client)); +		if (ret < 0) errx(EX_IOERR, "tls_write: %s", tls_error(client.tls));  		ptr += ret;  		len -= ret;  	}  } - -static void command(struct tls *client, const char *format, ...) { +static void clientFmt(const char *format, ...) {  	char *buf;  	va_list ap;  	va_start(ap, format);  	int len = vasprintf(&buf, format, ap);  	va_end(ap);  	if (!buf) err(EX_OSERR, "vasprintf"); -	writeAll(client, buf, len); +	if (client.verbose) uiFmt("<<< %.*s", len - 2, buf); +	clientWrite(buf, len);  	free(buf);  } -static void handle(struct tls *client, const char *line) { -	addstr(line); -	addch('\n'); -	refresh(); +typedef void (*Handler)(char *prefix, char *params); + +static char *shift(char **params) { +	char *rest = *params; +	if (!rest) errx(EX_PROTOCOL, "expected param"); +	if (rest[0] == ':') { +		*params = NULL; +		return &rest[1]; +	} +	return strsep(params, " "); +} + +static void handle001(char *prefix, char *params) { +	(void)prefix; (void)params; +	clientFmt("JOIN %s\r\n", client.chan);  } -static void readClient(struct tls *client) { +static void handlePing(char *prefix, char *params) { +	(void)prefix; +	clientFmt("PONG %s\r\n", params); +} + +static void handleJoin(char *prefix, char *params) { +	char *nick = strsep(&prefix, "!"); +	char *chan = shift(¶ms); +	uiFmt("--> %s arrived in %s", nick, chan); +} +static void handlePart(char *prefix, char *params) { +	char *nick = strsep(&prefix, "!"); +	char *chan = shift(¶ms); +	char *reason = shift(¶ms); +	uiFmt("<-- %s left %s, \"%s\"", nick, chan, reason); +} +static void handleQuit(char *prefix, char *params) { +	char *nick = strsep(&prefix, "!"); +	char *reason = shift(¶ms); +	uiFmt("<-- %s left, \"%s\"", nick, reason); +} + +static void handle332(char *prefix, char *params) { +	(void)prefix; +	shift(¶ms); +	char *chan = shift(¶ms); +	char *topic = shift(¶ms); +	uiTopic(topic); +	uiFmt("--- The sign in %s reads, \"%s\"", chan, topic); +} +static void handleTopic(char *prefix, char *params) { +	char *nick = strsep(&prefix, "!"); +	char *chan = shift(¶ms); +	char *topic = shift(¶ms); +	uiTopic(topic); +	uiFmt("--- %s placed a new sign in %s, \"%s\"", nick, chan, topic); +} + +static void handle353(char *prefix, char *params) { +	(void)prefix; +	shift(¶ms); +	shift(¶ms); +	char *chan = shift(¶ms); +	char *names = shift(¶ms); +	// TODO: Clean up names (add commas, remove sigils) +	uiFmt("--- In %s are %s", chan, names); +} + +static void handlePrivmsg(char *prefix, char *params) { +	char *nick = strsep(&prefix, "!"); +	shift(¶ms); +	char *message = shift(¶ms); +	uiFmt("<%s> %s", nick, message); +} +static void handleNotice(char *prefix, char *params) { +	char *nick = strsep(&prefix, "!"); +	shift(¶ms); +	char *message = shift(¶ms); +	uiFmt("-%s- %s", nick, message); +} + +static const struct { +	const char *command; +	Handler handler; +} HANDLERS[] = { +	{ "001", handle001 }, +	{ "332", handle332 }, +	{ "353", handle353 }, +	{ "JOIN", handleJoin }, +	{ "NOTICE", handleNotice }, +	{ "PART", handlePart }, +	{ "PING", handlePing }, +	{ "PRIVMSG", handlePrivmsg }, +	{ "QUIT", handleQuit }, +	{ "TOPIC", handleTopic }, +}; +static const size_t HANDLERS_LEN = sizeof(HANDLERS) / sizeof(HANDLERS[0]); + +static void handle(char *line) { +	char *prefix = NULL; +	if (line[0] == ':') { +		prefix = strsep(&line, " ") + 1; +		if (!line) errx(EX_PROTOCOL, "eol after prefix"); +	} +	char *command = strsep(&line, " "); +	for (size_t i = 0; i < HANDLERS_LEN; ++i) { +		if (strcmp(command, HANDLERS[i].command)) continue; +		HANDLERS[i].handler(prefix, line); +		break; +	} +} + +static void clientRead(void) {  	static char buf[4096];  	static size_t fill; -	ssize_t size = tls_read(client, buf + fill, sizeof(buf) - fill); -	if (size < 0) errx(EX_IOERR, "tls_read: %s", tls_error(client)); +	ssize_t size = tls_read(client.tls, buf + fill, sizeof(buf) - fill); +	if (size < 0) errx(EX_IOERR, "tls_read: %s", tls_error(client.tls));  	fill += size; -	char *end; -	char *line = buf; +	char *end, *line = buf;  	while ((end = strnstr(line, "\r\n", buf + fill - line))) {  		end[0] = '\0'; -		handle(client, line); +		if (client.verbose) uiFmt(">>> %s", line); +		handle(line);  		line = &end[2];  	} @@ -84,14 +251,34 @@ static void readClient(struct tls *client) {  	memmove(buf, line, fill);  } -static void readInput(void) { +static void uiRead(void) { +	static char buf[256]; +	static size_t fill; + +	// TODO: +	int ch = wgetch(ui.input); +	if (ch == '\n') { +		buf[fill] = '\0'; +		char *params; +		asprintf(¶ms, "%s :%s", client.chan, buf); +		if (!params) err(EX_OSERR, "asprintf"); +		clientFmt("PRIVMSG %s\r\n", params); +		handlePrivmsg(client.nick, params); +		free(params); +		fill = 0; +		wmove(ui.input, 1, 0); +		wclrtoeol(ui.input); +	} else { +		buf[fill++] = ch; +		waddch(ui.input, ch); +	}  } -static void webirc(struct tls *client, const char *pass, const char *user) { +static void webirc(const char *pass) {  	const char *ssh = getenv("SSH_CLIENT");  	if (!ssh) return;  	int len = strchrnul(ssh, ' ') - ssh; -	command(client, "WEBIRC %s %s %.*s %.*s\r\n", pass, user, len, ssh, len, ssh); +	clientFmt("WEBIRC %s %s %.*s %.*s\r\n", pass, client.nick, len, ssh, len, ssh);  }  int main(int argc, char *argv[]) { @@ -99,17 +286,16 @@ int main(int argc, char *argv[]) {  	const char *host = NULL;  	const char *port = "6697"; -	const char *join = NULL; -	const char *nick = NULL;  	const char *webPass = NULL;  	int opt; -	while (0 < (opt = getopt(argc, argv, "h:j:n:p:w:"))) { +	while (0 < (opt = getopt(argc, argv, "h:j:n:p:vw:"))) {  		switch (opt) {  			break; case 'h': host = optarg; -			break; case 'j': join = optarg; -			break; case 'n': nick = optarg; +			break; case 'j': client.chan = strdup(optarg); +			break; case 'n': client.nick = strdup(optarg);  			break; case 'p': port = optarg; +			break; case 'v': client.verbose = true;  			break; case 'w': webPass = optarg;  			break; default:  return EX_USAGE;  		} @@ -118,23 +304,30 @@ int main(int argc, char *argv[]) {  	curse();  	char hostBuf[64] = {0}; -	char joinBuf[64] = {0}; -	char nickBuf[16] = {0};  	if (!host) {  		addstr("Host: ");  		getnstr(hostBuf, sizeof(hostBuf) - 1);  		host = hostBuf;  	} -	if (!join) { +	if (!client.chan) { +		char buf[64] = {0};  		addstr("Join: "); -		getnstr(joinBuf, sizeof(joinBuf) - 1); -		join = joinBuf; +		getnstr(buf, sizeof(buf) - 1); +		client.chan = strdup(buf);  	} -	if (!nick) { -		addstr("Nick: "); -		getnstr(nickBuf, sizeof(nickBuf) - 1); -		nick = nickBuf; +	if (!client.nick) { +		char buf[16] = {0}; +		addstr("Name: "); +		getnstr(buf, sizeof(buf) - 1); +		client.nick = strdup(buf);  	} +	erase(); +	cbreak(); +	noecho(); + +	uiInit(); +	uiChat("=== Traveling..."); +	uiDraw();  	struct addrinfo *ai;  	struct addrinfo hints = { @@ -145,35 +338,36 @@ int main(int argc, char *argv[]) {  	error = getaddrinfo(host, port, &hints, &ai);  	if (error) errx(EX_NOHOST, "getaddrinfo: %s", gai_strerror(error)); -	int sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); -	if (sock < 0) err(EX_OSERR, "socket"); +	client.sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); +	if (client.sock < 0) err(EX_OSERR, "socket"); -	error = connect(sock, ai->ai_addr, ai->ai_addrlen); +	error = connect(client.sock, ai->ai_addr, ai->ai_addrlen);  	if (error) err(EX_UNAVAILABLE, "connect");  	freeaddrinfo(ai); -	struct tls *client = tls_client(); -	if (!client) errx(EX_OSERR, "tls_client"); +	client.tls = tls_client(); +	if (!client.tls) errx(EX_OSERR, "tls_client");  	struct tls_config *config = tls_config_new(); -	error = tls_configure(client, config); +	error = tls_configure(client.tls, config);  	if (error) errx(EX_OSERR, "tls_configure");  	tls_config_free(config); -	error = tls_connect_socket(client, sock, host); +	error = tls_connect_socket(client.tls, client.sock, host);  	if (error) err(EX_PROTOCOL, "tls_connect"); -	if (webPass) webirc(client, webPass, nick); -	command(client, "NICK %s\r\n", nick); -	command(client, "USER %s x x :%s\r\n", nick, nick); +	if (webPass) webirc(webPass); +	clientFmt("NICK %s\r\n", client.nick); +	clientFmt("USER %s x x :%s\r\n", client.nick, client.nick);  	struct pollfd fds[2] = {  		{ .fd = STDIN_FILENO, .events = POLLIN }, -		{ .fd = sock, .events = POLLIN }, +		{ .fd = client.sock,  .events = POLLIN },  	};  	while (0 < poll(fds, 2, -1)) { -		if (fds[0].revents) readInput(); -		if (fds[1].revents) readClient(client); +		if (fds[0].revents) uiRead(); +		if (fds[1].revents) clientRead(); +		uiDraw();  	}  	err(EX_IOERR, "poll");  } | 
