summary refs log tree commit diff
diff options
context:
space:
mode:
authorC. McEnroe2020-02-01 19:37:48 -0500
committerC. McEnroe2020-02-01 19:37:55 -0500
commite5363bcae0f726455fb4198cd21d46721ad5e39a (patch)
treea4f191af1fe34b16663247fad0784476311dde8c
parente289ff6b18e643eea4c72e04f7c0dc6ff768a335 (diff)
Implement the beginnings of UI
It takes so much code to do anything in curses...
-rw-r--r--Makefile3
-rw-r--r--chat.c8
-rw-r--r--chat.h22
-rw-r--r--handle.c21
-rw-r--r--irc.c6
-rw-r--r--ui.c174
6 files changed, 228 insertions, 6 deletions
diff --git a/Makefile b/Makefile
index 999b491..4af59ee 100644
--- a/Makefile
+++ b/Makefile
@@ -3,12 +3,13 @@ CFLAGS += -I${LIBRESSL_PREFIX}/include
 LDFLAGS += -L${LIBRESSL_PREFIX}/lib
 
 CFLAGS += -std=c11 -Wall -Wextra -Wpedantic
-LDLIBS = -lcrypto -ltls
+LDLIBS = -lcurses -lcrypto -ltls
 
 OBJS += chat.o
 OBJS += handle.o
 OBJS += irc.o
 OBJS += term.o
+OBJS += ui.o
 
 catgirl: ${OBJS}
 	${CC} ${LDFLAGS} ${OBJS} ${LDLIBS} -o $@
diff --git a/chat.c b/chat.c
index 462faa0..47227d5 100644
--- a/chat.c
+++ b/chat.c
@@ -15,6 +15,7 @@
  */
 
 #include <err.h>
+#include <locale.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -33,6 +34,8 @@ size_t idNext = Network + 1;
 struct Self self;
 
 int main(int argc, char *argv[]) {
+	setlocale(LC_CTYPE, "");
+
 	bool insecure = false;
 	const char *host = NULL;
 	const char *port = "6697";
@@ -71,6 +74,10 @@ int main(int argc, char *argv[]) {
 	if (!real) real = nick;
 
 	ircConfig(insecure, cert, priv);
+
+	uiInit();
+	uiFormat(Network, Cold, NULL, "Traveling...");
+	uiDraw();
 	
 	int irc = ircConnect(host, port);
 	if (pass) ircFormat("PASS :%s\r\n", pass);
@@ -80,6 +87,7 @@ int main(int argc, char *argv[]) {
 	ircFormat("USER %s 0 * :%s\r\n", user, real);
 
 	for (;;) {
+		uiDraw();
 		ircRecv();
 	}
 }
diff --git a/chat.h b/chat.h
index 93014ef..be3952c 100644
--- a/chat.h
+++ b/chat.h
@@ -82,6 +82,18 @@ struct Message {
 	char *params[ParamCap];
 };
 
+#define B "\2"
+#define C "\3"
+#define R "\17"
+#define V "\26"
+#define I "\35"
+#define U "\37"
+enum Color {
+	White, Black, Blue, Green, Red, Brown, Magenta, Orange,
+	Yellow, LightGreen, Cyan, LightCyan, LightBlue, Pink, Gray, LightGray,
+	Default = 99,
+};
+
 void ircConfig(bool insecure, const char *cert, const char *priv);
 int ircConnect(const char *host, const char *port);
 void ircRecv(void);
@@ -91,6 +103,14 @@ void ircFormat(const char *format, ...)
 
 void handle(struct Message msg);
 
+enum Heat { Cold, Warm, Hot };
+void uiInit(void);
+void uiDraw(void);
+void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str);
+void uiFormat(
+	size_t id, enum Heat heat, const struct tm *time, const char *format, ...
+) __attribute__((format(printf, 4, 5)));
+
 enum TermMode {
 	TermFocus,
 	TermPaste,
@@ -109,11 +129,9 @@ void termMode(enum TermMode mode, bool set);
 enum TermEvent termEvent(char ch);
 
 #define BASE64_SIZE(len) (1 + ((len) + 2) / 3 * 4)
-
 static const char Base64[64] = {
 	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
 };
-
 static inline void base64(char *dst, const byte *src, size_t len) {
 	size_t i = 0;
 	while (len > 2) {
diff --git a/handle.c b/handle.c
index 96bd2a2..b2b2b8d 100644
--- a/handle.c
+++ b/handle.c
@@ -136,11 +136,32 @@ static void handleReplyWelcome(struct Message *msg) {
 	if (self.join) ircFormat("JOIN :%s\r\n", self.join);
 }
 
+static void handleReplyISupport(struct Message *msg) {
+	// TODO: Extract CHANTYPES and PREFIX for future use.
+	for (size_t i = 1; i < ParamCap; ++i) {
+		if (!msg->params[i]) break;
+		char *key = strsep(&msg->params[i], "=");
+		if (!msg->params[i]) continue;
+		if (!strcmp(key, "NETWORK")) {
+			uiFormat(Network, Cold, NULL, "You arrive in %s", msg->params[i]);
+		}
+	}
+}
+
+static void handleReplyMOTD(struct Message *msg) {
+	require(msg, false, 2);
+	char *line = msg->params[1];
+	if (!strncmp(line, "- ", 2)) line += 2;
+	uiFormat(Network, Cold, NULL, "%s", line);
+}
+
 static const struct Handler {
 	const char *cmd;
 	Handler *fn;
 } Handlers[] = {
 	{ "001", handleReplyWelcome },
+	{ "005", handleReplyISupport },
+	{ "372", handleReplyMOTD },
 	{ "900", handleReplyLoggedIn },
 	{ "904", handleErrorSASLFail },
 	{ "905", handleErrorSASLFail },
diff --git a/irc.c b/irc.c
index cf8aab7..f07fcc8 100644
--- a/irc.c
+++ b/irc.c
@@ -104,10 +104,10 @@ int ircConnect(const char *host, const char *port) {
 static void debug(char dir, const char *line) {
 	if (!self.debug) return;
 	size_t len = strcspn(line, "\r\n");
-	/*uiFormat(
-		Debug, Cold, NULL, "\3%02d%c%c\3 %.*s",
+	uiFormat(
+		Debug, Cold, NULL, C"%d%c%c"C" %.*s",
 		Gray, dir, dir, (int)len, line
-	);*/
+	);
 	if (!isatty(STDERR_FILENO)) {
 		fprintf(stderr, "%c%c %.*s\n", dir, dir, (int)len, line);
 	}
diff --git a/ui.c b/ui.c
new file mode 100644
index 0000000..0295c8d
--- /dev/null
+++ b/ui.c
@@ -0,0 +1,174 @@
+/* Copyright (C) 2020  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <curses.h>
+#include <err.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <time.h>
+
+#include "chat.h"
+
+#ifndef A_ITALIC
+#define A_ITALIC A_UNDERLINE
+#endif
+
+#define BOTTOM (LINES - 1)
+#define RIGHT (COLS - 1)
+#define WINDOW_LINES (LINES - 2)
+
+static short colorPairs;
+
+static void colorInit(void) {
+	start_color();
+	use_default_colors();
+	for (short pair = 0; pair < 16; ++pair) {
+		init_pair(1 + pair, pair % COLORS, -1);
+	}
+	colorPairs = 17;
+}
+
+static attr_t colorAttr(short fg) {
+	return (fg >= COLORS ? A_BOLD : A_NORMAL);
+}
+
+static short colorPair(short fg, short bg) {
+	if (bg == -1) return 1 + fg;
+	for (short pair = 17; pair < colorPairs; ++pair) {
+		short f, b;
+		pair_content(pair, &f, &b);
+		if (f == fg && b == bg) return pair;
+	}
+	init_pair(colorPairs, fg, bg);
+	return colorPairs++;
+}
+
+enum {
+	InputCols = 512,
+	PadLines = 512,
+};
+
+static WINDOW *status;
+static WINDOW *input;
+
+struct Window {
+	size_t id;
+	WINDOW *pad;
+	enum Heat heat;
+	int unread;
+	int scroll;
+	bool mark;
+	struct Window *prev;
+	struct Window *next;
+};
+
+static struct {
+	struct Window *active;
+	struct Window *other;
+	struct Window *head;
+	struct Window *tail;
+} windows;
+
+static void windowAdd(struct Window *window) {
+	if (windows.tail) windows.tail->next = window;
+	window->prev = windows.tail;
+	window->next = NULL;
+	windows.tail = window;
+	if (!windows.head) windows.head = window;
+}
+
+static void windowRemove(struct Window *window) {
+	if (window->prev) window->prev->next = window->next;
+	if (window->next) window->next->prev = window->prev;
+	if (windows.head == window) windows.head = window->next;
+	if (windows.tail == window) windows.tail = window->prev;
+}
+
+static struct Window *windowFor(size_t id) {
+	struct Window *window;
+	for (window = windows.head; window; window = window->next) {
+		if (window->id == id) return window;
+	}
+	window = malloc(sizeof(*window));
+	if (!window) err(EX_OSERR, "malloc");
+	window->id = id;
+	window->pad = newpad(PadLines, COLS);
+	wsetscrreg(window->pad, 0, PadLines - 1);
+	scrollok(window->pad, true);
+	wmove(window->pad, PadLines - 1, 0);
+	window->heat = Cold;
+	window->unread = 0;
+	window->scroll = PadLines;
+	window->mark = true;
+	windowAdd(window);
+	return window;
+}
+
+void uiInit(void) {
+	initscr();
+	cbreak();
+	noecho();
+	termInit();
+	termNoFlow();
+	def_prog_mode();
+	colorInit();
+	status = newwin(1, COLS, 0, 0);
+	input = newpad(1, InputCols);
+	keypad(input, true);
+	nodelay(input, true);
+	windows.active = windowFor(Network);
+}
+
+void uiDraw(void) {
+	wnoutrefresh(status);
+	pnoutrefresh(
+		windows.active->pad,
+		windows.active->scroll - WINDOW_LINES, 0,
+		1, 0,
+		BOTTOM - 1, RIGHT
+	);
+	// TODO: Input scrolling.
+	pnoutrefresh(
+		input,
+		0, 0,
+		BOTTOM, 0,
+		BOTTOM, RIGHT
+	);
+	doupdate();
+}
+
+void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str) {
+	(void)time;
+	struct Window *window = windowFor(id);
+	waddch(window->pad, '\n');
+	waddstr(window->pad, str);
+}
+
+void uiFormat(
+	size_t id, enum Heat heat, const struct tm *time, const char *format, ...
+) {
+	char buf[1024];
+	va_list ap;
+	va_start(ap, format);
+	int len = vsnprintf(buf, sizeof(buf), format, ap);
+	va_end(ap);
+	assert((size_t)len < sizeof(buf));
+	uiWrite(id, heat, time, buf);
+}