about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2018-08-06 14:19:52 -0400
committerJune McEnroe <june@causal.agency>2018-08-06 14:19:52 -0400
commitd6fb797b11aa6dd031032faf425be3ce5a69661d (patch)
treee348e4ec6ed53a9700219336fd6a3e3733093a56
parentRename line editing functions (diff)
downloadcatgirl-d6fb797b11aa6dd031032faf425be3ce5a69661d.tar.gz
catgirl-d6fb797b11aa6dd031032faf425be3ce5a69661d.zip
Use wchar_t strings for all of UI
vaswprintf is a nightmare.
-rw-r--r--Makefile2
-rw-r--r--README1
-rw-r--r--chat.c2
-rw-r--r--chat.h13
-rw-r--r--handle.c32
-rw-r--r--input.c16
-rw-r--r--irc.c4
-rw-r--r--pls.c67
-rw-r--r--ui.c45
9 files changed, 126 insertions, 56 deletions
diff --git a/Makefile b/Makefile
index 26c70a1..76e130b 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@ CFLAGS += -Wall -Wextra -Wpedantic
 CFLAGS += -I/usr/local/include
 LDFLAGS += -L/usr/local/lib
 LDLIBS = -lcursesw -ltls
-OBJS = chat.o handle.o input.o irc.o ui.o
+OBJS = chat.o handle.o input.o irc.o pls.o ui.o
 
 all: tags chat
 
diff --git a/README b/README
index 89edd73..0d39bf2 100644
--- a/README
+++ b/README
@@ -8,3 +8,4 @@ This software targets FreeBSD and requires LibreSSL.
 	irc.c       TLS client connection
 	input.c     Input command handling
 	handle.c    Incoming command handling
+	pls.c       Functions which should not have to be written
diff --git a/chat.c b/chat.c
index 30fe272..ba83907 100644
--- a/chat.c
+++ b/chat.c
@@ -78,7 +78,7 @@ int main(int argc, char *argv[]) {
 	signal(SIGINT, sigint);
 
 	uiInit();
-	uiLog("Traveling...");
+	uiLog(L"Traveling...");
 	uiDraw();
 
 	int sock = ircConnect(host, port, webPass);
diff --git a/chat.h b/chat.h
index 2c9239c..3d3586c 100644
--- a/chat.h
+++ b/chat.h
@@ -16,6 +16,7 @@
 
 #define SOURCE_URL "https://code.causal.agency/june/chat"
 
+#include <stdarg.h>
 #include <stdbool.h>
 #include <stdlib.h>
 #include <wchar.h>
@@ -41,11 +42,15 @@ void uiInit(void);
 void uiHide(void);
 void uiDraw(void);
 void uiRead(void);
-void uiTopic(const char *topic);
-void uiLog(const char *line);
+void uiTopic(const wchar_t *topic);
+void uiTopicStr(const char *topic);
+void uiLog(const wchar_t *line);
 
-__attribute__((format(printf, 1, 2)))
-void uiFmt(const char *format, ...);
+//__attribute__((format(printf, 1, 2)))
+void uiFmt(const wchar_t *format, ...);
 
 void handle(char *line);
 void input(wchar_t *line);
+
+wchar_t *wcssep(wchar_t **stringp, const wchar_t *delim);
+int vaswprintf(wchar_t **ret, const wchar_t *format, va_list ap);
diff --git a/handle.c b/handle.c
index fa13ca8..c602c23 100644
--- a/handle.c
+++ b/handle.c
@@ -58,9 +58,9 @@ static void handle432(char *prefix, char *params) {
 	shift(&params);
 	shift(&params);
 	char *mesg = shift(&params);
-	uiLog("You can't use that name here");
-	uiFmt("Sheriff says, \"%s\"", mesg);
-	uiLog("Type /nick <name> to choose a new one");
+	uiLog(L"You can't use that name here");
+	uiFmt(L"Sheriff says, \"%s\"", mesg);
+	uiLog(L"Type /nick <name> to choose a new one");
 }
 
 static void handle001(char *prefix, char *params) {
@@ -82,7 +82,7 @@ static void handleJoin(char *prefix, char *params) {
 		chat.user = strdup(user);
 	}
 	uiFmt(
-		"\3%d%s\3 arrives in \3%d%s\3",
+		L"\3%d%s\3 arrives in \3%d%s\3",
 		color(user), nick, color(chan), chan
 	);
 }
@@ -93,7 +93,7 @@ static void handlePart(char *prefix, char *params) {
 	char *chan = shift(&params);
 	char *mesg = shift(&params);
 	uiFmt(
-		"\3%d%s\3 leaves \3%d%s\3, \"%s\"",
+		L"\3%d%s\3 leaves \3%d%s\3, \"%s\"",
 		color(user), nick, color(chan), chan, mesg
 	);
 }
@@ -104,7 +104,7 @@ static void handleQuit(char *prefix, char *params) {
 	char *mesg = shift(&params);
 	char *quot = (mesg[0] == '"') ? "" : "\"";
 	uiFmt(
-		"\3%d%s\3 leaves, %s%s%s",
+		L"\3%d%s\3 leaves, %s%s%s",
 		color(user), nick, quot, mesg, quot
 	);
 }
@@ -116,7 +116,7 @@ static void handleKick(char *prefix, char *params) {
 	char *kick = shift(&params);
 	char *mesg = shift(&params);
 	uiFmt(
-		"\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3, \"%s\"",
+		L"\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3, \"%s\"",
 		color(user), nick, color(kick), kick, color(chan), chan, mesg
 	);
 }
@@ -127,10 +127,10 @@ static void handle332(char *prefix, char *params) {
 	char *chan = shift(&params);
 	char *topic = shift(&params);
 	uiFmt(
-		"The sign in \3%d%s\3 reads, \"%s\"",
+		L"The sign in \3%d%s\3 reads, \"%s\"",
 		color(chan), chan, topic
 	);
-	uiTopic(topic);
+	uiTopicStr(topic);
 }
 
 static void handleTopic(char *prefix, char *params) {
@@ -139,10 +139,10 @@ static void handleTopic(char *prefix, char *params) {
 	char *chan = shift(&params);
 	char *topic = shift(&params);
 	uiFmt(
-		"\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"",
+		L"\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"",
 		color(user), nick, color(chan), chan, topic
 	);
-	uiTopic(topic);
+	uiTopicStr(topic);
 }
 
 static void handle366(char *prefix, char *params) {
@@ -176,7 +176,7 @@ static void handle315(char *prefix, char *params) {
 	char *chan = shift(&params);
 	whoLen = 0;
 	uiFmt(
-		"In \3%d%s\3 are %s",
+		L"In \3%d%s\3 are %s",
 		color(chan), chan, whoBuf
 	);
 }
@@ -190,7 +190,7 @@ static void handleNick(char *prefix, char *params) {
 		chat.nick = strdup(next);
 	}
 	uiFmt(
-		"\3%d%s\3 is now known as \3%d%s\3",
+		L"\3%d%s\3 is now known as \3%d%s\3",
 		color(user), prev, color(user), next
 	);
 }
@@ -202,9 +202,9 @@ static void handlePrivmsg(char *prefix, char *params) {
 	char *mesg = shift(&params);
 	if (mesg[0] == '\1') {
 		strsep(&mesg, " ");
-		uiFmt("* \3%d%s\3 %s", color(user), nick, strsep(&mesg, "\1"));
+		uiFmt(L"* \3%d%s\3 %s", color(user), nick, strsep(&mesg, "\1"));
 	} else {
-		uiFmt("<\3%d%s\3> %s", color(user), nick, mesg);
+		uiFmt(L"<\3%d%s\3> %s", color(user), nick, mesg);
 	}
 }
 
@@ -214,7 +214,7 @@ static void handleNotice(char *prefix, char *params) {
 	char *chan = shift(&params);
 	char *mesg = shift(&params);
 	if (strcmp(chat.chan, chan)) return;
-	uiFmt("-\3%d%s\3- %s", color(user), nick, mesg);
+	uiFmt(L"-\3%d%s\3- %s", color(user), nick, mesg);
 }
 
 static const struct {
diff --git a/input.c b/input.c
index 46bbc56..a53b0a2 100644
--- a/input.c
+++ b/input.c
@@ -23,18 +23,6 @@
 
 #include "chat.h"
 
-static wchar_t *wcssep(wchar_t **stringp, const wchar_t *delim) {
-	wchar_t *orig = *stringp;
-	if (!orig) return NULL;
-	size_t i = wcscspn(orig, delim);
-	*stringp = NULL;
-	if (orig[i]) {
-		orig[i] = '\0';
-		*stringp = &orig[i + 1];
-	}
-	return orig;
-}
-
 static void privmsg(bool action, const wchar_t *mesg) {
 	char *line;
 	int send;
@@ -60,7 +48,7 @@ static void inputNick(wchar_t *params) {
 	if (nick) {
 		ircFmt("NICK %ls\r\n", nick);
 	} else {
-		uiLog("/nick requires a name");
+		uiLog(L"/nick requires a name");
 	}
 }
 
@@ -110,5 +98,5 @@ void input(wchar_t *input) {
 		COMMANDS[i].handler(input);
 		return;
 	}
-	uiFmt("/%ls isn't a recognized command", command);
+	uiFmt(L"/%ls isn't a recognized command", command);
 }
diff --git a/irc.c b/irc.c
index 02a9f64..1692a73 100644
--- a/irc.c
+++ b/irc.c
@@ -99,7 +99,7 @@ void ircFmt(const char *format, ...) {
 	int len = vasprintf(&buf, format, ap);
 	va_end(ap);
 	if (!buf) err(EX_OSERR, "vasprintf");
-	if (chat.verbose) uiFmt("<<< %.*s", len - 2, buf);
+	if (chat.verbose) uiFmt(L"<<< %.*s", len - 2, buf);
 	ircWrite(buf, len);
 	free(buf);
 }
@@ -119,7 +119,7 @@ void ircRead(void) {
 	char *crlf, *line = buf;
 	while ((crlf = strnstr(line, "\r\n", &buf[len] - line))) {
 		crlf[0] = '\0';
-		if (chat.verbose) uiFmt(">>> %s", line);
+		if (chat.verbose) uiFmt(L">>> %s", line);
 		handle(line);
 		line = &crlf[2];
 	}
diff --git a/pls.c b/pls.c
new file mode 100644
index 0000000..bd40dbd
--- /dev/null
+++ b/pls.c
@@ -0,0 +1,67 @@
+/* Copyright (C) 2018  Curtis McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+wchar_t *wcssep(wchar_t **stringp, const wchar_t *delim) {
+	wchar_t *orig = *stringp;
+	if (!orig) return NULL;
+	size_t i = wcscspn(orig, delim);
+	*stringp = NULL;
+	if (orig[i]) {
+		orig[i] = '\0';
+		*stringp = &orig[i + 1];
+	}
+	return orig;
+}
+
+// From <https://en.cppreference.com/w/c/io/fwprintf#Notes>:
+//
+// While narrow strings provide snprintf, which makes it possible to determine
+// the required output buffer size, there is no equivalent for wide strings
+// (until C11's snwprintf_s), and in order to determine the buffer size, the
+// program may need to call swprintf, check the result value, and reallocate a
+// larger buffer, trying again until successful.
+//
+// snwprintf_s, unlike swprintf_s, will truncate the result to fit within the
+// array pointed to by buffer, even though truncation is treated as an error by
+// most bounds-checked functions.
+int vaswprintf(wchar_t **ret, const wchar_t *format, va_list ap) {
+	*ret = NULL;
+
+	for (size_t cap = 2 * wcslen(format);; cap *= 2) {
+		wchar_t *buf = realloc(*ret, 1 + cap);
+		if (!buf) goto fail;
+		*ret = buf;
+
+		va_list _ap;
+		va_copy(_ap, ap);
+		int len = vswprintf(*ret, 1 + cap, format, _ap);
+		va_end(_ap);
+
+		if (len >= 0) return len;
+		if (errno != EOVERFLOW) goto fail;
+	}
+
+fail:
+	free(*ret);
+	*ret = NULL;
+	return -1;
+}
diff --git a/ui.c b/ui.c
index 19eefdb..5f5efc3 100644
--- a/ui.c
+++ b/ui.c
@@ -174,19 +174,20 @@ static const short IRC_COLORS[16] = {
 	0 + COLOR_WHITE,   // light gray
 };
 
-static const char *parseColor(short *pair, const char *str) {
+static const wchar_t *parseColor(short *pair, const wchar_t *str) {
 	short fg = 0;
-	size_t fgLen = MIN(strspn(str, "0123456789"), 2);
+	size_t fgLen = MIN(wcsspn(str, L"0123456789"), 2);
 	if (!fgLen) { *pair = -1; return str; }
 	for (size_t i = 0; i < fgLen; ++i) {
-		fg = fg * 10 + (str[i] - '0');
+		fg = fg * 10 + (str[i] - L'0');
 	}
 	str = &str[fgLen];
 
 	short bg = 0;
-	size_t bgLen = (str[0] == ',') ? MIN(strspn(&str[1], "0123456789"), 2) : 0;
+	size_t bgLen = 0;
+	if (str[0] == L',') bgLen = MIN(wcsspn(&str[1], L"0123456789"), 2);
 	for (size_t i = 0; i < bgLen; ++i) {
-		bg = bg * 10 + (str[1 + i] - '0');
+		bg = bg * 10 + (str[1 + i] - L'0');
 	}
 	if (bgLen) str = &str[1 + bgLen];
 
@@ -197,43 +198,51 @@ static const char *parseColor(short *pair, const char *str) {
 	return str;
 }
 
-static void addIRC(WINDOW *win, const char *str) {
+static void addIRC(WINDOW *win, const wchar_t *str) {
 	attr_t attr = A_NORMAL;
 	short pair = -1;
 	for (;;) {
-		size_t cc = strcspn(str, "\2\3\35\37");
+		size_t cc = wcscspn(str, L"\2\3\35\37");
 		wattr_set(win, attr | attr8(pair), 1 + pair8(pair), NULL);
-		waddnstr(win, str, cc);
+		waddnwstr(win, str, cc);
 		if (!str[cc]) break;
 
 		str = &str[cc];
 		switch (*str++) {
-			break; case '\2': attr ^= A_BOLD;
-			break; case '\3': str = parseColor(&pair, str);
-			break; case '\35': attr ^= A_ITALIC;
-			break; case '\37': attr ^= A_UNDERLINE;
+			break; case L'\2': attr ^= A_BOLD;
+			break; case L'\3': str = parseColor(&pair, str);
+			break; case L'\35': attr ^= A_ITALIC;
+			break; case L'\37': attr ^= A_UNDERLINE;
 		}
 	}
 }
 
-void uiTopic(const char *topic) {
+void uiTopic(const wchar_t *topic) {
 	wmove(ui.topic, 0, 0);
 	addIRC(ui.topic, topic);
 	wclrtoeol(ui.topic);
 }
 
-void uiLog(const char *line) {
+void uiTopicStr(const char *topic) {
+	size_t len = strlen(topic);
+	wchar_t wcs[1 + len];
+	len = mbstowcs(wcs, topic, 1 + len);
+	if (len == (size_t)-1) err(EX_DATAERR, "mbstowcs");
+	uiTopic(wcs);
+}
+
+void uiLog(const wchar_t *line) {
 	waddch(ui.log, '\n');
 	addIRC(ui.log, line);
 }
 
-void uiFmt(const char *format, ...) {
-	char *buf;
+void uiFmt(const wchar_t *format, ...) {
+	wchar_t *buf;
 	va_list ap;
 	va_start(ap, format);
-	vasprintf(&buf, format, ap);
+	vaswprintf(&buf, format, ap);
 	va_end(ap);
-	if (!buf) err(EX_OSERR, "vasprintf");
+	if (!buf) err(EX_OSERR, "vaswprintf");
 	uiLog(buf);
 	free(buf);
 }