From 843160236381d0c76bef1eac89e556920d700a9d Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 1 Feb 2020 01:18:01 -0500 Subject: Blindly implement login flow --- chat.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 chat.c (limited to 'chat.c') diff --git a/chat.c b/chat.c new file mode 100644 index 0000000..89579c0 --- /dev/null +++ b/chat.c @@ -0,0 +1,73 @@ +/* Copyright (C) 2020 C. McEnroe + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include + +#include "chat.h" + +struct Self self; + +int main(int argc, char *argv[]) { + bool insecure = false; + const char *host = NULL; + const char *port = "6697"; + const char *cert = NULL; + const char *priv = NULL; + + bool sasl = false; + const char *pass = NULL; + const char *nick = NULL; + const char *user = NULL; + const char *real = NULL; + + int opt; + while (0 < (opt = getopt(argc, argv, "!a:c:eh:j:k:n:p:r:u:w:"))) { + switch (opt) { + break; case '!': insecure = true; + break; case 'a': sasl = true; self.plain = optarg; + break; case 'c': cert = optarg; + break; case 'e': sasl = true; + break; case 'h': host = optarg; + break; case 'j': self.join = optarg; + break; case 'k': priv = optarg; + break; case 'n': nick = optarg; + break; case 'p': port = optarg; + break; case 'r': real = optarg; + break; case 'u': user = optarg; + break; case 'w': pass = optarg; + } + } + if (!host) errx(EX_USAGE, "host required"); + + if (!nick) nick = getenv("USER"); + if (!nick) errx(EX_CONFIG, "USER unset"); + if (!user) user = nick; + if (!real) real = nick; + + ircConfig(insecure, cert, priv); + + int irc = ircConnect(host, port); + if (pass) ircFormat("PASS :%s\r\n", pass); + if (sasl) ircFormat("CAP REQ :sasl\r\n"); + ircFormat("CAP LS\r\n"); + ircFormat("NICK :%s\r\n", nick); + ircFormat("USER %s 0 * :%s\r\n", user, real); +} -- cgit 1.4.1 From 2b3a8bfb9c022269307feed01419c903ba754508 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 1 Feb 2020 02:26:35 -0500 Subject: Add -v flag --- catgirl.1 | 9 ++++++++- chat.c | 7 ++++++- chat.h | 3 ++- irc.c | 14 ++++++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) (limited to 'chat.c') diff --git a/catgirl.1 b/catgirl.1 index 158466b..e9bb40e 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -8,7 +8,7 @@ . .Sh SYNOPSIS .Nm -.Op Fl e +.Op Fl ev .Op Fl a Ar auth .Op Fl c Ar cert .Op Fl h Ar host @@ -88,6 +88,13 @@ Set username to .Ar user . The default username is the same as the nickname. . +.It Fl v +Log raw IRC messages to the +.Sy +window +as well as standard error +if it is not a terminal. +. .It Fl w Ar pass Log in with the server password .Ar pass . diff --git a/chat.c b/chat.c index 89579c0..5796085 100644 --- a/chat.c +++ b/chat.c @@ -39,7 +39,7 @@ int main(int argc, char *argv[]) { const char *real = NULL; int opt; - while (0 < (opt = getopt(argc, argv, "!a:c:eh:j:k:n:p:r:u:w:"))) { + while (0 < (opt = getopt(argc, argv, "!a:c:eh:j:k:n:p:r:u:vw:"))) { switch (opt) { break; case '!': insecure = true; break; case 'a': sasl = true; self.plain = optarg; @@ -52,6 +52,7 @@ int main(int argc, char *argv[]) { break; case 'p': port = optarg; break; case 'r': real = optarg; break; case 'u': user = optarg; + break; case 'v': self.debug = true; break; case 'w': pass = optarg; } } @@ -70,4 +71,8 @@ int main(int argc, char *argv[]) { ircFormat("CAP LS\r\n"); ircFormat("NICK :%s\r\n", nick); ircFormat("USER %s 0 * :%s\r\n", user, real); + + for (;;) { + ircRecv(); + } } diff --git a/chat.h b/chat.h index bb8929b..4dd4732 100644 --- a/chat.h +++ b/chat.h @@ -33,10 +33,11 @@ enum Cap { }; extern struct Self { + bool debug; + const char *join; enum Cap caps; char *plain; char *nick; - const char *join; } self; #define ENUM_TAG \ diff --git a/irc.c b/irc.c index c1c0e7a..cf8aab7 100644 --- a/irc.c +++ b/irc.c @@ -101,6 +101,18 @@ int ircConnect(const char *host, const char *port) { return sock; } +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", + Gray, dir, dir, (int)len, line + );*/ + if (!isatty(STDERR_FILENO)) { + fprintf(stderr, "%c%c %.*s\n", dir, dir, (int)len, line); + } +} + void ircSend(const char *ptr, size_t len) { assert(client); while (len) { @@ -119,6 +131,7 @@ void ircFormat(const char *format, ...) { int len = vsnprintf(buf, sizeof(buf), format, ap); va_end(ap); assert((size_t)len < sizeof(buf)); + debug('<', buf); ircSend(buf, len); } @@ -196,6 +209,7 @@ void ircRecv(void) { crlf = memmem(line, &buf[len] - line, "\r\n", 2); if (!crlf) break; *crlf = '\0'; + debug('>', line); handle(parse(line)); line = crlf + 2; } -- cgit 1.4.1 From 03cb0d7c04d287ba95b26924620ece0855002ad5 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 1 Feb 2020 02:33:17 -0500 Subject: Add IDs and names --- chat.c | 7 +++++++ chat.h | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) (limited to 'chat.c') diff --git a/chat.c b/chat.c index 5796085..462faa0 100644 --- a/chat.c +++ b/chat.c @@ -23,6 +23,13 @@ #include "chat.h" +char *idNames[IDCap] = { + [None] = "", + [Debug] = "", + [Network] = "", +}; +size_t idNext = Network + 1; + struct Self self; int main(int argc, char *argv[]) { diff --git a/chat.h b/chat.h index 4dd4732..8c13d49 100644 --- a/chat.h +++ b/chat.h @@ -14,13 +14,35 @@ * along with this program. If not, see . */ +#include #include +#include +#include +#include #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit typedef unsigned char byte; +enum { None, Debug, Network, IDCap = 256 }; +extern char *idNames[IDCap]; +extern size_t idNext; + +static inline size_t idFind(const char *name) { + for (size_t id = 0; id < idNext; ++id) { + if (!strcmp(idNames[id], name)) return id; + } + return None; +} +static inline size_t idFor(const char *name) { + size_t id = idFind(name); + if (id) return id; + idNames[idNext] = strdup(name); + if (!idNames[idNext]) err(EX_OSERR, "strdup"); + return idNext++; +} + #define ENUM_CAP \ X("sasl", CapSASL) \ X("server-time", CapServerTime) \ -- cgit 1.4.1 From e5363bcae0f726455fb4198cd21d46721ad5e39a Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 1 Feb 2020 19:37:48 -0500 Subject: Implement the beginnings of UI It takes so much code to do anything in curses... --- Makefile | 3 +- chat.c | 8 +++ chat.h | 22 +++++++- handle.c | 21 ++++++++ irc.c | 6 +-- ui.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 228 insertions(+), 6 deletions(-) create mode 100644 ui.c (limited to 'chat.c') 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 +#include #include #include #include @@ -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 + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} -- cgit 1.4.1 From cd3dc4ef4caaad3a696ad731c197f50105119b31 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 1 Feb 2020 21:57:11 -0500 Subject: Parse IRC styling in UI Wow the colorPair thing actually works. Have I finally cracked curses colors? --- chat.c | 2 +- ui.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 3 deletions(-) (limited to 'chat.c') diff --git a/chat.c b/chat.c index 47227d5..75cca62 100644 --- a/chat.c +++ b/chat.c @@ -76,7 +76,7 @@ int main(int argc, char *argv[]) { ircConfig(insecure, cert, priv); uiInit(); - uiFormat(Network, Cold, NULL, "Traveling..."); + uiFormat(Network, Cold, NULL, C "3Trave" U "ling" U C "0,3.." C "0,4."); uiDraw(); int irc = ircConnect(host, port); diff --git a/ui.c b/ui.c index 0295c8d..83c4bc7 100644 --- a/ui.c +++ b/ui.c @@ -15,12 +15,14 @@ */ #include +#include #include #include #include #include #include #include +#include #include #include @@ -56,7 +58,7 @@ static short colorPair(short fg, short bg) { pair_content(pair, &f, &b); if (f == fg && b == bg) return pair; } - init_pair(colorPairs, fg, bg); + init_pair(colorPairs, fg % COLORS, bg % COLORS); return colorPairs++; } @@ -154,11 +156,80 @@ void uiDraw(void) { doupdate(); } +struct Style { + attr_t attr; + enum Color fg, bg; +}; +static const struct Style Reset = { A_NORMAL, Default, Default }; + +static short mapColor(enum Color color) { + switch (color) { + break; case White: return 8 + COLOR_WHITE; + break; case Black: return 0 + COLOR_BLACK; + break; case Blue: return 0 + COLOR_BLUE; + break; case Green: return 0 + COLOR_GREEN; + break; case Red: return 8 + COLOR_RED; + break; case Brown: return 0 + COLOR_RED; + break; case Magenta: return 0 + COLOR_MAGENTA; + break; case Orange: return 0 + COLOR_YELLOW; + break; case Yellow: return 8 + COLOR_YELLOW; + break; case LightGreen: return 8 + COLOR_GREEN; + break; case Cyan: return 0 + COLOR_CYAN; + break; case LightCyan: return 8 + COLOR_CYAN; + break; case LightBlue: return 8 + COLOR_BLUE; + break; case Pink: return 8 + COLOR_MAGENTA; + break; case Gray: return 8 + COLOR_BLACK; + break; case LightGray: return 0 + COLOR_WHITE; + break; default: return -1; + } +} + +static void styleParse(struct Style *style, const char **str, size_t *len) { + switch (**str) { + break; case '\2': (*str)++; style->attr ^= A_BOLD; + break; case '\17': (*str)++; *style = Reset; + break; case '\26': (*str)++; style->attr ^= A_REVERSE; + break; case '\35': (*str)++; style->attr ^= A_ITALIC; + break; case '\37': (*str)++; style->attr ^= A_UNDERLINE; + break; case '\3': { + (*str)++; + if (!isdigit(**str)) { + style->fg = Default; + style->bg = Default; + break; + } + style->fg = *(*str)++ - '0'; + if (isdigit(**str)) style->fg = style->fg * 10 + *(*str)++ - '0'; + if ((*str)[0] != ',' || !isdigit((*str)[1])) break; + (*str)++; + style->bg = *(*str)++ - '0'; + if (isdigit(**str)) style->bg = style->bg * 10 + *(*str)++ - '0'; + } + } + *len = strcspn(*str, "\2\3\17\26\35\37"); +} + +static void styleAdd(WINDOW *win, const char *str) { + size_t len; + struct Style style = Reset; + while (*str) { + styleParse(&style, &str, &len); + wattr_set( + win, + style.attr | colorAttr(mapColor(style.fg)), + colorPair(mapColor(style.fg), mapColor(style.bg)), + NULL + ); + waddnstr(win, str, len); + str += len; + } +} + 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); + styleAdd(window->pad, str); } void uiFormat( -- cgit 1.4.1 From 05256b68fef9d9b64b01afb60de31f9c47b60ca1 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 1 Feb 2020 22:40:55 -0500 Subject: Implement word wrap This actually wasn't that bad? --- chat.c | 14 +++++++++++++- ui.c | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'chat.c') diff --git a/chat.c b/chat.c index 75cca62..ceaf1b5 100644 --- a/chat.c +++ b/chat.c @@ -76,7 +76,19 @@ int main(int argc, char *argv[]) { ircConfig(insecure, cert, priv); uiInit(); - uiFormat(Network, Cold, NULL, C "3Trave" U "ling" U C "0,3.." C "0,4."); + uiFormat(Network, Cold, NULL, C "3Trave" U "ling" U C "0,3.." C "0,4." R); + uiFormat( + Network, Cold, NULL, + "Jackdaws love my big sphinx of quartz. " + "The quick brown fox jumps over the lazy dog. " + "Jackdaws love my big sphinx of quartz. " + "Jackdaws love my big sphinx of quartz. " + "Jackdaws love my big sphinx of quartz. " + "The quick brown fox jumps over the lazy dog. " + "The quick brown fox jumps over the lazy dog. " + "Jackdaws love my big sphinx of quartz. " + "Jackdaws love my big sphinx of quartz. " + ); uiDraw(); int irc = ircConnect(host, port); diff --git a/ui.c b/ui.c index 83c4bc7..7ce0257 100644 --- a/ui.c +++ b/ui.c @@ -209,11 +209,33 @@ static void styleParse(struct Style *style, const char **str, size_t *len) { *len = strcspn(*str, "\2\3\17\26\35\37"); } +static int wordWidth(const char *str) { + size_t len = strcspn(str, " "); + // TODO: wcswidth. + return len; +} + static void styleAdd(WINDOW *win, const char *str) { + int _, x, width; + getmaxyx(win, _, width); + size_t len; struct Style style = Reset; while (*str) { + if (*str == ' ') { + const char *word = &str[strspn(str, " ")]; + getyx(win, _, x); + if (width - x - 1 < wordWidth(word)) { + waddch(win, '\n'); + str = word; + } + } + styleParse(&style, &str, &len); + size_t sp = strspn(str, " "); + sp += strcspn(&str[sp], " "); + if (sp < len) len = sp; + wattr_set( win, style.attr | colorAttr(mapColor(style.fg)), -- cgit 1.4.1 From ec83332e15d31c1ffbb7112ff6743f2a5c815c71 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 03:13:50 -0500 Subject: Implement window switching and status line --- chat.c | 15 ++------------- chat.h | 1 + handle.c | 22 +++++++++++++++++++--- ui.c | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 16 deletions(-) (limited to 'chat.c') diff --git a/chat.c b/chat.c index ceaf1b5..d4ed31c 100644 --- a/chat.c +++ b/chat.c @@ -76,19 +76,8 @@ int main(int argc, char *argv[]) { ircConfig(insecure, cert, priv); uiInit(); - uiFormat(Network, Cold, NULL, C "3Trave" U "ling" U C "0,3.." C "0,4." R); - uiFormat( - Network, Cold, NULL, - "Jackdaws love my big sphinx of quartz. " - "The quick brown fox jumps over the lazy dog. " - "Jackdaws love my big sphinx of quartz. " - "Jackdaws love my big sphinx of quartz. " - "Jackdaws love my big sphinx of quartz. " - "The quick brown fox jumps over the lazy dog. " - "The quick brown fox jumps over the lazy dog. " - "Jackdaws love my big sphinx of quartz. " - "Jackdaws love my big sphinx of quartz. " - ); + uiShowID(Network); + uiFormat(Network, Cold, NULL, "Traveling..."); uiDraw(); int irc = ircConnect(host, port); diff --git a/chat.h b/chat.h index fc5043d..9060f29 100644 --- a/chat.h +++ b/chat.h @@ -117,6 +117,7 @@ void handle(struct Message msg); enum Heat { Cold, Warm, Hot }; void uiInit(void); void uiDraw(void); +void uiShowID(size_t id); 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, ... diff --git a/handle.c b/handle.c index 350a636..609867e 100644 --- a/handle.c +++ b/handle.c @@ -65,9 +65,11 @@ static void set(char **field, const char *value) { if (!*field) err(EX_OSERR, "strdup"); } -static void require(const struct Message *msg, bool origin, size_t len) { - if (origin && !msg->nick) { - errx(EX_PROTOCOL, "%s missing origin", msg->cmd); +static void require(struct Message *msg, bool origin, size_t len) { + if (origin) { + if (!msg->nick) errx(EX_PROTOCOL, "%s missing origin", msg->cmd); + if (!msg->user) msg->user = msg->nick; + if (!msg->host) msg->host = msg->user; } for (size_t i = 0; i < len; ++i) { if (msg->params[i]) continue; @@ -177,6 +179,19 @@ static void handleReplyMOTD(struct Message *msg) { uiFormat(Network, Cold, tagTime(msg), "%s", line); } +static void handleJoin(struct Message *msg) { + require(msg, true, 1); + size_t id = idFor(msg->params[0]); + if (self.nick && !strcmp(msg->nick, self.nick)) { + uiShowID(id); + } + uiFormat( + id, Cold, tagTime(msg), + C"%02d%s"C" arrives in "C"%02d%s"C, + hash(msg->user), msg->nick, hash(idNames[id]), idNames[id] + ); +} + static void handlePing(struct Message *msg) { require(msg, false, 1); ircFormat("PONG :%s\r\n", msg->params[0]); @@ -197,6 +212,7 @@ static const struct Handler { { "906", handleErrorSASLFail }, { "AUTHENTICATE", handleAuthenticate }, { "CAP", handleCap }, + { "JOIN", handleJoin }, { "PING", handlePing }, }; diff --git a/ui.c b/ui.c index 90ba726..3ae6592 100644 --- a/ui.c +++ b/ui.c @@ -262,6 +262,40 @@ static void styleAdd(WINDOW *win, const char *str) { } } +static void statusUpdate(void) { + wmove(status, 0, 0); + int num; + const struct Window *window; + for (num = 0, window = windows.head; window; ++num, window = window->next) { + if (!window->unread && window != windows.active) continue; + enum Color color = hash(idNames[window->id]); // FIXME: queries. + int unread; + char buf[256]; + snprintf( + buf, sizeof(buf), C"%d%s %d %s %n("C"%02d%d"C"%d) ", + color, (window == windows.active ? V : ""), + num, idNames[window->id], + &unread, (window->heat > Warm ? White : color), window->unread, + color + ); + if (!window->unread) buf[unread] = '\0'; + styleAdd(status, buf); + } + wclrtoeol(status); +} + +void uiShowID(size_t id) { + struct Window *window = windowFor(id); + window->heat = Cold; + window->unread = 0; + window->mark = false; + if (windows.active) windows.active->mark = true; + windows.other = windows.active; + windows.active = window; + touchwin(window->pad); + statusUpdate(); +} + void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str) { (void)time; struct Window *window = windowFor(id); -- cgit 1.4.1 From 8bb9ea7b7ff2e98bbe629f9f2e63f1dcb70250e3 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 03:27:50 -0500 Subject: Add idColors --- chat.c | 7 +++++++ chat.h | 47 +++++++++++++++++++++++++---------------------- handle.c | 3 ++- ui.c | 8 ++++---- 4 files changed, 38 insertions(+), 27 deletions(-) (limited to 'chat.c') diff --git a/chat.c b/chat.c index d4ed31c..b61dd34 100644 --- a/chat.c +++ b/chat.c @@ -29,6 +29,13 @@ char *idNames[IDCap] = { [Debug] = "", [Network] = "", }; + +enum Color idColors[IDCap] = { + [None] = Black, + [Debug] = Red, + [Network] = Gray, +}; + size_t idNext = Network + 1; struct Self self; diff --git a/chat.h b/chat.h index 9060f29..4ced983 100644 --- a/chat.h +++ b/chat.h @@ -26,8 +26,21 @@ typedef unsigned char byte; +#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, +}; + enum { None, Debug, Network, IDCap = 256 }; extern char *idNames[IDCap]; +extern enum Color idColors[IDCap]; extern size_t idNext; static inline size_t idFind(const char *name) { @@ -36,6 +49,7 @@ static inline size_t idFind(const char *name) { } return None; } + static inline size_t idFor(const char *name) { size_t id = idFind(name); if (id) return id; @@ -83,28 +97,6 @@ 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, -}; -static inline enum Color hash(const char *str) { - if (*str == '~') str++; - uint32_t hash = 0; - for (; *str; ++str) { - hash = (hash << 5) | (hash >> 27); - hash ^= *str; - hash *= 0x27220A95; - } - return 2 + hash % 14; -} - void ircConfig(bool insecure, const char *cert, const char *priv); int ircConnect(const char *host, const char *port); void ircRecv(void); @@ -140,6 +132,17 @@ void termTitle(const char *title); void termMode(enum TermMode mode, bool set); enum TermEvent termEvent(char ch); +static inline enum Color hash(const char *str) { + if (*str == '~') str++; + uint32_t hash = 0; + for (; *str; ++str) { + hash = (hash << 5) | (hash >> 27); + hash ^= *str; + hash *= 0x27220A95; + } + return 2 + hash % 14; +} + #define BASE64_SIZE(len) (1 + ((len) + 2) / 3 * 4) static const char Base64[64] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" diff --git a/handle.c b/handle.c index 609867e..f52f6f9 100644 --- a/handle.c +++ b/handle.c @@ -183,12 +183,13 @@ static void handleJoin(struct Message *msg) { require(msg, true, 1); size_t id = idFor(msg->params[0]); if (self.nick && !strcmp(msg->nick, self.nick)) { + idColors[id] = hash(msg->params[0]); uiShowID(id); } uiFormat( id, Cold, tagTime(msg), C"%02d%s"C" arrives in "C"%02d%s"C, - hash(msg->user), msg->nick, hash(idNames[id]), idNames[id] + hash(msg->user), msg->nick, idColors[id], idNames[id] ); } diff --git a/ui.c b/ui.c index 3ae6592..961e448 100644 --- a/ui.c +++ b/ui.c @@ -268,15 +268,15 @@ static void statusUpdate(void) { const struct Window *window; for (num = 0, window = windows.head; window; ++num, window = window->next) { if (!window->unread && window != windows.active) continue; - enum Color color = hash(idNames[window->id]); // FIXME: queries. int unread; char buf[256]; snprintf( buf, sizeof(buf), C"%d%s %d %s %n("C"%02d%d"C"%d) ", - color, (window == windows.active ? V : ""), + idColors[window->id], (window == windows.active ? V : ""), num, idNames[window->id], - &unread, (window->heat > Warm ? White : color), window->unread, - color + &unread, (window->heat > Warm ? White : idColors[window->id]), + window->unread, + idColors[window->id] ); if (!window->unread) buf[unread] = '\0'; styleAdd(status, buf); -- cgit 1.4.1 From 0d6a60cc6634cafe03671e9d5a1a64295c98bb9d Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 17:37:36 -0500 Subject: Save NETWORK, CHANTYPES, PREFIX from ISUPPORT --- chat.c | 4 ++++ chat.h | 11 ++++++++++- handle.c | 14 +++++++------- 3 files changed, 21 insertions(+), 8 deletions(-) (limited to 'chat.c') diff --git a/chat.c b/chat.c index b61dd34..162f68f 100644 --- a/chat.c +++ b/chat.c @@ -80,6 +80,10 @@ int main(int argc, char *argv[]) { if (!user) user = nick; if (!real) real = nick; + set(&self.network, host); + set(&self.chanTypes, "#&"); + set(&self.prefixes, "@+"); + ircConfig(insecure, cert, priv); uiInit(); diff --git a/chat.h b/chat.h index 275fef9..f9de779 100644 --- a/chat.h +++ b/chat.h @@ -65,12 +65,21 @@ enum Cap { extern struct Self { bool debug; + char *plain; const char *join; enum Cap caps; - char *plain; + char *network; + char *chanTypes; + char *prefixes; char *nick; } self; +static inline void set(char **field, const char *value) { + free(*field); + *field = strdup(value); + if (!*field) err(EX_OSERR, "strdup"); +} + #define ENUM_TAG \ X("time", TagTime) diff --git a/handle.c b/handle.c index 2af5837..2766cc8 100644 --- a/handle.c +++ b/handle.c @@ -59,12 +59,6 @@ static const char *capList(enum Cap caps) { return buf; } -static void set(char **field, const char *value) { - free(*field); - *field = strdup(value); - if (!*field) err(EX_OSERR, "strdup"); -} - static void require(struct Message *msg, bool origin, size_t len) { if (origin) { if (!msg->nick) errx(EX_PROTOCOL, "%s missing origin", msg->cmd); @@ -158,16 +152,22 @@ static void handleReplyWelcome(struct Message *msg) { } 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")) { + set(&self.network, msg->params[i]); uiFormat( Network, Cold, tagTime(msg), "You arrive in %s", msg->params[i] ); + } else if (!strcmp(key, "CHANTYPES")) { + set(&self.chanTypes, msg->params[i]); + } else if (!strcmp(key, "PREFIX")) { + strsep(&msg->params[i], ")"); + if (!msg->params[i]) continue; + set(&self.prefixes, msg->params[i]); } } } -- cgit 1.4.1 From 43845c61156bf27955891d68c2e1a2504786b587 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Tue, 4 Feb 2020 03:58:56 -0500 Subject: Add beginnings of input handling --- chat.c | 12 +++++++++++- chat.h | 1 + ui.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) (limited to 'chat.c') diff --git a/chat.c b/chat.c index 162f68f..3402621 100644 --- a/chat.c +++ b/chat.c @@ -15,7 +15,9 @@ */ #include +#include #include +#include #include #include #include @@ -98,8 +100,16 @@ int main(int argc, char *argv[]) { ircFormat("NICK :%s\r\n", nick); ircFormat("USER %s 0 * :%s\r\n", user, real); + struct pollfd fds[2] = { + { .events = POLLIN, .fd = STDIN_FILENO }, + { .events = POLLIN, .fd = irc }, + }; for (;;) { + int nfds = poll(fds, 2, -1); + if (nfds < 0 && errno != EINTR) err(EX_IOERR, "poll"); + + if (fds[0].revents) uiRead(); + if (fds[1].revents) ircRecv(); uiDraw(); - ircRecv(); } } diff --git a/chat.h b/chat.h index 9165c13..8de5df1 100644 --- a/chat.h +++ b/chat.h @@ -115,6 +115,7 @@ void uiShow(void); void uiHide(void); void uiDraw(void); void uiShowID(size_t id); +void uiRead(void); void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str); void uiFormat( size_t id, enum Heat heat, const time_t *time, const char *format, ... diff --git a/ui.c b/ui.c index e2746f1..d69e706 100644 --- a/ui.c +++ b/ui.c @@ -14,6 +14,8 @@ * along with this program. If not, see . */ +#define _XOPEN_SOURCE_EXTENDED + #include #include #include @@ -192,7 +194,7 @@ void uiInit(void) { keypad(input, true); nodelay(input, true); windows.active = windowFor(Network); - //uiShow(); + uiShow(); } void uiDraw(void) { @@ -397,3 +399,44 @@ void uiFormat( assert((size_t)len < sizeof(buf)); uiWrite(id, heat, time, buf); } + +static void keyCode(int code) { + switch (code) { + break; case KEY_RESIZE:; // TODO + break; case KeyFocusIn:; // TODO + break; case KeyFocusOut: windows.active->mark = true; + break; case KeyPasteOn:; // TODO + break; case KeyPasteOff:; // TODO + } +} + +static void keyMeta(wchar_t ch) { + switch (ch) { + break; case L'm': uiWrite(windows.active->id, Cold, NULL, ""); + } +} + +static void keyChar(wchar_t ch) { + switch (ch) { + break; case CTRL(L'L'): clearok(curscr, true); + } +} + +void uiRead(void) { + int ret; + wint_t ch; + static bool meta; + while (ERR != (ret = wget_wch(input, &ch))) { + if (ret == KEY_CODE_YES) { + keyCode(ch); + } else if (ch == '\33') { + meta = true; + continue; + } else if (meta) { + keyMeta(ch); + } else { + keyChar(ch); + } + meta = false; + } +} -- cgit 1.4.1 From 5e9863fa82f674ad8eb05148eade5c859a32c7ba Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Tue, 4 Feb 2020 19:02:54 -0500 Subject: Handle signals in poll loop --- chat.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'chat.c') diff --git a/chat.c b/chat.c index 3402621..3aa4ad2 100644 --- a/chat.c +++ b/chat.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -34,7 +35,7 @@ char *idNames[IDCap] = { enum Color idColors[IDCap] = { [None] = Black, - [Debug] = Red, + [Debug] = Green, [Network] = Gray, }; @@ -42,6 +43,11 @@ size_t idNext = Network + 1; struct Self self; +static volatile sig_atomic_t signals[NSIG]; +static void signalHandler(int signal) { + signals[signal] = 1; +} + int main(int argc, char *argv[]) { setlocale(LC_CTYPE, ""); @@ -100,6 +106,11 @@ int main(int argc, char *argv[]) { ircFormat("NICK :%s\r\n", nick); ircFormat("USER %s 0 * :%s\r\n", user, real); + signal(SIGHUP, signalHandler); + signal(SIGINT, signalHandler); + signal(SIGTERM, signalHandler); + sig_t cursesWinch = signal(SIGWINCH, signalHandler); + struct pollfd fds[2] = { { .events = POLLIN, .fd = STDIN_FILENO }, { .events = POLLIN, .fd = irc }, @@ -108,8 +119,20 @@ int main(int argc, char *argv[]) { int nfds = poll(fds, 2, -1); if (nfds < 0 && errno != EINTR) err(EX_IOERR, "poll"); + if (signals[SIGHUP] || signals[SIGINT] || signals[SIGTERM]) { + break; + } + if (signals[SIGWINCH]) { + signals[SIGWINCH] = 0; + cursesWinch(SIGWINCH); + fds[0].revents = POLLIN; + } + if (fds[0].revents) uiRead(); if (fds[1].revents) ircRecv(); uiDraw(); } + + ircFormat("QUIT\r\n"); + uiHide(); } -- cgit 1.4.1 From f3fb466a31d78431a686981b7d9b718385591bce Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Tue, 4 Feb 2020 19:06:54 -0500 Subject: Only check revents if nfds > 0 If an error occurs, poll leaves the array unmodified. --- chat.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'chat.c') diff --git a/chat.c b/chat.c index 3aa4ad2..1656a53 100644 --- a/chat.c +++ b/chat.c @@ -125,11 +125,11 @@ int main(int argc, char *argv[]) { if (signals[SIGWINCH]) { signals[SIGWINCH] = 0; cursesWinch(SIGWINCH); - fds[0].revents = POLLIN; + uiRead(); } - if (fds[0].revents) uiRead(); - if (fds[1].revents) ircRecv(); + if (nfds > 0 && fds[0].revents) uiRead(); + if (nfds > 0 && fds[1].revents) ircRecv(); uiDraw(); } -- cgit 1.4.1 From 9944dc484bf8cc7d5f1c506610a0593202bb5f92 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Tue, 4 Feb 2020 20:23:55 -0500 Subject: Split showing style codes and word wrapping --- Makefile | 1 + chat.c | 2 +- chat.h | 4 ++ edit.c | 30 ++++++++++++ ui.c | 168 +++++++++++++++++++++++++++++++++++++++++---------------------- 5 files changed, 146 insertions(+), 59 deletions(-) create mode 100644 edit.c (limited to 'chat.c') diff --git a/Makefile b/Makefile index 6ba0ba5..63f719d 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ CFLAGS += -std=c11 -Wall -Wextra -Wpedantic LDLIBS = -lcurses -lcrypto -ltls OBJS += chat.o +OBJS += edit.o OBJS += handle.o OBJS += irc.o OBJS += ui.o diff --git a/chat.c b/chat.c index 1656a53..545eca6 100644 --- a/chat.c +++ b/chat.c @@ -41,7 +41,7 @@ enum Color idColors[IDCap] = { size_t idNext = Network + 1; -struct Self self; +struct Self self = { .color = Default }; static volatile sig_atomic_t signals[NSIG]; static void signalHandler(int signal) { diff --git a/chat.h b/chat.h index 8de5df1..c754357 100644 --- a/chat.h +++ b/chat.h @@ -72,6 +72,7 @@ extern struct Self { char *chanTypes; char *prefixes; char *nick; + enum Color color; } self; static inline void set(char **field, const char *value) { @@ -121,6 +122,9 @@ void uiFormat( size_t id, enum Heat heat, const time_t *time, const char *format, ... ) __attribute__((format(printf, 4, 5))); +const char *editHead(void); +const char *editTail(void); + static inline enum Color hash(const char *str) { if (*str == '~') str++; uint32_t hash = 0; diff --git a/edit.c b/edit.c new file mode 100644 index 0000000..446e0e9 --- /dev/null +++ b/edit.c @@ -0,0 +1,30 @@ +/* Copyright (C) 2020 C. McEnroe + * + * 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 . + */ + +#include +#include +#include +#include + +#include "chat.h" + +const char *editHead(void) { + return "foo\0033bar"; +} + +const char *editTail(void) { + return "baz\3"; +} diff --git a/ui.c b/ui.c index e93c08c..568df8d 100644 --- a/ui.c +++ b/ui.c @@ -268,71 +268,18 @@ static void styleParse(struct Style *style, const char **str, size_t *len) { *len = strcspn(*str, "\2\3\17\26\35\37"); } -static int wordWidth(const char *str) { - size_t len = strcspn(str, " "); - int width = 0; - while (len) { - wchar_t wc; - int n = mbtowc(&wc, str, len); - if (n < 1) return width + len; - width += (iswprint(wc) ? wcwidth(wc) : 0); - str += n; - len -= n; - } - return width; -} - -static void styleAdd(WINDOW *win, const char *str, bool show) { - int y, x, width; - getmaxyx(win, y, width); - +static void statusAdd(const char *str) { size_t len; - int align = 0; struct Style style = Reset; while (*str) { - if (*str == '\t') { - waddch(win, ' '); - getyx(win, y, align); - str++; - } else if (*str == ' ') { - getyx(win, y, x); - const char *word = &str[strspn(str, " ")]; - if (width - x - 1 <= wordWidth(word)) { - waddch(win, '\n'); - getyx(win, y, x); - wmove(win, y, align); - str = word; - } else { - waddch(win, ' '); - str++; - } - } - - const char *code = str; styleParse(&style, &str, &len); - if (show) { - wattr_set(win, A_BOLD | A_REVERSE, 0, NULL); - switch (*code) { - break; case '\2': waddch(win, 'B'); - break; case '\3': waddch(win, 'C'); - break; case '\17': waddch(win, 'O'); - break; case '\26': waddch(win, 'R'); - break; case '\35': waddch(win, 'I'); - break; case '\37': waddch(win, 'U'); - } - if (str - code > 1) waddnstr(win, &code[1], str - &code[1]); - } - - size_t ws = strcspn(str, "\t "); - if (ws < len) len = ws; - wattr_set( - win, + status, style.attr | colorAttr(mapColor(style.fg)), colorPair(mapColor(style.fg), mapColor(style.bg)), NULL ); - waddnstr(win, str, len); + waddnstr(status, str, len); str += len; } } @@ -354,7 +301,7 @@ static void statusUpdate(void) { idColors[window->id] ); if (!window->unread) buf[unread] = '\0'; - styleAdd(status, buf, false); + statusAdd(buf); } wclrtoeol(status); @@ -399,6 +346,61 @@ void uiShowNum(size_t num) { windowShow(window); } +static int wordWidth(const char *str) { + size_t len = strcspn(str, " "); + int width = 0; + while (len) { + wchar_t wc; + int n = mbtowc(&wc, str, len); + if (n < 1) return width + len; + width += (iswprint(wc) ? wcwidth(wc) : 0); + str += n; + len -= n; + } + return width; +} + +static void wordWrap(WINDOW *win, const char *str) { + int y, x, width; + getmaxyx(win, y, width); + + size_t len; + int align = 0; + struct Style style = Reset; + while (*str) { + if (*str == '\t') { + waddch(win, ' '); + getyx(win, y, align); + str++; + } else if (*str == ' ') { + getyx(win, y, x); + const char *word = &str[strspn(str, " ")]; + if (width - x - 1 <= wordWidth(word)) { + waddch(win, '\n'); + getyx(win, y, x); + wmove(win, y, align); + str = word; + } else { + waddch(win, ' '); + str++; + } + } + + styleParse(&style, &str, &len); + size_t ws = strcspn(str, "\t "); + if (ws < len) len = ws; + + wattr_set( + win, + style.attr | colorAttr(mapColor(style.fg)), + colorPair(mapColor(style.fg), mapColor(style.bg)), + NULL + ); + waddnstr(win, str, len); + str += len; + } +} + void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str) { (void)time; struct Window *window = windowFor(id); @@ -410,7 +412,7 @@ void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str) { window->heat = heat; statusUpdate(); } - styleAdd(window->pad, str, false); + wordWrap(window->pad, str); } void uiFormat( @@ -425,6 +427,55 @@ void uiFormat( uiWrite(id, heat, time, buf); } +static void inputAdd(struct Style *style, const char *str) { + size_t len; + while (*str) { + const char *code = str; + styleParse(style, &str, &len); + wattr_set(input, A_BOLD | A_REVERSE, 0, NULL); + switch (*code) { + break; case '\2': waddch(input, 'B'); + break; case '\3': waddch(input, 'C'); + break; case '\17': waddch(input, 'O'); + break; case '\26': waddch(input, 'R'); + break; case '\35': waddch(input, 'I'); + break; case '\37': waddch(input, 'U'); + } + if (str - code > 1) waddnstr(input, &code[1], str - &code[1]); + wattr_set( + input, + style->attr | colorAttr(mapColor(style->fg)), + colorPair(mapColor(style->fg), mapColor(style->bg)), + NULL + ); + waddnstr(input, str, len); + str += len; + } +} + +static void inputUpdate(void) { + wmove(input, 0, 0); + wattr_set( + input, + colorAttr(mapColor(self.color)), + colorPair(mapColor(self.color), -1), + NULL + ); + if (self.nick) { + waddch(input, '<'); + waddstr(input, self.nick); + waddstr(input, "> "); + } + + int y, x; + struct Style style = Reset; + inputAdd(&style, editHead()); + getyx(input, y, x); + inputAdd(&style, editTail()); + wclrtoeol(input); + wmove(input, y, x); +} + static void keyCode(int code) { switch (code) { break; case KEY_RESIZE:; // TODO @@ -467,4 +518,5 @@ void uiRead(void) { } meta = false; } + inputUpdate(); } -- cgit 1.4.1 From 42210e079bcdea26261af577d0802fdc4c3d03b6 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 02:03:21 -0500 Subject: Reflow text on window resize --- chat.c | 2 ++ ui.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 53 insertions(+), 17 deletions(-) (limited to 'chat.c') diff --git a/chat.c b/chat.c index 545eca6..a8b2fa2 100644 --- a/chat.c +++ b/chat.c @@ -122,6 +122,8 @@ int main(int argc, char *argv[]) { if (signals[SIGHUP] || signals[SIGINT] || signals[SIGTERM]) { break; } + // FIXME: Display doesn't update properly when receiving many of these + // until some input? if (signals[SIGWINCH]) { signals[SIGWINCH] = 0; cursesWinch(SIGWINCH); diff --git a/ui.c b/ui.c index b764f84..12205cc 100644 --- a/ui.c +++ b/ui.c @@ -35,6 +35,9 @@ #include "chat.h" +// Annoying stuff from : +#undef lines + #ifndef A_ITALIC #define A_ITALIC A_UNDERLINE #endif @@ -43,16 +46,28 @@ #define RIGHT (COLS - 1) #define WINDOW_LINES (LINES - 2) -enum { - InputCols = 512, - PadLines = 512, -}; - static WINDOW *status; static WINDOW *input; +enum { BufferCap = 512 }; +struct Buffer { + time_t times[BufferCap]; + char *lines[BufferCap]; + size_t len; +}; +static_assert(!(BufferCap & (BufferCap - 1)), "BufferCap is power of two"); + +static void bufferPush(struct Buffer *buffer, time_t time, const char *line) { + size_t i = buffer->len++ % BufferCap; + free(buffer->lines[i]); + buffer->times[i] = time; + buffer->lines[i] = strdup(line); + if (!buffer->lines[i]) err(EX_OSERR, "strdup"); +} + struct Window { size_t id; + struct Buffer buffer; WINDOW *pad; enum Heat heat; int unread; @@ -89,17 +104,15 @@ static struct Window *windowFor(size_t id) { for (window = windows.head; window; window = window->next) { if (window->id == id) return window; } - window = malloc(sizeof(*window)); + window = calloc(1, sizeof(*window)); if (!window) err(EX_OSERR, "malloc"); window->id = id; - window->pad = newpad(PadLines, COLS); - window->heat = Cold; - window->unread = 0; - window->scroll = PadLines; - window->mark = true; + window->pad = newpad(BufferCap, COLS); scrollok(window->pad, true); - wmove(window->pad, PadLines - 1, 0); + wmove(window->pad, BufferCap - 1, 0); + window->scroll = BufferCap; + window->mark = true; windowAdd(window); return window; @@ -190,7 +203,7 @@ void uiInit(void) { colorInit(); status = newwin(1, COLS, 0, 0); - input = newpad(1, InputCols); + input = newpad(1, 512); keypad(input, true); nodelay(input, true); windows.active = windowFor(Network); @@ -380,9 +393,11 @@ static void wordWrap(WINDOW *win, const char *str) { } } -void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str) { - (void)time; +void uiWrite(size_t id, enum Heat heat, const time_t *src, const char *str) { struct Window *window = windowFor(id); + time_t clock = (src ? *src : time(NULL)); + bufferPush(&window->buffer, clock, str); + waddch(window->pad, '\n'); if (window->mark && heat > Cold) { if (!window->unread++) { @@ -406,6 +421,25 @@ void uiFormat( uiWrite(id, heat, time, buf); } +static void reflow(struct Window *window) { + werase(window->pad); + wmove(window->pad, BufferCap - 1, 0); + size_t len = window->buffer.len; + for (size_t i = (len > BufferCap ? len - BufferCap : 0); i < len; ++i) { + waddch(window->pad, '\n'); + wordWrap(window->pad, window->buffer.lines[i % BufferCap]); + } +} + +static void resize(void) { + // FIXME: Only reflow when COLS changes. + for (struct Window *window = windows.head; window; window = window->next) { + wresize(window->pad, BufferCap, COLS); + reflow(window); + } + statusUpdate(); +} + static void inputAdd(struct Style *style, const char *str) { size_t len; while (*str) { @@ -480,7 +514,7 @@ void uiShowNum(size_t num) { static void keyCode(int code) { switch (code) { - break; case KEY_RESIZE:; // TODO + break; case KEY_RESIZE: resize(); break; case KeyFocusIn: unmark(); break; case KeyFocusOut: windows.active->mark = true; break; case KeyPasteOn:; // TODO @@ -490,7 +524,7 @@ static void keyCode(int code) { static void keyMeta(wchar_t ch) { switch (ch) { - break; case L'm': uiWrite(windows.active->id, Cold, NULL, ""); + break; case L'm': waddch(windows.active->pad, '\n'); break; default: { if (ch >= L'0' && ch <= L'9') uiShowNum(ch - L'0'); } -- cgit 1.4.1 From 2d5f608cc5dde360be4acdf5b0006a78e2b433c8 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 17:58:49 -0500 Subject: Fix SIGWINCH handling curses is dumb. --- chat.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'chat.c') diff --git a/chat.c b/chat.c index a8b2fa2..2d58b1e 100644 --- a/chat.c +++ b/chat.c @@ -122,11 +122,12 @@ int main(int argc, char *argv[]) { if (signals[SIGHUP] || signals[SIGINT] || signals[SIGTERM]) { break; } - // FIXME: Display doesn't update properly when receiving many of these - // until some input? if (signals[SIGWINCH]) { signals[SIGWINCH] = 0; cursesWinch(SIGWINCH); + // XXX: For some reason, calling uiDraw() here is the only way to + // get uiRead() to properly receive KEY_RESIZE. + uiDraw(); uiRead(); } -- cgit 1.4.1 From 7c0e9cf3d2e83814fab3bb4bb09f7b955c2afaca Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 21:57:23 -0500 Subject: Add /quit --- chat.c | 13 ++++++++----- chat.h | 1 + command.c | 5 +++++ 3 files changed, 14 insertions(+), 5 deletions(-) (limited to 'chat.c') diff --git a/chat.c b/chat.c index 2d58b1e..1ad2833 100644 --- a/chat.c +++ b/chat.c @@ -115,13 +115,12 @@ int main(int argc, char *argv[]) { { .events = POLLIN, .fd = STDIN_FILENO }, { .events = POLLIN, .fd = irc }, }; - for (;;) { + while (!self.quit) { int nfds = poll(fds, 2, -1); if (nfds < 0 && errno != EINTR) err(EX_IOERR, "poll"); - if (signals[SIGHUP] || signals[SIGINT] || signals[SIGTERM]) { - break; - } + if (signals[SIGHUP]) self.quit = "zzz"; + if (signals[SIGINT] || signals[SIGTERM]) break; if (signals[SIGWINCH]) { signals[SIGWINCH] = 0; cursesWinch(SIGWINCH); @@ -136,6 +135,10 @@ int main(int argc, char *argv[]) { uiDraw(); } - ircFormat("QUIT\r\n"); + if (self.quit) { + ircFormat("QUIT :%s\r\n", self.quit); + } else { + ircFormat("QUIT\r\n"); + } uiHide(); } diff --git a/chat.h b/chat.h index b73cf40..5b3c01c 100644 --- a/chat.h +++ b/chat.h @@ -75,6 +75,7 @@ extern struct Self { char *nick; char *user; enum Color color; + char *quit; } self; static inline void set(char **field, const char *value) { diff --git a/command.c b/command.c index 0843bd3..7fb98af 100644 --- a/command.c +++ b/command.c @@ -56,12 +56,17 @@ static void commandMe(size_t id, char *params) { commandPrivmsg(id, buf); } +static void commandQuit(size_t id, char *params) { + set(&self.quit, (params ? params : "Goodbye")); +} + static const struct Handler { const char *cmd; Command *fn; } Commands[] = { { "/me", commandMe }, { "/notice", commandNotice }, + { "/quit", commandQuit }, { "/quote", commandQuote }, }; -- cgit 1.4.1 From 27eaddb6b9524b43448f4e5c88ac74bbe8fdb3a5 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 22:49:56 -0500 Subject: Use getopt_config to load options I'm really getting a lot of use out of this config.c huh. --- Makefile | 1 + catgirl.1 | 41 ++++++++++----- chat.c | 20 ++++++- chat.h | 7 +++ config.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 234 insertions(+), 13 deletions(-) create mode 100644 config.c (limited to 'chat.c') diff --git a/Makefile b/Makefile index 05f8bb8..213ecb5 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ LDLIBS = -lcurses -lcrypto -ltls OBJS += chat.o OBJS += command.o +OBJS += config.o OBJS += edit.o OBJS += handle.o OBJS += irc.o diff --git a/catgirl.1 b/catgirl.1 index fb031c2..91c4ff4 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -19,6 +19,7 @@ .Op Fl r Ar real .Op Fl u Ar user .Op Fl w Ar pass +.Op Ar config ... . .Sh DESCRIPTION The @@ -27,9 +28,25 @@ program is a curses TLS-only IRC client. . .Pp +Options can be loaded from files +listed on the command line. +Files are searched for in +.Pa $XDG_CONFIG_DIRS/catgirl +unless the path starts with +.Ql / +or +.Ql \&. . +Each option is placed on a line, +and lines beginning with +.Ql # +are ignored. +The options are listed below +following their corresponding flags. +. +.Pp The arguments are as follows: .Bl -tag -width Ds -.It Fl a Ar user Ns : Ns Ar pass +.It Fl a Ar user Ns : Ns Ar pass , Cm sasl-plain = Ar user Ns : Ns Ar pass Authenticate as .Ar user with @@ -40,7 +57,7 @@ in plain text, it is recommended to use SASL EXTERNAL instead with .Fl e . . -.It Fl c Ar path +.It Fl c Ar path , Cm cert = Ar path Load the TLS client certificate from .Ar path . If the private key is in a separate file, @@ -50,52 +67,52 @@ With .Fl e , authenticate using SASL EXTERNAL. . -.It Fl e +.It Fl e , Cm sasl-external Authenticate using SASL EXTERNAL, also known as CertFP. The TLS client certificate is loaded with .Fl c . . -.It Fl h Ar host +.It Fl h Ar host , Cm host = Ar host Connect to .Ar host . . -.It Fl j Ar join +.It Fl j Ar join , Cm join = Ar join Join the comma-separated list of channels .Ar join . . -.It Fl k Ar path +.It Fl k Ar path , Cm priv = Ar priv Load the TLS client private key from .Ar path . . -.It Fl n Ar nick +.It Fl n Ar nick , Cm nick = Ar nick Set nickname to .Ar nick . The default nickname is the user's name. . -.It Fl p Ar port +.It Fl p Ar port , Cm port = Ar port Connect to .Ar port . The default port is 6697. . -.It Fl r Ar real +.It Fl r Ar real , Cm real = Ar real Set realname to .Ar real . The default realname is the same as the nickname. . -.It Fl u Ar user +.It Fl u Ar user , Cm user = Ar user Set username to .Ar user . The default username is the same as the nickname. . -.It Fl v +.It Fl v , Cm debug Log raw IRC messages to the .Sy window as well as standard error if it is not a terminal. . -.It Fl w Ar pass +.It Fl w Ar pass , Cm pass = Ar pass Log in with the server password .Ar pass . .El diff --git a/chat.c b/chat.c index 1ad2833..c67d8a9 100644 --- a/chat.c +++ b/chat.c @@ -63,8 +63,26 @@ int main(int argc, char *argv[]) { const char *user = NULL; const char *real = NULL; + const char *Opts = "!a:c:eh:j:k:n:p:r:u:vw:"; + const struct option LongOpts[] = { + { "insecure", no_argument, NULL, '!' }, + { "sasl-plain", required_argument, NULL, 'a' }, + { "cert", required_argument, NULL, 'c' }, + { "sasl-external", no_argument, NULL, 'e' }, + { "host", required_argument, NULL, 'h' }, + { "join", required_argument, NULL, 'j' }, + { "priv", required_argument, NULL, 'k' }, + { "nick", required_argument, NULL, 'n' }, + { "port", required_argument, NULL, 'p' }, + { "real", required_argument, NULL, 'r' }, + { "user", required_argument, NULL, 'u' }, + { "debug", no_argument, NULL, 'v' }, + { "pass", required_argument, NULL, 'w' }, + {0}, + }; + int opt; - while (0 < (opt = getopt(argc, argv, "!a:c:eh:j:k:n:p:r:u:vw:"))) { + while (0 < (opt = getopt_config(argc, argv, Opts, LongOpts, NULL))) { switch (opt) { break; case '!': insecure = true; break; case 'a': sasl = true; self.plain = optarg; diff --git a/chat.h b/chat.h index 9317843..57d4ba6 100644 --- a/chat.h +++ b/chat.h @@ -15,6 +15,7 @@ */ #include +#include #include #include #include @@ -139,6 +140,12 @@ void edit(size_t id, enum Edit op, wchar_t ch); char *editHead(void); char *editTail(void); +FILE *configOpen(const char *path, const char *mode); +int getopt_config( + int argc, char *const *argv, + const char *optstring, const struct option *longopts, int *longindex +); + static inline enum Color hash(const char *str) { if (*str == '~') str++; uint32_t hash = 0; diff --git a/config.c b/config.c new file mode 100644 index 0000000..b3e42f9 --- /dev/null +++ b/config.c @@ -0,0 +1,178 @@ +/* Copyright (C) 2019 C. McEnroe + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "chat.h" + +#define CONFIG_DIR "catgirl" + +FILE *configOpen(const char *path, const char *mode) { + if (path[0] == '/' || path[0] == '.') goto local; + + const char *home = getenv("HOME"); + const char *configHome = getenv("XDG_CONFIG_HOME"); + const char *configDirs = getenv("XDG_CONFIG_DIRS"); + + char buf[PATH_MAX]; + if (configHome) { + snprintf(buf, sizeof(buf), "%s/" CONFIG_DIR "/%s", configHome, path); + } else { + if (!home) goto local; + snprintf(buf, sizeof(buf), "%s/.config/" CONFIG_DIR "/%s", home, path); + } + FILE *file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) return NULL; + + if (!configDirs) configDirs = "/etc/xdg"; + while (*configDirs) { + size_t len = strcspn(configDirs, ":"); + snprintf( + buf, sizeof(buf), "%.*s/" CONFIG_DIR "/%s", + (int)len, configDirs, path + ); + file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) return NULL; + configDirs += len; + if (*configDirs) configDirs++; + } + +local: + return fopen(path, mode); +} + +#define WS "\t " + +static const char *path; +static FILE *file; +static size_t num; +static char *line; +static size_t cap; + +static int clean(int opt) { + if (file) fclose(file); + free(line); + line = NULL; + cap = 0; + return opt; +} + +int getopt_config( + int argc, char *const *argv, + const char *optstring, const struct option *longopts, int *longindex +) { + static int opt; + if (opt >= 0) { + opt = getopt_long(argc, argv, optstring, longopts, longindex); + } + if (opt >= 0) return opt; + + for (;;) { + if (!file) { + if (optind < argc) { + num = 0; + path = argv[optind++]; + file = configOpen(path, "r"); + if (!file) { + warn("%s", path); + return clean('?'); + } + } else { + return clean(-1); + } + } + + for (;;) { + ssize_t llen = getline(&line, &cap, file); + if (ferror(file)) { + warn("%s", path); + return clean('?'); + } + if (llen <= 0) break; + if (line[llen - 1] == '\n') line[llen - 1] = '\0'; + num++; + + char *name = line + strspn(line, WS); + size_t len = strcspn(name, WS "="); + if (!name[0] || name[0] == '#') continue; + + const struct option *option; + for (option = longopts; option->name; ++option) { + if (strlen(option->name) != len) continue; + if (!strncmp(option->name, name, len)) break; + } + if (!option->name) { + warnx( + "%s:%zu: unrecognized option `%.*s'", + path, num, (int)len, name + ); + return clean('?'); + } + + char *equal = &name[len] + strspn(&name[len], WS); + if (*equal && *equal != '=') { + warnx( + "%s:%zu: option `%s' missing equals sign", + path, num, option->name + ); + return clean('?'); + } + if (option->has_arg == no_argument && *equal) { + warnx( + "%s:%zu: option `%s' doesn't allow an argument", + path, num, option->name + ); + return clean('?'); + } + if (option->has_arg == required_argument && !*equal) { + warnx( + "%s:%zu: option `%s' requires an argument", + path, num, option->name + ); + return clean(':'); + } + + optarg = NULL; + if (*equal) { + char *arg = &equal[1] + strspn(&equal[1], WS); + optarg = strdup(arg); + if (!optarg) { + warn("getopt_config"); + return clean('?'); + } + } + + if (longindex) *longindex = option - longopts; + if (option->flag) { + *option->flag = option->val; + return 0; + } else { + return option->val; + } + } + + fclose(file); + file = NULL; + } +} -- cgit 1.4.1 From 839cc362a85e3cf463cd0cfe6c092ca0978b8c29 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 22:51:45 -0500 Subject: Handle errors from getopt --- chat.c | 1 + 1 file changed, 1 insertion(+) (limited to 'chat.c') diff --git a/chat.c b/chat.c index c67d8a9..115fe38 100644 --- a/chat.c +++ b/chat.c @@ -97,6 +97,7 @@ int main(int argc, char *argv[]) { break; case 'u': user = optarg; break; case 'v': self.debug = true; break; case 'w': pass = optarg; + break; default: return EX_USAGE; } } if (!host) errx(EX_USAGE, "host required"); -- cgit 1.4.1 From 8b3bf897c2b7a14ff6a4c096b9969eaeb695a9e0 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 02:21:04 -0500 Subject: Search for cert and priv in config dirs --- chat.c | 14 +++++++++++++- chat.h | 2 +- irc.c | 37 ++++++++++++++++++++++++++++++++++--- 3 files changed, 48 insertions(+), 5 deletions(-) (limited to 'chat.c') diff --git a/chat.c b/chat.c index 115fe38..c487722 100644 --- a/chat.c +++ b/chat.c @@ -111,7 +111,19 @@ int main(int argc, char *argv[]) { set(&self.chanTypes, "#&"); set(&self.prefixes, "@+"); - ircConfig(insecure, cert, priv); + FILE *certFile = NULL; + FILE *privFile = NULL; + if (cert) { + certFile = configOpen(cert, "r"); + if (!certFile) err(EX_NOINPUT, "%s", cert); + } + if (priv) { + privFile = configOpen(priv, "r"); + if (!privFile) err(EX_NOINPUT, "%s", priv); + } + ircConfig(insecure, certFile, privFile); + if (certFile) fclose(certFile); + if (privFile) fclose(privFile); uiInit(); uiShowID(Network); diff --git a/chat.h b/chat.h index 57d4ba6..112530d 100644 --- a/chat.h +++ b/chat.h @@ -105,7 +105,7 @@ struct Message { char *params[ParamCap]; }; -void ircConfig(bool insecure, const char *cert, const char *priv); +void ircConfig(bool insecure, FILE *cert, FILE *priv); int ircConnect(const char *host, const char *port); void ircRecv(void); void ircSend(const char *ptr, size_t len); diff --git a/irc.c b/irc.c index 2d6f00b..05f8f9d 100644 --- a/irc.c +++ b/irc.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -31,7 +32,22 @@ struct tls *client; -void ircConfig(bool insecure, const char *cert, const char *priv) { +static byte *readFile(size_t *len, FILE *file) { + struct stat stat; + int error = fstat(fileno(file), &stat); + if (error) err(EX_IOERR, "fstat"); + + byte *buf = malloc(stat.st_size); + if (!buf) err(EX_OSERR, "malloc"); + + rewind(file); + *len = fread(buf, 1, stat.st_size, file); + if (ferror(file)) err(EX_IOERR, "fread"); + + return buf; +} + +void ircConfig(bool insecure, FILE *cert, FILE *priv) { struct tls_config *config = tls_config_new(); if (!config) errx(EX_SOFTWARE, "tls_config_new"); @@ -49,13 +65,28 @@ void ircConfig(bool insecure, const char *cert, const char *priv) { } if (cert) { - error = tls_config_set_keypair_file(config, cert, (priv ? priv : cert)); + size_t len; + byte *buf = readFile(&len, cert); + error = tls_config_set_cert_mem(config, buf, len); + if (error) { + errx( + EX_CONFIG, "tls_config_set_cert_mem: %s", + tls_config_error(config) + ); + } + if (priv) { + free(buf); + buf = readFile(&len, priv); + } + error = tls_config_set_key_mem(config, buf, len); if (error) { errx( - EX_SOFTWARE, "tls_config_set_keypair_file: %s", + EX_CONFIG, "tls_config_set_key_mem: %s", tls_config_error(config) ); } + explicit_bzero(buf, len); + free(buf); } client = tls_client(); -- cgit 1.4.1 From fe5fd897052abd1909d1536056936a0417666459 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 7 Feb 2020 21:30:25 -0500 Subject: Populate completion with commands --- Makefile | 1 + chat.c | 1 + chat.h | 8 +++++ command.c | 6 ++++ complete.c | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ edit.c | 3 ++ ui.c | 1 + 7 files changed, 130 insertions(+) create mode 100644 complete.c (limited to 'chat.c') diff --git a/Makefile b/Makefile index 5380d20..48aba7b 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ LDLIBS = -lcrypto -ltls -lncursesw OBJS += chat.o OBJS += command.o +OBJS += complete.o OBJS += config.o OBJS += edit.o OBJS += handle.o diff --git a/chat.c b/chat.c index c487722..91da6e3 100644 --- a/chat.c +++ b/chat.c @@ -110,6 +110,7 @@ int main(int argc, char *argv[]) { set(&self.network, host); set(&self.chanTypes, "#&"); set(&self.prefixes, "@+"); + commandComplete(); FILE *certFile = NULL; FILE *privFile = NULL; diff --git a/chat.h b/chat.h index a327620..f164e7a 100644 --- a/chat.h +++ b/chat.h @@ -118,6 +118,7 @@ void command(size_t id, char *input); const char *commandIsPrivmsg(size_t id, const char *input); const char *commandIsNotice(size_t id, const char *input); const char *commandIsAction(size_t id, const char *input); +void commandComplete(void); enum Heat { Cold, Warm, Hot }; void uiInit(void); @@ -140,12 +141,19 @@ enum Edit { EditKill, EditErase, EditInsert, + EditComplete, EditEnter, }; void edit(size_t id, enum Edit op, wchar_t ch); char *editHead(void); char *editTail(void); +const char *complete(size_t id, const char *prefix); +void completeAccept(void); +void completeReject(void); +void completeAdd(size_t id, const char *str, enum Color color); +void completeTouch(size_t id, const char *str, enum Color color); + FILE *configOpen(const char *path, const char *mode); int getopt_config( int argc, char *const *argv, diff --git a/command.c b/command.c index 3215322..41aacc9 100644 --- a/command.c +++ b/command.c @@ -136,3 +136,9 @@ void command(size_t id, char *input) { } } } + +void commandComplete(void) { + for (size_t i = 0; i < ARRAY_LEN(Commands); ++i) { + completeAdd(None, Commands[i].cmd, Default); + } +} diff --git a/complete.c b/complete.c new file mode 100644 index 0000000..b8f2dfc --- /dev/null +++ b/complete.c @@ -0,0 +1,110 @@ +/* Copyright (C) 2020 C. McEnroe + * + * 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 . + */ + +#include +#include +#include +#include +#include + +#include "chat.h" + +struct Node { + size_t id; + char *str; + enum Color color; + struct Node *prev; + struct Node *next; +}; + +static struct Node *alloc(size_t id, const char *str, enum Color color) { + struct Node *node = malloc(sizeof(*node)); + if (!node) err(EX_OSERR, "malloc"); + node->id = id; + node->str = strdup(str); + if (!node->str) err(EX_OSERR, "strdup"); + node->color = color; + node->prev = NULL; + node->next = NULL; + return node; +} + +static struct Node *head; +static struct Node *tail; + +static struct Node *detach(struct Node *node) { + if (node->prev) node->prev->next = node->next; + if (node->next) node->next->prev = node->prev; + if (head == node) head = node->next; + if (tail == node) tail = node->prev; + node->prev = NULL; + node->next = NULL; + return node; +} + +static struct Node *prepend(struct Node *node) { + node->prev = NULL; + node->next = head; + if (head) head->prev = node; + head = node; + if (!tail) tail = node; + return node; +} + +static struct Node *append(struct Node *node) { + node->next = NULL; + node->prev = tail; + if (tail) tail->next = node; + tail = node; + if (!head) head = node; + return node; +} + +static struct Node *find(size_t id, const char *str) { + for (struct Node *node = head; node; node = node->next) { + if (node->id == id && !strcmp(node->str, str)) return node; + } + return NULL; +} + +void completeAdd(size_t id, const char *str, enum Color color) { + if (!find(id, str)) append(alloc(id, str, color)); +} + +void completeTouch(size_t id, const char *str, enum Color color) { + struct Node *node = find(id, str); + prepend(node ? detach(node) : alloc(id, str, color)); +} + +static struct Node *match; + +const char *complete(size_t id, const char *prefix) { + for (match = (match ? match->next : head); match; match = match->next) { + if (match->id && match->id != id) continue; + if (strncasecmp(match->str, prefix, strlen(prefix))) continue; + return match->str; + } + return NULL; +} + +void completeAccept(void) { + if (match) prepend(detach(match)); + match = NULL; +} + +void completeReject(void) { + match = NULL; +} diff --git a/edit.c b/edit.c index b6edb98..0c50f33 100644 --- a/edit.c +++ b/edit.c @@ -73,6 +73,9 @@ void edit(size_t id, enum Edit op, wchar_t ch) { reserve(pos, 1); if (pos < Cap) buf[pos++] = ch; } + break; case EditComplete: { + // TODO + } break; case EditEnter: { pos = 0; command(id, editTail()); diff --git a/ui.c b/ui.c index 147381e..5a8f155 100644 --- a/ui.c +++ b/ui.c @@ -596,6 +596,7 @@ static void keyCtrl(wchar_t ch) { break; case L'A': edit(id, EditHome, 0); break; case L'E': edit(id, EditEnd, 0); break; case L'H': edit(id, EditErase, 0); + break; case L'I': edit(id, EditComplete, 0); break; case L'J': edit(id, EditEnter, 0); break; case L'L': clearok(curscr, true); break; case L'U': edit(id, EditKill, 0); -- cgit 1.4.1 From 72d8749454820cf025f05a8ab9cc1cff4c8c5b6e Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 17:04:25 -0500 Subject: Check signals after file descriptors If a signal happens while processing an FD, it should be handled immediately, rather than waiting for another poll return. --- chat.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'chat.c') diff --git a/chat.c b/chat.c index 91da6e3..b3e6825 100644 --- a/chat.c +++ b/chat.c @@ -150,6 +150,8 @@ int main(int argc, char *argv[]) { while (!self.quit) { int nfds = poll(fds, 2, -1); if (nfds < 0 && errno != EINTR) err(EX_IOERR, "poll"); + if (nfds > 0 && fds[0].revents) uiRead(); + if (nfds > 0 && fds[1].revents) ircRecv(); if (signals[SIGHUP]) self.quit = "zzz"; if (signals[SIGINT] || signals[SIGTERM]) break; @@ -162,8 +164,6 @@ int main(int argc, char *argv[]) { uiRead(); } - if (nfds > 0 && fds[0].revents) uiRead(); - if (nfds > 0 && fds[1].revents) ircRecv(); uiDraw(); } -- cgit 1.4.1 From 8128edc7eb1e7a86e82d5936ec1100e1f9912f54 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 17:22:51 -0500 Subject: Handle SIGCHLD --- chat.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'chat.c') diff --git a/chat.c b/chat.c index b3e6825..372cbbd 100644 --- a/chat.c +++ b/chat.c @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include @@ -141,6 +143,7 @@ int main(int argc, char *argv[]) { signal(SIGHUP, signalHandler); signal(SIGINT, signalHandler); signal(SIGTERM, signalHandler); + signal(SIGCHLD, signalHandler); sig_t cursesWinch = signal(SIGWINCH, signalHandler); struct pollfd fds[2] = { @@ -155,6 +158,25 @@ int main(int argc, char *argv[]) { if (signals[SIGHUP]) self.quit = "zzz"; if (signals[SIGINT] || signals[SIGTERM]) break; + + if (signals[SIGCHLD]) { + int status; + while (0 < waitpid(-1, &status, WNOHANG)) { + if (WIFEXITED(status) && WEXITSTATUS(status)) { + uiFormat( + Network, Warm, NULL, + "Process exits with status %d", WEXITSTATUS(status) + ); + } else if (WIFSIGNALED(status)) { + uiFormat( + Network, Warm, NULL, + "Process terminates from %s", + strsignal(WTERMSIG(status)) + ); + } + } + } + if (signals[SIGWINCH]) { signals[SIGWINCH] = 0; cursesWinch(SIGWINCH); -- cgit 1.4.1 From 156282c95d523b0c19f5409eb15cd53fc3211894 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 17:42:19 -0500 Subject: Add procPipe for subprocesses --- chat.c | 30 ++++++++++++++++++++++++++---- chat.h | 2 ++ 2 files changed, 28 insertions(+), 4 deletions(-) (limited to 'chat.c') diff --git a/chat.c b/chat.c index 372cbbd..ca35d7d 100644 --- a/chat.c +++ b/chat.c @@ -45,6 +45,21 @@ size_t idNext = Network + 1; struct Self self = { .color = Default }; +int procPipe[2] = { -1, -1 }; + +static void pipeRead(void) { + char buf[1024]; + ssize_t len = read(procPipe[0], buf, sizeof(buf) - 1); + if (len < 0) err(EX_IOERR, "read"); + if (!len) return; + buf[len - 1] = '\0'; + char *ptr = buf; + while (ptr) { + char *line = strsep(&ptr, "\n"); + uiFormat(Network, Warm, NULL, "%s", line); + } +} + static volatile sig_atomic_t signals[NSIG]; static void signalHandler(int signal) { signals[signal] = 1; @@ -146,15 +161,22 @@ int main(int argc, char *argv[]) { signal(SIGCHLD, signalHandler); sig_t cursesWinch = signal(SIGWINCH, signalHandler); - struct pollfd fds[2] = { + int error = pipe(procPipe); + if (error) err(EX_OSERR, "pipe"); + + struct pollfd fds[3] = { { .events = POLLIN, .fd = STDIN_FILENO }, { .events = POLLIN, .fd = irc }, + { .events = POLLIN, .fd = procPipe[0] }, }; while (!self.quit) { - int nfds = poll(fds, 2, -1); + int nfds = poll(fds, ARRAY_LEN(fds), -1); if (nfds < 0 && errno != EINTR) err(EX_IOERR, "poll"); - if (nfds > 0 && fds[0].revents) uiRead(); - if (nfds > 0 && fds[1].revents) ircRecv(); + if (nfds > 0) { + if (fds[0].revents) uiRead(); + if (fds[1].revents) ircRecv(); + if (fds[2].revents) pipeRead(); + } if (signals[SIGHUP]) self.quit = "zzz"; if (signals[SIGINT] || signals[SIGTERM]) break; diff --git a/chat.h b/chat.h index 34e1812..909527e 100644 --- a/chat.h +++ b/chat.h @@ -28,6 +28,8 @@ typedef unsigned char byte; +int procPipe[2]; + enum Color { White, Black, Blue, Green, Red, Brown, Magenta, Orange, Yellow, LightGreen, Cyan, LightCyan, LightBlue, Pink, Gray, LightGray, -- cgit 1.4.1 From 362d779b61adc3f59ef7b4617b3bb7a19f024048 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 19:12:05 -0500 Subject: Set FDs CLOEXEC --- chat.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'chat.c') diff --git a/chat.c b/chat.c index ca35d7d..c0c2d28 100644 --- a/chat.c +++ b/chat.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -164,6 +165,10 @@ int main(int argc, char *argv[]) { int error = pipe(procPipe); if (error) err(EX_OSERR, "pipe"); + fcntl(irc, F_SETFD, FD_CLOEXEC); + fcntl(procPipe[0], F_SETFD, FD_CLOEXEC); + fcntl(procPipe[1], F_SETFD, FD_CLOEXEC); + struct pollfd fds[3] = { { .events = POLLIN, .fd = STDIN_FILENO }, { .events = POLLIN, .fd = irc }, -- cgit 1.4.1 From 2db17e83a914586fd351437ac5323713f1e66478 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 21:21:21 -0500 Subject: Allow overriding the /open utility --- catgirl.1 | 8 ++++++++ chat.c | 4 +++- chat.h | 1 + url.c | 14 ++++++++++---- 4 files changed, 22 insertions(+), 5 deletions(-) (limited to 'chat.c') diff --git a/catgirl.1 b/catgirl.1 index f489d07..6129b71 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -9,6 +9,7 @@ .Sh SYNOPSIS .Nm .Op Fl ev +.Op Fl O Ar open .Op Fl a Ar auth .Op Fl c Ar cert .Op Fl h Ar host @@ -46,6 +47,13 @@ following their corresponding flags. .Pp The arguments are as follows: .Bl -tag -width Ds +.It Fl O Ar util , Cm open = Ar util +Set the command used by +.Ic /open . +The default is the first available of +.Xr open 1 , +.Xr xdg-open 1 . +. .It Fl a Ar user Ns : Ns Ar pass , Cm sasl-plain = Ar user Ns : Ns Ar pass Authenticate as .Ar user diff --git a/chat.c b/chat.c index c0c2d28..77aa61d 100644 --- a/chat.c +++ b/chat.c @@ -81,9 +81,10 @@ int main(int argc, char *argv[]) { const char *user = NULL; const char *real = NULL; - const char *Opts = "!a:c:eh:j:k:n:p:r:u:vw:"; + const char *Opts = "!O:a:c:eh:j:k:n:p:r:u:vw:"; const struct option LongOpts[] = { { "insecure", no_argument, NULL, '!' }, + { "open", required_argument, NULL, 'O' }, { "sasl-plain", required_argument, NULL, 'a' }, { "cert", required_argument, NULL, 'c' }, { "sasl-external", no_argument, NULL, 'e' }, @@ -103,6 +104,7 @@ int main(int argc, char *argv[]) { while (0 < (opt = getopt_config(argc, argv, Opts, LongOpts, NULL))) { switch (opt) { break; case '!': insecure = true; + break; case 'O': urlOpenUtil = optarg; break; case 'a': sasl = true; self.plain = optarg; break; case 'c': cert = optarg; break; case 'e': sasl = true; diff --git a/chat.h b/chat.h index 583107a..3084359 100644 --- a/chat.h +++ b/chat.h @@ -169,6 +169,7 @@ void completeClear(size_t id); size_t completeID(const char *str); enum Color completeColor(size_t id, const char *str); +extern const char *urlOpenUtil; void urlScan(size_t id, const char *nick, const char *mesg); void urlOpenCount(size_t id, size_t count); void urlOpenMatch(size_t id, const char *str); diff --git a/url.c b/url.c index 1396765..c9c4d5c 100644 --- a/url.c +++ b/url.c @@ -95,7 +95,8 @@ void urlScan(size_t id, const char *nick, const char *mesg) { } } -static const char *OpenBins[] = { "open", "xdg-open" }; +const char *urlOpenUtil; +static const char *OpenUtils[] = { "open", "xdg-open" }; static void urlOpen(const char *url) { pid_t pid = fork(); @@ -105,10 +106,15 @@ static void urlOpen(const char *url) { close(STDIN_FILENO); dup2(procPipe[1], STDOUT_FILENO); dup2(procPipe[1], STDERR_FILENO); - for (size_t i = 0; i < ARRAY_LEN(OpenBins); ++i) { - execlp(OpenBins[i], OpenBins[i], url, NULL); + if (urlOpenUtil) { + execlp(urlOpenUtil, urlOpenUtil, url, NULL); + warn("%s", urlOpenUtil); + _exit(EX_CONFIG); + } + for (size_t i = 0; i < ARRAY_LEN(OpenUtils); ++i) { + execlp(OpenUtils[i], OpenUtils[i], url, NULL); if (errno != ENOENT) { - warn("%s", OpenBins[i]); + warn("%s", OpenUtils[i]); _exit(EX_CONFIG); } } -- cgit 1.4.1 From 3e6868414811be8902e6973c78ef2010b26a9e08 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 21:44:50 -0500 Subject: Add /copy --- catgirl.1 | 17 ++++++++++++++++- chat.c | 4 +++- chat.h | 2 ++ command.c | 5 +++++ url.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 2 deletions(-) (limited to 'chat.c') diff --git a/catgirl.1 b/catgirl.1 index 6129b71..4dabb4f 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -9,6 +9,7 @@ .Sh SYNOPSIS .Nm .Op Fl ev +.Op Fl C Ar copy .Op Fl O Ar open .Op Fl a Ar auth .Op Fl c Ar cert @@ -47,8 +48,17 @@ following their corresponding flags. .Pp The arguments are as follows: .Bl -tag -width Ds +.It Fl C Ar util , Cm copy = Ar util +Set the utility used by +.Ic /copy . +The default is the first available of +.Xr pbcopy 1 , +.Xr wl-copy 1 , +.Xr xclip 1 , +.Xr xsel 1 . +. .It Fl O Ar util , Cm open = Ar util -Set the command used by +Set the utility used by .Ic /open . The default is the first available of .Xr open 1 , @@ -160,6 +170,11 @@ Show or set the topic of the channel. .Bl -tag -width Ds .It Ic /close Op Ar name | num Close the named, numbered or current window. +.It Ic /copy Op Ar nick | substring +Copy the most recent URL from +.Ar nick +or matching +.Ar substring . .It Ic /debug Toggle logging in the .Sy diff --git a/chat.c b/chat.c index 77aa61d..dbad242 100644 --- a/chat.c +++ b/chat.c @@ -81,9 +81,10 @@ int main(int argc, char *argv[]) { const char *user = NULL; const char *real = NULL; - const char *Opts = "!O:a:c:eh:j:k:n:p:r:u:vw:"; + const char *Opts = "!C:O:a:c:eh:j:k:n:p:r:u:vw:"; const struct option LongOpts[] = { { "insecure", no_argument, NULL, '!' }, + { "copy", required_argument, NULL, 'C' }, { "open", required_argument, NULL, 'O' }, { "sasl-plain", required_argument, NULL, 'a' }, { "cert", required_argument, NULL, 'c' }, @@ -104,6 +105,7 @@ int main(int argc, char *argv[]) { while (0 < (opt = getopt_config(argc, argv, Opts, LongOpts, NULL))) { switch (opt) { break; case '!': insecure = true; + break; case 'C': urlCopyUtil = optarg; break; case 'O': urlOpenUtil = optarg; break; case 'a': sasl = true; self.plain = optarg; break; case 'c': cert = optarg; diff --git a/chat.h b/chat.h index 3084359..8bc8e81 100644 --- a/chat.h +++ b/chat.h @@ -170,9 +170,11 @@ size_t completeID(const char *str); enum Color completeColor(size_t id, const char *str); extern const char *urlOpenUtil; +extern const char *urlCopyUtil; void urlScan(size_t id, const char *nick, const char *mesg); void urlOpenCount(size_t id, size_t count); void urlOpenMatch(size_t id, const char *str); +void urlCopyMatch(size_t id, const char *str); FILE *configOpen(const char *path, const char *mode); int getopt_config( diff --git a/command.c b/command.c index 4100928..feb52b7 100644 --- a/command.c +++ b/command.c @@ -154,11 +154,16 @@ static void commandOpen(size_t id, char *params) { } } +static void commandCopy(size_t id, char *params) { + urlCopyMatch(id, params); +} + static const struct Handler { const char *cmd; Command *fn; } Commands[] = { { "/close", commandClose }, + { "/copy", commandCopy }, { "/debug", commandDebug }, { "/join", commandJoin }, { "/me", commandMe }, diff --git a/url.c b/url.c index c9c4d5c..7ab1e53 100644 --- a/url.c +++ b/url.c @@ -122,6 +122,47 @@ static void urlOpen(const char *url) { _exit(EX_CONFIG); } +const char *urlCopyUtil; +static const char *CopyUtils[] = { "pbcopy", "wl-copy", "xclip", "xsel" }; + +static void urlCopy(const char *url) { + int rw[2]; + int error = pipe(rw); + if (error) err(EX_OSERR, "pipe"); + + ssize_t len = write(rw[1], url, strlen(url)); + if (len < 0) err(EX_IOERR, "write"); + + error = close(rw[1]); + if (error) err(EX_IOERR, "close"); + + pid_t pid = fork(); + if (pid < 0) err(EX_OSERR, "fork"); + if (pid) { + close(rw[0]); + return; + } + + dup2(rw[0], STDIN_FILENO); + dup2(procPipe[1], STDOUT_FILENO); + dup2(procPipe[1], STDERR_FILENO); + close(rw[0]); + if (urlCopyUtil) { + execlp(urlCopyUtil, urlCopyUtil, NULL); + warn("%s", urlCopyUtil); + _exit(EX_CONFIG); + } + for (size_t i = 0; i < ARRAY_LEN(CopyUtils); ++i) { + execlp(CopyUtils[i], CopyUtils[i], NULL); + if (errno != ENOENT) { + warn("%s", CopyUtils[i]); + _exit(EX_CONFIG); + } + } + warnx("no copy utility found"); + _exit(EX_CONFIG); +} + void urlOpenCount(size_t id, size_t count) { for (size_t i = 1; i <= Cap; ++i) { const struct URL *url = &ring.urls[(ring.len - i) % Cap]; @@ -143,3 +184,19 @@ void urlOpenMatch(size_t id, const char *str) { } } } + +void urlCopyMatch(size_t id, const char *str) { + for (size_t i = 1; i <= Cap; ++i) { + const struct URL *url = &ring.urls[(ring.len - i) % Cap]; + if (!url->url) break; + if (url->id != id) continue; + if ( + !str + || (url->nick && !strcmp(url->nick, str)) + || strstr(url->url, str) + ) { + urlCopy(url->url); + break; + } + } +} -- cgit 1.4.1 From 5254e1035c5945407ee354276f839426fc17e432 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 14:09:27 -0500 Subject: Add /help Now with automatic search! Also had to fix the SIGCHLD handling... --- catgirl.1 | 6 ++++++ chat.c | 2 ++ command.c | 19 +++++++++++++++++++ ui.c | 7 +++++++ 4 files changed, 34 insertions(+) (limited to 'chat.c') diff --git a/catgirl.1 b/catgirl.1 index eb7310d..5772db3 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -179,6 +179,12 @@ or matching Toggle logging in the .Sy window. +.It Ic /help Op Ar search +View this manual. +Type +.Ic q +to return to +.Nm . .It Ic /open Op Ar count Open each of .Ar count diff --git a/chat.c b/chat.c index dbad242..ff74485 100644 --- a/chat.c +++ b/chat.c @@ -191,6 +191,7 @@ int main(int argc, char *argv[]) { if (signals[SIGINT] || signals[SIGTERM]) break; if (signals[SIGCHLD]) { + signals[SIGCHLD] = 0; int status; while (0 < waitpid(-1, &status, WNOHANG)) { if (WIFEXITED(status) && WEXITSTATUS(status)) { @@ -206,6 +207,7 @@ int main(int argc, char *argv[]) { ); } } + uiShow(); } if (signals[SIGWINCH]) { diff --git a/command.c b/command.c index f88a6d5..44d0d54 100644 --- a/command.c +++ b/command.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "chat.h" @@ -158,6 +159,23 @@ static void commandCopy(size_t id, char *params) { urlCopyMatch(id, params); } +static void commandHelp(size_t id, char *params) { + (void)id; + uiHide(); + + pid_t pid = fork(); + if (pid < 0) err(EX_OSERR, "fork"); + if (pid) return; + + char buf[256]; + snprintf(buf, sizeof(buf), "ip%s$", (params ? params : "COMMANDS")); + setenv("LESS", buf, 1); + execlp("man", "man", "1", "catgirl", NULL); + dup2(procPipe[1], STDERR_FILENO); + warn("man"); + _exit(EX_UNAVAILABLE); +} + static const struct Handler { const char *cmd; Command *fn; @@ -165,6 +183,7 @@ static const struct Handler { { "/close", commandClose }, { "/copy", commandCopy }, { "/debug", commandDebug }, + { "/help", commandHelp }, { "/join", commandJoin }, { "/me", commandMe }, { "/names", commandNames }, diff --git a/ui.c b/ui.c index 9abfffc..66a9c59 100644 --- a/ui.c +++ b/ui.c @@ -156,13 +156,18 @@ static const char *ExitFocusMode = "\33[?1004l"; static const char *EnterPasteMode = "\33[?2004h"; static const char *ExitPasteMode = "\33[?2004l"; +static bool hidden; + void uiShow(void) { putp(EnterFocusMode); putp(EnterPasteMode); fflush(stdout); + hidden = false; + uiDraw(); } void uiHide(void) { + hidden = true; putp(ExitFocusMode); putp(ExitPasteMode); endwin(); @@ -250,6 +255,7 @@ void uiInit(void) { } void uiDraw(void) { + if (hidden) return; wnoutrefresh(status); struct Window *window = windows.active; pnoutrefresh( @@ -755,6 +761,7 @@ static void keyStyle(wchar_t ch) { } void uiRead(void) { + if (hidden) return; int ret; wint_t ch; static bool style; -- cgit 1.4.1 From 0d93e66a68ded28440e20cd7012b4e8b0c705fc6 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 05:50:28 -0500 Subject: Add -H --- catgirl.1 | 7 ++++++- chat.c | 7 ++++++- chat.h | 3 ++- 3 files changed, 14 insertions(+), 3 deletions(-) (limited to 'chat.c') diff --git a/catgirl.1 b/catgirl.1 index 5e333a8..15b387b 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -1,4 +1,4 @@ -.Dd February 9, 2020 +.Dd February 10, 2020 .Dt CATGIRL 1 .Os . @@ -10,6 +10,7 @@ .Nm .Op Fl ev .Op Fl C Ar copy +.Op Fl H Ar hash .Op Fl O Ar open .Op Fl a Ar auth .Op Fl c Ar cert @@ -57,6 +58,10 @@ The default is the first available of .Xr xclip 1 , .Xr xsel 1 . . +.It Fl H Ar hash , Cm hash = Ar hash +Set the initial value of +the nick color hash function. +. .It Fl O Ar util , Cm open = Ar util Set the utility used by .Ic /open . diff --git a/chat.c b/chat.c index ff74485..c58fdc5 100644 --- a/chat.c +++ b/chat.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,8 @@ size_t idNext = Network + 1; struct Self self = { .color = Default }; +uint32_t hashInit; + int procPipe[2] = { -1, -1 }; static void pipeRead(void) { @@ -81,10 +84,11 @@ int main(int argc, char *argv[]) { const char *user = NULL; const char *real = NULL; - const char *Opts = "!C:O:a:c:eh:j:k:n:p:r:u:vw:"; + const char *Opts = "!C:H:O:a:c:eh:j:k:n:p:r:u:vw:"; const struct option LongOpts[] = { { "insecure", no_argument, NULL, '!' }, { "copy", required_argument, NULL, 'C' }, + { "hash", required_argument, NULL, 'H' }, { "open", required_argument, NULL, 'O' }, { "sasl-plain", required_argument, NULL, 'a' }, { "cert", required_argument, NULL, 'c' }, @@ -106,6 +110,7 @@ int main(int argc, char *argv[]) { switch (opt) { break; case '!': insecure = true; break; case 'C': urlCopyUtil = optarg; + break; case 'H': hashInit = strtoul(optarg, NULL, 0); break; case 'O': urlOpenUtil = optarg; break; case 'a': sasl = true; self.plain = optarg; break; case 'c': cert = optarg; diff --git a/chat.h b/chat.h index e7bb9cc..16cc683 100644 --- a/chat.h +++ b/chat.h @@ -190,9 +190,10 @@ int getopt_config( const char *optstring, const struct option *longopts, int *longindex ); +extern uint32_t hashInit; static inline enum Color hash(const char *str) { if (*str == '~') str++; - uint32_t hash = 0; + uint32_t hash = hashInit; for (; *str; ++str) { hash = (hash << 5) | (hash >> 27); hash ^= *str; -- cgit 1.4.1 From b59431bb15ec74f05119a7c710a1f6a21e702bad Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 19:40:13 -0500 Subject: Add -s to save and load buffers --- catgirl.1 | 27 +++++++++- chat.c | 17 +++++- chat.h | 4 ++ config.c | 8 ++- ui.c | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 220 insertions(+), 12 deletions(-) (limited to 'chat.c') diff --git a/catgirl.1 b/catgirl.1 index 15b387b..00f875b 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -20,6 +20,7 @@ .Op Fl n Ar nick .Op Fl p Ar port .Op Fl r Ar real +.Op Fl s Ar save .Op Fl u Ar user .Op Fl w Ar pass .Op Ar config ... @@ -123,6 +124,18 @@ Set realname to .Ar real . The default realname is the same as the nickname. . +.It Fl s Ar name , Cm save = Ar name +Load and save the contents of windows from +.Ar name +in +.Pa $XDG_DATA_DIRS/catgirl , +or an absolute or relative path if +.Ar name +starts with +.Ql / +or +.Ql \&. . +. .It Fl u Ar user , Cm user = Ar user Set username to .Ar user . @@ -324,7 +337,7 @@ The color numbers are as follows: .Sh FILES .Bl -tag -width Ds .It Pa $XDG_CONFIG_DIRS/catgirl -Configuration files are search for first in +Configuration files are searched for first in .Ev $XDG_CONFIG_HOME , usually .Pa ~/.config , @@ -334,6 +347,18 @@ usually .Pa /etc/xdg . .It Pa ~/.config/catgirl The most likely location of configuration files. +. +.It Pa $XDG_DATA_DIRS/catgirl +Save files are searched for first in +.Ev $XDG_DATA_HOME , +usually +.Pa ~/.local/share , +followed by the colon-separated list of paths +.Ev $XDG_DATA_DIRS , +usually +.Pa /usr/local/share:/usr/share . +.It Pa ~/.local/share/catgirl +The most likely location of save files. .El . .Sh EXAMPLES diff --git a/chat.c b/chat.c index c58fdc5..e8713bb 100644 --- a/chat.c +++ b/chat.c @@ -47,6 +47,15 @@ size_t idNext = Network + 1; struct Self self = { .color = Default }; +static const char *save; +static void exitSave(void) { + int error = uiSave(save); + if (error) { + warn("%s", save); + _exit(EX_IOERR); + } +} + uint32_t hashInit; int procPipe[2] = { -1, -1 }; @@ -84,7 +93,7 @@ int main(int argc, char *argv[]) { const char *user = NULL; const char *real = NULL; - const char *Opts = "!C:H:O:a:c:eh:j:k:n:p:r:u:vw:"; + const char *Opts = "!C:H:O:a:c:eh:j:k:n:p:r:s:u:vw:"; const struct option LongOpts[] = { { "insecure", no_argument, NULL, '!' }, { "copy", required_argument, NULL, 'C' }, @@ -99,6 +108,7 @@ int main(int argc, char *argv[]) { { "nick", required_argument, NULL, 'n' }, { "port", required_argument, NULL, 'p' }, { "real", required_argument, NULL, 'r' }, + { "save", required_argument, NULL, 's' }, { "user", required_argument, NULL, 'u' }, { "debug", no_argument, NULL, 'v' }, { "pass", required_argument, NULL, 'w' }, @@ -121,6 +131,7 @@ int main(int argc, char *argv[]) { break; case 'n': nick = optarg; break; case 'p': port = optarg; break; case 'r': real = optarg; + break; case 's': save = optarg; break; case 'u': user = optarg; break; case 'v': self.debug = true; break; case 'w': pass = optarg; @@ -154,6 +165,10 @@ int main(int argc, char *argv[]) { if (privFile) fclose(privFile); uiInit(); + if (save) { + uiLoad(save); + atexit(exitSave); + } uiShowID(Network); uiFormat(Network, Cold, NULL, "Traveling..."); uiDraw(); diff --git a/chat.h b/chat.h index 13319da..47a6163 100644 --- a/chat.h +++ b/chat.h @@ -26,6 +26,8 @@ #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit +#define XDG_SUBDIR "catgirl" + typedef unsigned char byte; int procPipe[2]; @@ -144,6 +146,8 @@ void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str); void uiFormat( size_t id, enum Heat heat, const time_t *time, const char *format, ... ) __attribute__((format(printf, 4, 5))); +void uiLoad(const char *name); +int uiSave(const char *name); enum Edit { EditHead, diff --git a/config.c b/config.c index b3e42f9..3bf56c0 100644 --- a/config.c +++ b/config.c @@ -24,8 +24,6 @@ #include "chat.h" -#define CONFIG_DIR "catgirl" - FILE *configOpen(const char *path, const char *mode) { if (path[0] == '/' || path[0] == '.') goto local; @@ -35,10 +33,10 @@ FILE *configOpen(const char *path, const char *mode) { char buf[PATH_MAX]; if (configHome) { - snprintf(buf, sizeof(buf), "%s/" CONFIG_DIR "/%s", configHome, path); + snprintf(buf, sizeof(buf), "%s/" XDG_SUBDIR "/%s", configHome, path); } else { if (!home) goto local; - snprintf(buf, sizeof(buf), "%s/.config/" CONFIG_DIR "/%s", home, path); + snprintf(buf, sizeof(buf), "%s/.config/" XDG_SUBDIR "/%s", home, path); } FILE *file = fopen(buf, mode); if (file) return file; @@ -48,7 +46,7 @@ FILE *configOpen(const char *path, const char *mode) { while (*configDirs) { size_t len = strcspn(configDirs, ":"); snprintf( - buf, sizeof(buf), "%.*s/" CONFIG_DIR "/%s", + buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", (int)len, configDirs, path ); file = fopen(buf, mode); diff --git a/ui.c b/ui.c index 9a070a6..57ef322 100644 --- a/ui.c +++ b/ui.c @@ -20,11 +20,14 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -66,6 +69,13 @@ static void bufferPush(struct Buffer *buffer, time_t time, const char *line) { if (!buffer->lines[i]) err(EX_OSERR, "strdup"); } +static time_t bufferTime(const struct Buffer *buffer, size_t i) { + return buffer->times[(buffer->len + i) % BufferCap]; +} +static const char *bufferLine(const struct Buffer *buffer, size_t i) { + return buffer->lines[(buffer->len + i) % BufferCap]; +} + enum { WindowLines = BufferCap }; struct Window { size_t id; @@ -532,9 +542,8 @@ static void reflow(struct Window *window) { werase(window->pad); wmove(window->pad, WindowLines - 1, 0); window->unreadLines = 0; - struct Buffer *buffer = &window->buffer; for (size_t i = 0; i < BufferCap; ++i) { - char *line = buffer->lines[(buffer->len + i) % BufferCap]; + const char *line = bufferLine(&window->buffer, i); if (!line) continue; waddch(window->pad, '\n'); if (i >= (size_t)(BufferCap - window->unreadCount)) { @@ -557,12 +566,12 @@ static void resize(void) { statusUpdate(); } -static void bufferList(struct Buffer *buffer) { +static void bufferList(const struct Buffer *buffer) { uiHide(); waiting = true; for (size_t i = 0; i < BufferCap; ++i) { - time_t time = buffer->times[(buffer->len + i) % BufferCap]; - const char *line = buffer->lines[(buffer->len + i) % BufferCap]; + time_t time = bufferTime(buffer, i); + const char *line = bufferLine(buffer, i); if (!line) continue; struct tm *tm = localtime(&time); @@ -848,3 +857,160 @@ void uiRead(void) { } inputUpdate(); } + +static FILE *dataOpen(const char *path, const char *mode) { + if (path[0] == '/' || path[0] == '.') goto local; + + const char *home = getenv("HOME"); + const char *dataHome = getenv("XDG_DATA_HOME"); + const char *dataDirs = getenv("XDG_DATA_DIRS"); + + char homePath[PATH_MAX]; + if (dataHome) { + snprintf( + homePath, sizeof(homePath), + "%s/" XDG_SUBDIR "/%s", dataHome, path + ); + } else { + if (!home) goto local; + snprintf( + homePath, sizeof(homePath), + "%s/.local/share/" XDG_SUBDIR "/%s", home, path + ); + } + FILE *file = fopen(homePath, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", homePath); + return NULL; + } + + char buf[PATH_MAX]; + if (!dataDirs) dataDirs = "/usr/local/share:/usr/share"; + while (*dataDirs) { + size_t len = strcspn(dataDirs, ":"); + snprintf( + buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", + (int)len, dataDirs, path + ); + file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", buf); + return NULL; + } + dataDirs += len; + if (*dataDirs) dataDirs++; + } + + if (mode[0] != 'r') { + char *base = strrchr(homePath, '/'); + *base = '\0'; + int error = mkdir(homePath, S_IRWXU); + if (error && errno != EEXIST) { + warn("%s", homePath); + return NULL; + } + *base = '/'; + file = fopen(homePath, mode); + if (!file) warn("%s", homePath); + return file; + } + +local: + file = fopen(path, mode); + if (!file) warn("%s", path); + return file; +} + +static const size_t Signatures[] = { + 0x6C72696774616301, +}; + +static size_t signatureVersion(size_t signature) { + for (size_t i = 0; i < ARRAY_LEN(Signatures); ++i) { + if (signature == Signatures[i]) return i; + } + err(EX_DATAERR, "unknown file signature %zX", signature); +} + +static int writeSize(FILE *file, size_t value) { + return (fwrite(&value, sizeof(value), 1, file) ? 0 : -1); +} +static int writeTime(FILE *file, time_t time) { + return (fwrite(&time, sizeof(time), 1, file) ? 0 : -1); +} +static int writeString(FILE *file, const char *str) { + return (fwrite(str, strlen(str) + 1, 1, file) ? 0 : -1); +} + +int uiSave(const char *name) { + FILE *file = dataOpen(name, "w"); + if (!file) return -1; + + if (writeSize(file, Signatures[0])) return -1; + const struct Window *window; + for (window = windows.head; window; window = window->next) { + if (writeString(file, idNames[window->id])) return -1; + for (size_t i = 0; i < BufferCap; ++i) { + time_t time = bufferTime(&window->buffer, i); + const char *line = bufferLine(&window->buffer, i); + if (!line) continue; + if (writeTime(file, time)) return -1; + if (writeString(file, line)) return -1; + } + if (writeTime(file, 0)) return -1; + } + return fclose(file); +} + +static size_t readSize(FILE *file) { + size_t value; + fread(&value, sizeof(value), 1, file); + if (ferror(file)) err(EX_IOERR, "fread"); + if (feof(file)) errx(EX_DATAERR, "unexpected eof"); + return value; +} +static time_t readTime(FILE *file) { + time_t time; + fread(&time, sizeof(time), 1, file); + if (ferror(file)) err(EX_IOERR, "fread"); + if (feof(file)) errx(EX_DATAERR, "unexpected eof"); + return time; +} +static ssize_t readString(FILE *file, char **buf, size_t *cap) { + ssize_t len = getdelim(buf, cap, '\0', file); + if (len < 0 && !feof(file)) err(EX_IOERR, "getdelim"); + return len; +} + +void uiLoad(const char *name) { + FILE *file = dataOpen(name, "r"); + if (!file) { + if (errno != ENOENT) exit(EX_NOINPUT); + file = dataOpen(name, "w"); + if (!file) exit(EX_CANTCREAT); + fclose(file); + return; + } + + size_t signature = readSize(file); + signatureVersion(signature); + + char *buf = NULL; + size_t cap = 0; + while (0 < readString(file, &buf, &cap)) { + struct Window *window = windowFor(idFor(buf)); + for (;;) { + time_t time = readTime(file); + if (!time) break; + readString(file, &buf, &cap); + bufferPush(&window->buffer, time, buf); + } + reflow(window); + // TODO: Place some marker of end of save. + } + + free(buf); + fclose(file); +} -- cgit 1.4.1 From 99480a42e56e70707822934ffeb56f0454afc127 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 19:57:10 -0500 Subject: Factor out XDG base directory code And add warnings to configOpen, since that's the only way to be accurate if a weird error occurs. --- Makefile | 1 + chat.c | 4 +- chat.h | 2 + config.c | 41 +------------------ ui.c | 67 -------------------------------- xdg.c | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 140 insertions(+), 109 deletions(-) create mode 100644 xdg.c (limited to 'chat.c') diff --git a/Makefile b/Makefile index 89af9b3..b1ffede 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ OBJS += handle.o OBJS += irc.o OBJS += ui.o OBJS += url.o +OBJS += xdg.o dev: tags all diff --git a/chat.c b/chat.c index e8713bb..1ae7090 100644 --- a/chat.c +++ b/chat.c @@ -154,11 +154,11 @@ int main(int argc, char *argv[]) { FILE *privFile = NULL; if (cert) { certFile = configOpen(cert, "r"); - if (!certFile) err(EX_NOINPUT, "%s", cert); + if (!certFile) return EX_NOINPUT; } if (priv) { privFile = configOpen(priv, "r"); - if (!privFile) err(EX_NOINPUT, "%s", priv); + if (!privFile) return EX_NOINPUT; } ircConfig(insecure, certFile, privFile); if (certFile) fclose(certFile); diff --git a/chat.h b/chat.h index 47a6163..03a0a50 100644 --- a/chat.h +++ b/chat.h @@ -189,6 +189,8 @@ void urlOpenMatch(size_t id, const char *str); void urlCopyMatch(size_t id, const char *str); FILE *configOpen(const char *path, const char *mode); +FILE *dataOpen(const char *path, const char *mode); + int getopt_config( int argc, char *const *argv, const char *optstring, const struct option *longopts, int *longindex diff --git a/config.c b/config.c index 3bf56c0..3a87948 100644 --- a/config.c +++ b/config.c @@ -24,42 +24,6 @@ #include "chat.h" -FILE *configOpen(const char *path, const char *mode) { - if (path[0] == '/' || path[0] == '.') goto local; - - const char *home = getenv("HOME"); - const char *configHome = getenv("XDG_CONFIG_HOME"); - const char *configDirs = getenv("XDG_CONFIG_DIRS"); - - char buf[PATH_MAX]; - if (configHome) { - snprintf(buf, sizeof(buf), "%s/" XDG_SUBDIR "/%s", configHome, path); - } else { - if (!home) goto local; - snprintf(buf, sizeof(buf), "%s/.config/" XDG_SUBDIR "/%s", home, path); - } - FILE *file = fopen(buf, mode); - if (file) return file; - if (errno != ENOENT) return NULL; - - if (!configDirs) configDirs = "/etc/xdg"; - while (*configDirs) { - size_t len = strcspn(configDirs, ":"); - snprintf( - buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", - (int)len, configDirs, path - ); - file = fopen(buf, mode); - if (file) return file; - if (errno != ENOENT) return NULL; - configDirs += len; - if (*configDirs) configDirs++; - } - -local: - return fopen(path, mode); -} - #define WS "\t " static const char *path; @@ -92,10 +56,7 @@ int getopt_config( num = 0; path = argv[optind++]; file = configOpen(path, "r"); - if (!file) { - warn("%s", path); - return clean('?'); - } + if (!file) return clean('?'); } else { return clean(-1); } diff --git a/ui.c b/ui.c index ecf7e60..9601aaa 100644 --- a/ui.c +++ b/ui.c @@ -21,13 +21,11 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include #include @@ -858,71 +856,6 @@ void uiRead(void) { inputUpdate(); } -static FILE *dataOpen(const char *path, const char *mode) { - if (path[0] == '/' || path[0] == '.') goto local; - - const char *home = getenv("HOME"); - const char *dataHome = getenv("XDG_DATA_HOME"); - const char *dataDirs = getenv("XDG_DATA_DIRS"); - - char homePath[PATH_MAX]; - if (dataHome) { - snprintf( - homePath, sizeof(homePath), - "%s/" XDG_SUBDIR "/%s", dataHome, path - ); - } else { - if (!home) goto local; - snprintf( - homePath, sizeof(homePath), - "%s/.local/share/" XDG_SUBDIR "/%s", home, path - ); - } - FILE *file = fopen(homePath, mode); - if (file) return file; - if (errno != ENOENT) { - warn("%s", homePath); - return NULL; - } - - char buf[PATH_MAX]; - if (!dataDirs) dataDirs = "/usr/local/share:/usr/share"; - while (*dataDirs) { - size_t len = strcspn(dataDirs, ":"); - snprintf( - buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", - (int)len, dataDirs, path - ); - file = fopen(buf, mode); - if (file) return file; - if (errno != ENOENT) { - warn("%s", buf); - return NULL; - } - dataDirs += len; - if (*dataDirs) dataDirs++; - } - - if (mode[0] != 'r') { - char *base = strrchr(homePath, '/'); - *base = '\0'; - int error = mkdir(homePath, S_IRWXU); - if (error && errno != EEXIST) { - warn("%s", homePath); - return NULL; - } - *base = '/'; - file = fopen(homePath, mode); - if (!file) warn("%s", homePath); - return file; - } - -local: - file = fopen(path, mode); - if (!file) warn("%s", path); - return file; -} - static const size_t Signatures[] = { 0x6C72696774616301, }; diff --git a/xdg.c b/xdg.c new file mode 100644 index 0000000..6e33210 --- /dev/null +++ b/xdg.c @@ -0,0 +1,134 @@ +/* Copyright (C) 2019, 2020 C. McEnroe + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "chat.h" + +FILE *configOpen(const char *path, const char *mode) { + if (path[0] == '/' || path[0] == '.') goto local; + + const char *home = getenv("HOME"); + const char *configHome = getenv("XDG_CONFIG_HOME"); + const char *configDirs = getenv("XDG_CONFIG_DIRS"); + + char buf[PATH_MAX]; + if (configHome) { + snprintf(buf, sizeof(buf), "%s/" XDG_SUBDIR "/%s", configHome, path); + } else { + if (!home) goto local; + snprintf(buf, sizeof(buf), "%s/.config/" XDG_SUBDIR "/%s", home, path); + } + FILE *file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", buf); + return NULL; + } + + if (!configDirs) configDirs = "/etc/xdg"; + while (*configDirs) { + size_t len = strcspn(configDirs, ":"); + snprintf( + buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", + (int)len, configDirs, path + ); + file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", buf); + return NULL; + } + configDirs += len; + if (*configDirs) configDirs++; + } + +local: + file = fopen(path, mode); + if (!file) warn("%s", path); + return file; +} + +FILE *dataOpen(const char *path, const char *mode) { + if (path[0] == '/' || path[0] == '.') goto local; + + const char *home = getenv("HOME"); + const char *dataHome = getenv("XDG_DATA_HOME"); + const char *dataDirs = getenv("XDG_DATA_DIRS"); + + char homePath[PATH_MAX]; + if (dataHome) { + snprintf( + homePath, sizeof(homePath), + "%s/" XDG_SUBDIR "/%s", dataHome, path + ); + } else { + if (!home) goto local; + snprintf( + homePath, sizeof(homePath), + "%s/.local/share/" XDG_SUBDIR "/%s", home, path + ); + } + FILE *file = fopen(homePath, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", homePath); + return NULL; + } + + char buf[PATH_MAX]; + if (!dataDirs) dataDirs = "/usr/local/share:/usr/share"; + while (*dataDirs) { + size_t len = strcspn(dataDirs, ":"); + snprintf( + buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", + (int)len, dataDirs, path + ); + file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", buf); + return NULL; + } + dataDirs += len; + if (*dataDirs) dataDirs++; + } + + if (mode[0] != 'r') { + char *base = strrchr(homePath, '/'); + *base = '\0'; + int error = mkdir(homePath, S_IRWXU); + if (error && errno != EEXIST) { + warn("%s", homePath); + return NULL; + } + *base = '/'; + file = fopen(homePath, mode); + if (!file) warn("%s", homePath); + return file; + } + +local: + file = fopen(path, mode); + if (!file) warn("%s", path); + return file; +} -- cgit 1.4.1 From babd3b0a6c4715bba573ffdf7bd07eac62b8cd0f Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 20:06:25 -0500 Subject: Synthesize a QUIT message to handle on exit So that the end of a saved buffer contains the self quit. --- chat.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'chat.c') diff --git a/chat.c b/chat.c index 1ae7090..f854a33 100644 --- a/chat.c +++ b/chat.c @@ -247,5 +247,13 @@ int main(int argc, char *argv[]) { } else { ircFormat("QUIT\r\n"); } + struct Message msg = { + .nick = self.nick, + .user = self.user, + .cmd = "QUIT", + .params[0] = self.quit, + }; + handle(msg); + uiHide(); } -- cgit 1.4.1