Use wchar_t strings for all of UI

vaswprintf is a nightmare.
master
Curtis McEnroe 2018-08-06 14:19:52 -04:00
parent ea4c70dae5
commit d6fb797b11
No known key found for this signature in database
GPG Key ID: CEA2F97ADCFCD77C
9 changed files with 126 additions and 56 deletions

View File

@ -3,7 +3,7 @@ CFLAGS += -Wall -Wextra -Wpedantic
CFLAGS += -I/usr/local/include CFLAGS += -I/usr/local/include
LDFLAGS += -L/usr/local/lib LDFLAGS += -L/usr/local/lib
LDLIBS = -lcursesw -ltls 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 all: tags chat

1
README
View File

@ -8,3 +8,4 @@ This software targets FreeBSD and requires LibreSSL.
irc.c TLS client connection irc.c TLS client connection
input.c Input command handling input.c Input command handling
handle.c Incoming command handling handle.c Incoming command handling
pls.c Functions which should not have to be written

2
chat.c
View File

@ -78,7 +78,7 @@ int main(int argc, char *argv[]) {
signal(SIGINT, sigint); signal(SIGINT, sigint);
uiInit(); uiInit();
uiLog("Traveling..."); uiLog(L"Traveling...");
uiDraw(); uiDraw();
int sock = ircConnect(host, port, webPass); int sock = ircConnect(host, port, webPass);

13
chat.h
View File

@ -16,6 +16,7 @@
#define SOURCE_URL "https://code.causal.agency/june/chat" #define SOURCE_URL "https://code.causal.agency/june/chat"
#include <stdarg.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
#include <wchar.h> #include <wchar.h>
@ -41,11 +42,15 @@ void uiInit(void);
void uiHide(void); void uiHide(void);
void uiDraw(void); void uiDraw(void);
void uiRead(void); void uiRead(void);
void uiTopic(const char *topic); void uiTopic(const wchar_t *topic);
void uiLog(const char *line); void uiTopicStr(const char *topic);
void uiLog(const wchar_t *line);
__attribute__((format(printf, 1, 2))) //__attribute__((format(printf, 1, 2)))
void uiFmt(const char *format, ...); void uiFmt(const wchar_t *format, ...);
void handle(char *line); void handle(char *line);
void input(wchar_t *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);

View File

@ -58,9 +58,9 @@ static void handle432(char *prefix, char *params) {
shift(&params); shift(&params);
shift(&params); shift(&params);
char *mesg = shift(&params); char *mesg = shift(&params);
uiLog("You can't use that name here"); uiLog(L"You can't use that name here");
uiFmt("Sheriff says, \"%s\"", mesg); uiFmt(L"Sheriff says, \"%s\"", mesg);
uiLog("Type /nick <name> to choose a new one"); uiLog(L"Type /nick <name> to choose a new one");
} }
static void handle001(char *prefix, char *params) { static void handle001(char *prefix, char *params) {
@ -82,7 +82,7 @@ static void handleJoin(char *prefix, char *params) {
chat.user = strdup(user); chat.user = strdup(user);
} }
uiFmt( 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 color(user), nick, color(chan), chan
); );
} }
@ -93,7 +93,7 @@ static void handlePart(char *prefix, char *params) {
char *chan = shift(&params); char *chan = shift(&params);
char *mesg = shift(&params); char *mesg = shift(&params);
uiFmt( 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 color(user), nick, color(chan), chan, mesg
); );
} }
@ -104,7 +104,7 @@ static void handleQuit(char *prefix, char *params) {
char *mesg = shift(&params); char *mesg = shift(&params);
char *quot = (mesg[0] == '"') ? "" : "\""; char *quot = (mesg[0] == '"') ? "" : "\"";
uiFmt( uiFmt(
"\3%d%s\3 leaves, %s%s%s", L"\3%d%s\3 leaves, %s%s%s",
color(user), nick, quot, mesg, quot color(user), nick, quot, mesg, quot
); );
} }
@ -116,7 +116,7 @@ static void handleKick(char *prefix, char *params) {
char *kick = shift(&params); char *kick = shift(&params);
char *mesg = shift(&params); char *mesg = shift(&params);
uiFmt( 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 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 *chan = shift(&params);
char *topic = shift(&params); char *topic = shift(&params);
uiFmt( uiFmt(
"The sign in \3%d%s\3 reads, \"%s\"", L"The sign in \3%d%s\3 reads, \"%s\"",
color(chan), chan, topic color(chan), chan, topic
); );
uiTopic(topic); uiTopicStr(topic);
} }
static void handleTopic(char *prefix, char *params) { static void handleTopic(char *prefix, char *params) {
@ -139,10 +139,10 @@ static void handleTopic(char *prefix, char *params) {
char *chan = shift(&params); char *chan = shift(&params);
char *topic = shift(&params); char *topic = shift(&params);
uiFmt( 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 color(user), nick, color(chan), chan, topic
); );
uiTopic(topic); uiTopicStr(topic);
} }
static void handle366(char *prefix, char *params) { static void handle366(char *prefix, char *params) {
@ -176,7 +176,7 @@ static void handle315(char *prefix, char *params) {
char *chan = shift(&params); char *chan = shift(&params);
whoLen = 0; whoLen = 0;
uiFmt( uiFmt(
"In \3%d%s\3 are %s", L"In \3%d%s\3 are %s",
color(chan), chan, whoBuf color(chan), chan, whoBuf
); );
} }
@ -190,7 +190,7 @@ static void handleNick(char *prefix, char *params) {
chat.nick = strdup(next); chat.nick = strdup(next);
} }
uiFmt( 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 color(user), prev, color(user), next
); );
} }
@ -202,9 +202,9 @@ static void handlePrivmsg(char *prefix, char *params) {
char *mesg = shift(&params); char *mesg = shift(&params);
if (mesg[0] == '\1') { if (mesg[0] == '\1') {
strsep(&mesg, " "); 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 { } 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 *chan = shift(&params);
char *mesg = shift(&params); char *mesg = shift(&params);
if (strcmp(chat.chan, chan)) return; 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 { static const struct {

16
input.c
View File

@ -23,18 +23,6 @@
#include "chat.h" #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) { static void privmsg(bool action, const wchar_t *mesg) {
char *line; char *line;
int send; int send;
@ -60,7 +48,7 @@ static void inputNick(wchar_t *params) {
if (nick) { if (nick) {
ircFmt("NICK %ls\r\n", nick); ircFmt("NICK %ls\r\n", nick);
} else { } 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); COMMANDS[i].handler(input);
return; return;
} }
uiFmt("/%ls isn't a recognized command", command); uiFmt(L"/%ls isn't a recognized command", command);
} }

4
irc.c
View File

@ -99,7 +99,7 @@ void ircFmt(const char *format, ...) {
int len = vasprintf(&buf, format, ap); int len = vasprintf(&buf, format, ap);
va_end(ap); va_end(ap);
if (!buf) err(EX_OSERR, "vasprintf"); 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); ircWrite(buf, len);
free(buf); free(buf);
} }
@ -119,7 +119,7 @@ void ircRead(void) {
char *crlf, *line = buf; char *crlf, *line = buf;
while ((crlf = strnstr(line, "\r\n", &buf[len] - line))) { while ((crlf = strnstr(line, "\r\n", &buf[len] - line))) {
crlf[0] = '\0'; crlf[0] = '\0';
if (chat.verbose) uiFmt(">>> %s", line); if (chat.verbose) uiFmt(L">>> %s", line);
handle(line); handle(line);
line = &crlf[2]; line = &crlf[2];
} }

67
pls.c 100644
View File

@ -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;
}

45
ui.c
View File

@ -174,19 +174,20 @@ static const short IRC_COLORS[16] = {
0 + COLOR_WHITE, // light gray 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; 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; } if (!fgLen) { *pair = -1; return str; }
for (size_t i = 0; i < fgLen; ++i) { for (size_t i = 0; i < fgLen; ++i) {
fg = fg * 10 + (str[i] - '0'); fg = fg * 10 + (str[i] - L'0');
} }
str = &str[fgLen]; str = &str[fgLen];
short bg = 0; 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) { 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]; if (bgLen) str = &str[1 + bgLen];
@ -197,43 +198,51 @@ static const char *parseColor(short *pair, const char *str) {
return 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; attr_t attr = A_NORMAL;
short pair = -1; short pair = -1;
for (;;) { 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); wattr_set(win, attr | attr8(pair), 1 + pair8(pair), NULL);
waddnstr(win, str, cc); waddnwstr(win, str, cc);
if (!str[cc]) break; if (!str[cc]) break;
str = &str[cc]; str = &str[cc];
switch (*str++) { switch (*str++) {
break; case '\2': attr ^= A_BOLD; break; case L'\2': attr ^= A_BOLD;
break; case '\3': str = parseColor(&pair, str); break; case L'\3': str = parseColor(&pair, str);
break; case '\35': attr ^= A_ITALIC; break; case L'\35': attr ^= A_ITALIC;
break; case '\37': attr ^= A_UNDERLINE; break; case L'\37': attr ^= A_UNDERLINE;
} }
} }
} }
void uiTopic(const char *topic) { void uiTopic(const wchar_t *topic) {
wmove(ui.topic, 0, 0); wmove(ui.topic, 0, 0);
addIRC(ui.topic, topic); addIRC(ui.topic, topic);
wclrtoeol(ui.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'); waddch(ui.log, '\n');
addIRC(ui.log, line); addIRC(ui.log, line);
} }
void uiFmt(const char *format, ...) { void uiFmt(const wchar_t *format, ...) {
char *buf; wchar_t *buf;
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
vasprintf(&buf, format, ap); vaswprintf(&buf, format, ap);
va_end(ap); va_end(ap);
if (!buf) err(EX_OSERR, "vasprintf"); if (!buf) err(EX_OSERR, "vaswprintf");
uiLog(buf); uiLog(buf);
free(buf); free(buf);
} }