diff options
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | chat.c | 8 | ||||
-rw-r--r-- | chat.h | 22 | ||||
-rw-r--r-- | handle.c | 21 | ||||
-rw-r--r-- | irc.c | 6 | ||||
-rw-r--r-- | ui.c | 174 |
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); +} |