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.h | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 chat.h (limited to 'chat.h') diff --git a/chat.h b/chat.h new file mode 100644 index 0000000..bb8929b --- /dev/null +++ b/chat.h @@ -0,0 +1,102 @@ +/* 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 + +#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; + +#define ENUM_CAP \ + X("sasl", CapSASL) \ + X("server-time", CapServerTime) \ + X("userhost-in-names", CapUserhostInNames) + +enum Cap { +#define X(name, id) BIT(id), + ENUM_CAP +#undef X +}; + +extern struct Self { + enum Cap caps; + char *plain; + char *nick; + const char *join; +} self; + +#define ENUM_TAG \ + X("time", TagTime) + +enum Tag { +#define X(name, id) id, + ENUM_TAG +#undef X + TagCap, +}; + +enum { ParamCap = 15 }; +struct Message { + char *tags[TagCap]; + char *nick; + char *user; + char *host; + char *cmd; + char *params[ParamCap]; +}; + +void ircConfig(bool insecure, const char *cert, const char *priv); +int ircConnect(const char *host, const char *port); +void ircRecv(void); +void ircSend(const char *ptr, size_t len); +void ircFormat(const char *format, ...) + __attribute__((format(printf, 1, 2))); + +void handle(struct Message msg); + +#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) { + dst[i++] = Base64[0x3F & (src[0] >> 2)]; + dst[i++] = Base64[0x3F & (src[0] << 4 | src[1] >> 4)]; + dst[i++] = Base64[0x3F & (src[1] << 2 | src[2] >> 6)]; + dst[i++] = Base64[0x3F & src[2]]; + src += 3; + len -= 3; + } + if (len) { + dst[i++] = Base64[0x3F & (src[0] >> 2)]; + if (len > 1) { + dst[i++] = Base64[0x3F & (src[0] << 4 | src[1] >> 4)]; + dst[i++] = Base64[0x3F & (src[1] << 2)]; + } else { + dst[i++] = Base64[0x3F & (src[0] << 4)]; + dst[i++] = '='; + } + dst[i++] = '='; + } + dst[i] = '\0'; +} + +// Defined in libcrypto if missing from libc: +void explicit_bzero(void *b, size_t len); -- 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.h') 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.h') 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 e289ff6b18e643eea4c72e04f7c0dc6ff768a335 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 1 Feb 2020 02:55:07 -0500 Subject: Add term stuff Copied almost verbatim from existing catgirl... I think I did a better job on that state machine this time tbh. --- Makefile | 1 + chat.h | 17 ++++++++++++++ term.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 term.c (limited to 'chat.h') diff --git a/Makefile b/Makefile index 30bcf66..999b491 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ LDLIBS = -lcrypto -ltls OBJS += chat.o OBJS += handle.o OBJS += irc.o +OBJS += term.o catgirl: ${OBJS} ${CC} ${LDFLAGS} ${OBJS} ${LDLIBS} -o $@ diff --git a/chat.h b/chat.h index 8c13d49..93014ef 100644 --- a/chat.h +++ b/chat.h @@ -91,6 +91,23 @@ void ircFormat(const char *format, ...) void handle(struct Message msg); +enum TermMode { + TermFocus, + TermPaste, +}; +enum TermEvent { + TermNone, + TermFocusIn, + TermFocusOut, + TermPasteStart, + TermPasteEnd, +}; +void termInit(void); +void termNoFlow(void); +void termTitle(const char *title); +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] = { diff --git a/term.c b/term.c new file mode 100644 index 0000000..ade5392 --- /dev/null +++ b/term.c @@ -0,0 +1,79 @@ +/* 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" + +static bool xterm; + +void termInit(void) { + const char *term = getenv("TERM"); + xterm = (term && !strncmp(term, "xterm", 5)); +} + +void termNoFlow(void) { + struct termios attr; + int error = tcgetattr(STDIN_FILENO, &attr); + if (error) return; + attr.c_iflag &= ~IXON; + attr.c_cc[VDISCARD] = _POSIX_VDISABLE; + tcsetattr(STDIN_FILENO, TCSANOW, &attr); +} + +void termTitle(const char *title) { + if (!xterm) return; + printf("\33]0;%s\33\\", title); + fflush(stdout); +} + +static void privateMode(const char *mode, bool set) { + printf("\33[?%s%c", mode, (set ? 'h' : 'l')); + fflush(stdout); +} + +void termMode(enum TermMode mode, bool set) { + switch (mode) { + break; case TermFocus: privateMode("1004", set); + break; case TermPaste: privateMode("2004", set); + } +} + +enum { Esc = '\33' }; + +enum TermEvent termEvent(char ch) { + static int st; +#define T(st, ch) ((st) << 8 | (ch)) + switch (T(st, ch)) { + break; case T(0, Esc): st = 1; + break; case T(1, '['): st = 2; + break; case T(2, 'I'): st = 0; return TermFocusIn; + break; case T(2, 'O'): st = 0; return TermFocusOut; + break; case T(2, '2'): st = 3; + break; case T(3, '0'): st = 4; + break; case T(4, '0'): st = 5; + break; case T(5, '~'): st = 0; return TermPasteStart; + break; case T(4, '1'): st = 6; + break; case T(6, '~'): st = 0; return TermPasteEnd; + break; default: st = 0; + } + return 0; +#undef T +} -- 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.h') 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 3c824684e5f262c2b2fab3f8009b29382277d086 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 02:31:20 -0500 Subject: Add color hashing function --- chat.h | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'chat.h') diff --git a/chat.h b/chat.h index be3952c..fc5043d 100644 --- a/chat.h +++ b/chat.h @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -93,6 +94,16 @@ enum Color { 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); -- 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.h') 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.h') 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 052cd2ed2688867f8b980d283ea3aa410d9dd6aa Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 03:34:05 -0500 Subject: Remove style string macros --- chat.h | 6 ------ handle.c | 2 +- irc.c | 2 +- ui.c | 4 ++-- 4 files changed, 4 insertions(+), 10 deletions(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 4ced983..275fef9 100644 --- a/chat.h +++ b/chat.h @@ -26,12 +26,6 @@ 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, diff --git a/handle.c b/handle.c index f52f6f9..ef61129 100644 --- a/handle.c +++ b/handle.c @@ -188,7 +188,7 @@ static void handleJoin(struct Message *msg) { } uiFormat( id, Cold, tagTime(msg), - C"%02d%s"C" arrives in "C"%02d%s"C, + "\3%02d%s\3 arrives in \3%02d%s\3", hash(msg->user), msg->nick, idColors[id], idNames[id] ); } diff --git a/irc.c b/irc.c index f07fcc8..d8c6a21 100644 --- a/irc.c +++ b/irc.c @@ -105,7 +105,7 @@ static void debug(char dir, const char *line) { if (!self.debug) return; size_t len = strcspn(line, "\r\n"); uiFormat( - Debug, Cold, NULL, C"%d%c%c"C" %.*s", + Debug, Cold, NULL, "\3%d%c%c\3 %.*s", Gray, dir, dir, (int)len, line ); if (!isatty(STDERR_FILENO)) { diff --git a/ui.c b/ui.c index 961e448..3f74e14 100644 --- a/ui.c +++ b/ui.c @@ -271,8 +271,8 @@ static void statusUpdate(void) { int unread; char buf[256]; snprintf( - buf, sizeof(buf), C"%d%s %d %s %n("C"%02d%d"C"%d) ", - idColors[window->id], (window == windows.active ? V : ""), + buf, sizeof(buf), "\3%d%s %d %s %n(\3%02d%d\3%d) ", + idColors[window->id], (window == windows.active ? "\26" : ""), num, idNames[window->id], &unread, (window->heat > Warm ? White : idColors[window->id]), window->unread, -- 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.h') 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 8ec17d4f8ce2edab30d998d6b279f0e5cd840022 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 18:39:08 -0500 Subject: Use tsl/fsl capabilities for title if available Also manually fill them if TERM=xterm* because they really should be there. --- chat.h | 2 -- term.c | 14 -------------- ui.c | 11 +++++++++-- 3 files changed, 9 insertions(+), 18 deletions(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index f9de779..8a806f1 100644 --- a/chat.h +++ b/chat.h @@ -129,9 +129,7 @@ enum TermEvent { TermPasteStart, TermPasteEnd, }; -void termInit(void); void termNoFlow(void); -void termTitle(const char *title); void termMode(enum TermMode mode, bool set); enum TermEvent termEvent(char ch); diff --git a/term.c b/term.c index bf4a933..427cac6 100644 --- a/term.c +++ b/term.c @@ -17,19 +17,11 @@ #include #include #include -#include #include #include #include "chat.h" -static bool xterm; - -void termInit(void) { - const char *term = getenv("TERM"); - xterm = (term && !strncmp(term, "xterm", 5)); -} - void termNoFlow(void) { struct termios attr; int error = tcgetattr(STDIN_FILENO, &attr); @@ -39,12 +31,6 @@ void termNoFlow(void) { tcsetattr(STDIN_FILENO, TCSANOW, &attr); } -void termTitle(const char *title) { - if (!xterm) return; - printf("\33]0;%s\33\\", title); - fflush(stdout); -} - static void privateMode(const char *mode, bool set) { printf("\33[?%s%c", mode, (set ? 'h' : 'l')); fflush(stdout); diff --git a/ui.c b/ui.c index 2d5e454..f434289 100644 --- a/ui.c +++ b/ui.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -133,11 +134,15 @@ void uiInit(void) { initscr(); cbreak(); noecho(); - termInit(); termNoFlow(); def_prog_mode(); err_set_exit(errExit); colorInit(); + if (!to_status_line && !strncmp(termname(), "xterm", 5)) { + to_status_line = "\33]2;"; + from_status_line = "\7"; + } + status = newwin(1, COLS, 0, 0); input = newpad(1, InputCols); keypad(input, true); @@ -305,7 +310,9 @@ static void statusUpdate(void) { &unread, windows.active->unread ); if (!windows.active->unread) buf[unread] = '\0'; - termTitle(buf); + putp(to_status_line); + putp(buf); + putp(from_status_line); } void uiShowID(size_t id) { -- cgit 1.4.1 From 5c328c7a8801d6a4aded769092ead9715d4ecf98 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 19:34:35 -0500 Subject: Remove term.c in favor of more curses APIs --- Makefile | 1 - chat.h | 15 --------------- term.c | 66 ---------------------------------------------------------------- ui.c | 28 +++++++++++++++++++++++++-- 4 files changed, 26 insertions(+), 84 deletions(-) delete mode 100644 term.c (limited to 'chat.h') diff --git a/Makefile b/Makefile index ce27d4e..6ba0ba5 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,6 @@ LDLIBS = -lcurses -lcrypto -ltls OBJS += chat.o OBJS += handle.o OBJS += irc.o -OBJS += term.o OBJS += ui.o dev: tags all diff --git a/chat.h b/chat.h index 8a806f1..43f62fd 100644 --- a/chat.h +++ b/chat.h @@ -118,21 +118,6 @@ void uiFormat( size_t id, enum Heat heat, const struct tm *time, const char *format, ... ) __attribute__((format(printf, 4, 5))); -enum TermMode { - TermFocus, - TermPaste, -}; -enum TermEvent { - TermNone, - TermFocusIn, - TermFocusOut, - TermPasteStart, - TermPasteEnd, -}; -void termNoFlow(void); -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; diff --git a/term.c b/term.c deleted file mode 100644 index 427cac6..0000000 --- a/term.c +++ /dev/null @@ -1,66 +0,0 @@ -/* Copyright (C) 2018, 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" - -void termNoFlow(void) { - struct termios attr; - int error = tcgetattr(STDIN_FILENO, &attr); - if (error) return; - attr.c_iflag &= ~IXON; - attr.c_cc[VDISCARD] = _POSIX_VDISABLE; - tcsetattr(STDIN_FILENO, TCSANOW, &attr); -} - -static void privateMode(const char *mode, bool set) { - printf("\33[?%s%c", mode, (set ? 'h' : 'l')); - fflush(stdout); -} - -void termMode(enum TermMode mode, bool set) { - switch (mode) { - break; case TermFocus: privateMode("1004", set); - break; case TermPaste: privateMode("2004", set); - } -} - -enum { Esc = '\33' }; - -enum TermEvent termEvent(char ch) { - static int st; -#define T(st, ch) ((st) << 8 | (ch)) - switch (T(st, ch)) { - break; case T(0, Esc): st = 1; - break; case T(1, '['): st = 2; - break; case T(2, 'I'): st = 0; return TermFocusIn; - break; case T(2, 'O'): st = 0; return TermFocusOut; - break; case T(2, '2'): st = 3; - break; case T(3, '0'): st = 4; - break; case T(4, '0'): st = 5; - break; case T(5, '~'): st = 0; return TermPasteStart; - break; case T(4, '1'): st = 6; - break; case T(6, '~'): st = 0; return TermPasteEnd; - break; default: st = 0; - } - return 0; -#undef T -} diff --git a/ui.c b/ui.c index f434289..5d626ce 100644 --- a/ui.c +++ b/ui.c @@ -25,7 +25,9 @@ #include #include #include +#include #include +#include #include #include @@ -125,6 +127,23 @@ static struct Window *windowFor(size_t id) { return window; } +enum { + KeyFocusIn = KEY_MAX + 1, + KeyFocusOut, + KeyPasteOn, + KeyPasteOff, +}; + +static void disableFlowControl(void) { + struct termios term; + int error = tcgetattr(STDOUT_FILENO, &term); + if (error) err(EX_OSERR, "tcgetattr"); + term.c_iflag &= ~IXON; + term.c_cc[VDISCARD] = _POSIX_VDISABLE; + error = tcsetattr(STDOUT_FILENO, TCSADRAIN, &term); + if (error) err(EX_OSERR, "tcsetattr"); +} + static void errExit(int eval) { (void)eval; reset_shell_mode(); @@ -134,15 +153,20 @@ void uiInit(void) { initscr(); cbreak(); noecho(); - termNoFlow(); + disableFlowControl(); def_prog_mode(); err_set_exit(errExit); - colorInit(); + if (!to_status_line && !strncmp(termname(), "xterm", 5)) { to_status_line = "\33]2;"; from_status_line = "\7"; } + define_key("\33[I", KeyFocusIn); + define_key("\33[O", KeyFocusOut); + define_key("\33[200~", KeyPasteOn); + define_key("\33[201~", KeyPasteOff); + colorInit(); status = newwin(1, COLS, 0, 0); input = newpad(1, InputCols); keypad(input, true); -- cgit 1.4.1 From c9470b59a151f639e7985ca545bd67182e7a88d8 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 23:20:19 -0500 Subject: Add sequences for toggling focus/paste modes --- chat.h | 2 ++ ui.c | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 43f62fd..76d69c9 100644 --- a/chat.h +++ b/chat.h @@ -111,6 +111,8 @@ void handle(struct Message msg); enum Heat { Cold, Warm, Hot }; void uiInit(void); +void uiShow(void); +void uiHide(void); void uiDraw(void); void uiShowID(size_t id); void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str); diff --git a/ui.c b/ui.c index b9aadec..072ee84 100644 --- a/ui.c +++ b/ui.c @@ -136,6 +136,24 @@ enum { KeyPasteOff, }; +// XXX: Assuming terminals will be fine with these even if they're unsupported, +// since they're "private" modes. +static const char *EnterFocusMode = "\33[?1004h"; +static const char *ExitFocusMode = "\33[?1004l"; +static const char *EnterPasteMode = "\33[?2004h"; +static const char *ExitPasteMode = "\33[?2004l"; + +void uiShow(void) { + putp(EnterFocusMode); + putp(EnterPasteMode); +} + +void uiHide(void) { + putp(ExitFocusMode); + putp(ExitPasteMode); + endwin(); +} + static void disableFlowControl(void) { struct termios term; int error = tcgetattr(STDOUT_FILENO, &term); @@ -174,6 +192,7 @@ void uiInit(void) { keypad(input, true); nodelay(input, true); windows.active = windowFor(Network); + //uiShow(); } void uiDraw(void) { -- cgit 1.4.1 From 26e9dd9adfd4df90cd4cc6ef14d91cdad2efb239 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 3 Feb 2020 18:41:52 -0500 Subject: Use time_t rather than struct tm --- chat.h | 4 ++-- handle.c | 11 ++++++----- ui.c | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 76d69c9..9165c13 100644 --- a/chat.h +++ b/chat.h @@ -115,9 +115,9 @@ void uiShow(void); void uiHide(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 uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str); void uiFormat( - size_t id, enum Heat heat, const struct tm *time, const char *format, ... + size_t id, enum Heat heat, const time_t *time, const char *format, ... ) __attribute__((format(printf, 4, 5))); static inline enum Color hash(const char *str) { diff --git a/handle.c b/handle.c index 4bc2e3d..ef49f7c 100644 --- a/handle.c +++ b/handle.c @@ -71,12 +71,13 @@ static void require(struct Message *msg, bool origin, size_t len) { } } -static const struct tm *tagTime(const struct Message *msg) { +static const time_t *tagTime(const struct Message *msg) { + static time_t time; + struct tm tm; if (!msg->tags[TagTime]) return NULL; - static struct tm time; - char *rest = strptime(msg->tags[TagTime], "%FT%T", &time); - time.tm_gmtoff = 0; - return (rest ? &time : NULL); + if (!strptime(msg->tags[TagTime], "%FT%T", &tm)) return NULL; + time = timegm(&tm); + return &time; } typedef void Handler(struct Message *msg); diff --git a/ui.c b/ui.c index 072ee84..e2746f1 100644 --- a/ui.c +++ b/ui.c @@ -372,7 +372,7 @@ void uiShowID(size_t id) { statusUpdate(); } -void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str) { +void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str) { (void)time; struct Window *window = windowFor(id); waddch(window->pad, '\n'); @@ -387,7 +387,7 @@ 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, ... + size_t id, enum Heat heat, const time_t *time, const char *format, ... ) { char buf[1024]; va_list ap; -- 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.h') 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 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.h') 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 4cce893eab7403821ff211f64a7df05051fd6f52 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 00:20:39 -0500 Subject: Add extremely basic editing and message sending --- Makefile | 1 + chat.h | 12 ++++++++++-- command.c | 32 ++++++++++++++++++++++++++++++++ edit.c | 32 +++++++++++++++++++++++++------- ui.c | 5 ++++- 5 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 command.c (limited to 'chat.h') diff --git a/Makefile b/Makefile index 63f719d..05f8bb8 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ CFLAGS += -std=c11 -Wall -Wextra -Wpedantic LDLIBS = -lcurses -lcrypto -ltls OBJS += chat.o +OBJS += command.o OBJS += edit.o OBJS += handle.o OBJS += irc.o diff --git a/chat.h b/chat.h index c754357..c8b31c2 100644 --- a/chat.h +++ b/chat.h @@ -20,6 +20,7 @@ #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 @@ -109,6 +110,7 @@ void ircFormat(const char *format, ...) __attribute__((format(printf, 1, 2))); void handle(struct Message msg); +void command(size_t id, char *input); enum Heat { Cold, Warm, Hot }; void uiInit(void); @@ -122,8 +124,14 @@ 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); +enum Edit { + EditKill, + EditInsert, + EditEnter, +}; +void edit(size_t id, enum Edit op, wchar_t ch); +char *editHead(void); +char *editTail(void); static inline enum Color hash(const char *str) { if (*str == '~') str++; diff --git a/command.c b/command.c new file mode 100644 index 0000000..ab05587 --- /dev/null +++ b/command.c @@ -0,0 +1,32 @@ +/* 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 "chat.h" + +void command(size_t id, char *input) { + ircFormat("PRIVMSG %s :%s\r\n", idNames[id], input); + struct Message msg = { + .nick = self.nick, + // TODO: .user, + .cmd = "PRIVMSG", + .params[0] = idNames[id], + .params[1] = input, + }; + handle(msg); +} diff --git a/edit.c b/edit.c index e142507..68593d1 100644 --- a/edit.c +++ b/edit.c @@ -23,22 +23,40 @@ #include "chat.h" enum { Cap = 512 }; -static wchar_t buf[Cap] = L"foo\0033bar\3baz"; -static size_t len = 12; -static size_t pos = 6; +static wchar_t buf[Cap]; +static size_t len; +static size_t pos; -const char *editHead(void) { +char *editHead(void) { static char mbs[MB_LEN_MAX * Cap]; const wchar_t *ptr = buf; - size_t n = wcsnrtombs(mbs, &ptr, pos, sizeof(mbs), NULL); + size_t n = wcsnrtombs(mbs, &ptr, pos, sizeof(mbs) - 1, NULL); assert(n != (size_t)-1); + mbs[n] = '\0'; return mbs; } -const char *editTail(void) { +char *editTail(void) { static char mbs[MB_LEN_MAX * Cap]; const wchar_t *ptr = &buf[pos]; - size_t n = wcsnrtombs(mbs, &ptr, len - pos, sizeof(mbs), NULL); + size_t n = wcsnrtombs(mbs, &ptr, len - pos, sizeof(mbs) - 1, NULL); assert(n != (size_t)-1); + mbs[n] = '\0'; return mbs; } + +void edit(size_t id, enum Edit op, wchar_t ch) { + switch (op) { + break; case EditKill: len = pos = 0; + break; case EditInsert: { + if (len == Cap) break; + buf[pos++] = ch; + len++; + } + break; case EditEnter: { + pos = 0; + command(id, editTail()); + len = 0; + } + } +} diff --git a/ui.c b/ui.c index 8f813b4..b764f84 100644 --- a/ui.c +++ b/ui.c @@ -498,8 +498,11 @@ static void keyMeta(wchar_t ch) { } static void keyCtrl(wchar_t ch) { + size_t id = windows.active->id; switch (ch) { + break; case L'J': edit(id, EditEnter, 0); break; case L'L': clearok(curscr, true); + break; case L'U': edit(id, EditKill, 0); } } @@ -518,7 +521,7 @@ void uiRead(void) { } else if (iswcntrl(ch)) { keyCtrl(ch ^ L'@'); } else { - // TODO: Insert. + edit(windows.active->id, EditInsert, ch); } meta = false; } -- cgit 1.4.1 From 7414a8a11cd8d16fea47e30513b3a5eaeb232ba1 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 00:40:24 -0500 Subject: Save own username for message echoing --- chat.h | 1 + command.c | 2 +- handle.c | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index c8b31c2..90c7da8 100644 --- a/chat.h +++ b/chat.h @@ -73,6 +73,7 @@ extern struct Self { char *chanTypes; char *prefixes; char *nick; + char *user; enum Color color; } self; diff --git a/command.c b/command.c index ab05587..76d7d7b 100644 --- a/command.c +++ b/command.c @@ -23,7 +23,7 @@ void command(size_t id, char *input) { ircFormat("PRIVMSG %s :%s\r\n", idNames[id], input); struct Message msg = { .nick = self.nick, - // TODO: .user, + .user = self.user, .cmd = "PRIVMSG", .params[0] = idNames[id], .params[1] = input, diff --git a/handle.c b/handle.c index b5585ba..85783d7 100644 --- a/handle.c +++ b/handle.c @@ -187,7 +187,10 @@ 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)) { - self.color = hash(msg->user); + if (!self.user) { + set(&self.user, msg->user); + self.color = hash(msg->user); + } idColors[id] = hash(msg->params[0]); uiShowID(id); } -- cgit 1.4.1 From b2d35edcb22a9a41235229b41b180a50b51b5908 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 19:00:54 -0500 Subject: Change prompt depending on command --- chat.h | 3 +++ command.c | 21 +++++++++++++++++++++ ui.c | 23 ++++++++++++++++++----- 3 files changed, 42 insertions(+), 5 deletions(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 90c7da8..b73cf40 100644 --- a/chat.h +++ b/chat.h @@ -112,6 +112,9 @@ void ircFormat(const char *format, ...) void handle(struct Message msg); 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); enum Heat { Cold, Warm, Hot }; void uiInit(void); diff --git a/command.c b/command.c index ef27888..928f470 100644 --- a/command.c +++ b/command.c @@ -19,6 +19,27 @@ #include "chat.h" +const char *commandIsPrivmsg(size_t id, const char *input) { + if (id == Network || id == Debug) return NULL; + if (input[0] != '/') return input; + const char *space = strchr(&input[1], ' '); + const char *slash = strchr(&input[1], '/'); + if (slash && (!space || slash < space)) return input; + return NULL; +} + +const char *commandIsNotice(size_t id, const char *input) { + if (id == Network || id == Debug) return NULL; + if (strncmp(input, "/notice ", 8)) return NULL; + return &input[8]; +} + +const char *commandIsAction(size_t id, const char *input) { + if (id == Network || id == Debug) return NULL; + if (strncmp(input, "/me ", 4)) return NULL; + return &input[4]; +} + void command(size_t id, char *input) { if (id == Debug) { ircFormat("%s\r\n", input); diff --git a/ui.c b/ui.c index 12c8541..daa6dec 100644 --- a/ui.c +++ b/ui.c @@ -479,16 +479,29 @@ static void inputUpdate(void) { colorPair(mapColor(self.color), -1), NULL ); + const char *head = editHead(); + const char *skip = NULL; if (self.nick) { - // TODO: Check if input is command or action. - waddch(input, '<'); - waddstr(input, self.nick); - waddstr(input, "> "); + size_t id = windows.active->id; + if (NULL != (skip = commandIsPrivmsg(id, head))) { + waddch(input, '<'); + waddstr(input, self.nick); + waddstr(input, "> "); + } else if (NULL != (skip = commandIsNotice(id, head))) { + waddch(input, '-'); + waddstr(input, self.nick); + waddstr(input, "- "); + } else if (NULL != (skip = commandIsAction(id, head))) { + waddstr(input, "* "); + waddstr(input, self.nick); + waddch(input, ' '); + } } + if (skip) head = skip; int y, x; struct Style style = Reset; - inputAdd(&style, editHead()); + inputAdd(&style, head); getyx(input, y, x); inputAdd(&style, editTail()); wclrtoeol(input); -- 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.h') 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 b2cf8733048029354aeb794b14dd71d1bbb0b72d Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 22:09:29 -0500 Subject: Add /window --- chat.h | 1 + command.c | 34 +++++++++++++++++++++------------- 2 files changed, 22 insertions(+), 13 deletions(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 5b3c01c..9317843 100644 --- a/chat.h +++ b/chat.h @@ -123,6 +123,7 @@ void uiShow(void); void uiHide(void); void uiDraw(void); void uiShowID(size_t id); +void uiShowNum(size_t num); void uiRead(void); void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str); void uiFormat( diff --git a/command.c b/command.c index 7fb98af..8471214 100644 --- a/command.c +++ b/command.c @@ -57,9 +57,16 @@ static void commandMe(size_t id, char *params) { } static void commandQuit(size_t id, char *params) { + (void)id; set(&self.quit, (params ? params : "Goodbye")); } +static void commandWindow(size_t id, char *params) { + (void)id; + if (!params) return; + uiShowNum(strtoul(params, NULL, 10)); +} + static const struct Handler { const char *cmd; Command *fn; @@ -68,6 +75,7 @@ static const struct Handler { { "/notice", commandNotice }, { "/quit", commandQuit }, { "/quote", commandQuote }, + { "/window", commandWindow }, }; static int compar(const void *cmd, const void *_handler) { @@ -97,21 +105,21 @@ const char *commandIsAction(size_t id, const char *input) { } void command(size_t id, char *input) { - if (id == Debug) { + if (id == Debug && input[0] != '/') { commandQuote(id, input); - return; - } - if (commandIsPrivmsg(id, input)) { + } else if (commandIsPrivmsg(id, input)) { commandPrivmsg(id, input); - return; - } - char *cmd = strsep(&input, " "); - const struct Handler *handler = bsearch( - cmd, Commands, ARRAY_LEN(Commands), sizeof(*handler), compar - ); - if (handler) { - handler->fn(id, input); + } else if (input[0] == '/' && isdigit(input[1])) { + commandWindow(id, &input[1]); } else { - uiFormat(id, Hot, NULL, "No such command %s", cmd); + char *cmd = strsep(&input, " "); + const struct Handler *handler = bsearch( + cmd, Commands, ARRAY_LEN(Commands), sizeof(*handler), compar + ); + if (handler) { + handler->fn(id, input); + } else { + uiFormat(id, Hot, NULL, "No such command %s", cmd); + } } } -- 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.h') 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 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.h') 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 e9394bfff9dff32b98890b89ecdc884ffb4cba79 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 04:19:56 -0500 Subject: Set id color to Default on allocation --- chat.h | 1 + 1 file changed, 1 insertion(+) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 112530d..8279d1b 100644 --- a/chat.h +++ b/chat.h @@ -51,6 +51,7 @@ static inline size_t idFor(const char *name) { if (id) return id; idNames[idNext] = strdup(name); if (!idNames[idNext]) err(EX_OSERR, "strdup"); + idColors[idNext] = Default; return idNext++; } -- cgit 1.4.1 From af244ad3cd19fb50bf9b9855f02d81e61441ab50 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 7 Feb 2020 01:55:26 -0500 Subject: Add some real line editing operations --- chat.h | 5 +++++ edit.c | 28 ++++++++++++++++++++++++---- ui.c | 12 ++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 8279d1b..a327620 100644 --- a/chat.h +++ b/chat.h @@ -133,7 +133,12 @@ void uiFormat( ) __attribute__((format(printf, 4, 5))); enum Edit { + EditHome, + EditEnd, + EditLeft, + EditRight, EditKill, + EditErase, EditInsert, EditEnter, }; diff --git a/edit.c b/edit.c index 68593d1..b6edb98 100644 --- a/edit.c +++ b/edit.c @@ -45,13 +45,33 @@ char *editTail(void) { return mbs; } +static void reserve(size_t index, size_t count) { + if (len + count > Cap) return; + memmove(&buf[index + count], &buf[index], sizeof(*buf) * (len - index)); + len += count; +} + +static void delete(size_t index, size_t count) { + if (index + count > len) return; + memmove( + &buf[index], &buf[index + count], sizeof(*buf) * (len - index - count) + ); + len -= count; +} + void edit(size_t id, enum Edit op, wchar_t ch) { switch (op) { - break; case EditKill: len = pos = 0; + break; case EditHome: pos = 0; + break; case EditEnd: pos = len; + break; case EditLeft: if (pos) pos--; + break; case EditRight: if (pos < len) pos++; + + break; case EditKill: len = pos = 0; + break; case EditErase: if (pos) delete(--pos, 1); + break; case EditInsert: { - if (len == Cap) break; - buf[pos++] = ch; - len++; + reserve(pos, 1); + if (pos < Cap) buf[pos++] = ch; } break; case EditEnter: { pos = 0; diff --git a/ui.c b/ui.c index ffe8748..f73020a 100644 --- a/ui.c +++ b/ui.c @@ -546,12 +546,20 @@ void uiShowNum(size_t num) { } static void keyCode(int code) { + size_t id = windows.active->id; switch (code) { break; case KEY_RESIZE: resize(); break; case KeyFocusIn: unmark(); break; case KeyFocusOut: windows.active->mark = true; break; case KeyPasteOn:; // TODO break; case KeyPasteOff:; // TODO + + break; case KEY_BACKSPACE: edit(id, EditErase, 0); + break; case KEY_END: edit(id, EditEnd, 0); + break; case KEY_ENTER: edit(id, EditEnter, 0); + break; case KEY_HOME: edit(id, EditHome, 0); + break; case KEY_LEFT: edit(id, EditLeft, 0); + break; case KEY_RIGHT: edit(id, EditRight, 0); } } @@ -567,6 +575,10 @@ static void keyMeta(wchar_t ch) { static void keyCtrl(wchar_t ch) { size_t id = windows.active->id; switch (ch) { + break; case L'?': edit(id, EditErase, 0); + 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'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 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.h') 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 d314523b90f41cfdbca867ad0ad48f2f68f66c58 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 7 Feb 2020 23:33:23 -0500 Subject: Update completion on join, part, privmsg --- chat.h | 1 + complete.c | 13 +++++++++++++ handle.c | 8 +++++++- 3 files changed, 21 insertions(+), 1 deletion(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index f164e7a..aec5a68 100644 --- a/chat.h +++ b/chat.h @@ -153,6 +153,7 @@ 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); +void completeRemove(size_t id, const char *str); FILE *configOpen(const char *path, const char *mode); int getopt_config( diff --git a/complete.c b/complete.c index 8247149..5067512 100644 --- a/complete.c +++ b/complete.c @@ -109,3 +109,16 @@ void completeAccept(void) { void completeReject(void) { match = NULL; } + +void completeRemove(size_t id, const char *str) { + struct Node *next = NULL; + for (struct Node *node = head; node; node = next) { + next = node->next; + if (id && node->id != id) continue; + if (strcmp(node->str, str)) continue; + if (match == node) match = NULL; + detach(node); + free(node->str); + free(node); + } +} diff --git a/handle.c b/handle.c index fb49206..4faabba 100644 --- a/handle.c +++ b/handle.c @@ -154,6 +154,7 @@ static void handleErrorSASLFail(struct Message *msg) { static void handleReplyWelcome(struct Message *msg) { require(msg, false, 1); set(&self.nick, msg->params[0]); + completeTouch(None, self.nick, Default); if (self.join) ircFormat("JOIN %s\r\n", self.join); } @@ -197,8 +198,10 @@ static void handleJoin(struct Message *msg) { self.color = hash(msg->user); } idColors[id] = hash(msg->params[0]); + completeTouch(None, msg->params[0], idColors[id]); uiShowID(id); } + completeTouch(id, msg->nick, hash(msg->user)); uiFormat( id, Cold, tagTime(msg), "\3%02d%s\3\tarrives in \3%02d%s\3", @@ -208,8 +211,10 @@ static void handleJoin(struct Message *msg) { static void handlePart(struct Message *msg) { require(msg, true, 1); + size_t id = idFor(msg->params[0]); + completeRemove(id, msg->nick); uiFormat( - idFor(msg->params[0]), Cold, tagTime(msg), + id, Cold, tagTime(msg), "\3%02d%s\3\tleaves \3%02d%s\3%s%s", hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0], (msg->params[1] ? ": " : ""), @@ -294,6 +299,7 @@ static void handlePrivmsg(struct Message *msg) { bool notice = (msg->cmd[0] == 'N'); bool action = isAction(msg); bool mention = !mine && isMention(msg); + if (!notice && !mine) completeTouch(id, msg->nick, hash(msg->user)); if (notice) { uiFormat( id, Warm, tagTime(msg), -- cgit 1.4.1 From 8b7cc1a0ed95e8a3ff413fa77eb12a3dca7fccb4 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 7 Feb 2020 23:44:03 -0500 Subject: Clear completion for ID on self-part --- chat.h | 1 + complete.c | 12 ++++++++++++ handle.c | 3 +++ 3 files changed, 16 insertions(+) (limited to 'chat.h') diff --git a/chat.h b/chat.h index aec5a68..6eeed60 100644 --- a/chat.h +++ b/chat.h @@ -154,6 +154,7 @@ 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); void completeRemove(size_t id, const char *str); +void completeClear(size_t id); FILE *configOpen(const char *path, const char *mode); int getopt_config( diff --git a/complete.c b/complete.c index 5067512..437bb7d 100644 --- a/complete.c +++ b/complete.c @@ -122,3 +122,15 @@ void completeRemove(size_t id, const char *str) { free(node); } } + +void completeClear(size_t id) { + struct Node *next = NULL; + for (struct Node *node = head; node; node = next) { + next = node->next; + if (node->id != id) continue; + if (match == node) match = NULL; + detach(node); + free(node->str); + free(node); + } +} diff --git a/handle.c b/handle.c index 4faabba..b73d200 100644 --- a/handle.c +++ b/handle.c @@ -212,6 +212,9 @@ static void handleJoin(struct Message *msg) { static void handlePart(struct Message *msg) { require(msg, true, 1); size_t id = idFor(msg->params[0]); + if (self.nick && !strcmp(msg->nick, self.nick)) { + completeClear(id); + } completeRemove(id, msg->nick); uiFormat( id, Cold, tagTime(msg), -- cgit 1.4.1 From 58e1d5b4e2fabead1aae356dd060bfc9748bdd5e Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 00:01:59 -0500 Subject: Handle NICK --- chat.h | 2 ++ complete.c | 21 +++++++++++++++++++++ handle.c | 17 +++++++++++++++++ 3 files changed, 40 insertions(+) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 6eeed60..413cee4 100644 --- a/chat.h +++ b/chat.h @@ -151,8 +151,10 @@ char *editTail(void); const char *complete(size_t id, const char *prefix); void completeAccept(void); void completeReject(void); +size_t completeID(const char *str); void completeAdd(size_t id, const char *str, enum Color color); void completeTouch(size_t id, const char *str, enum Color color); +void completeReplace(size_t id, const char *old, const char *new); void completeRemove(size_t id, const char *str); void completeClear(size_t id); diff --git a/complete.c b/complete.c index 437bb7d..c194536 100644 --- a/complete.c +++ b/complete.c @@ -110,6 +110,27 @@ void completeReject(void) { match = NULL; } +size_t completeID(const char *str) { + for (match = (match ? match->next : head); match; match = match->next) { + if (match->id && !strcmp(match->str, str)) return match->id; + } + return None; +} + +void completeReplace(size_t id, const char *old, const char *new) { + struct Node *next = NULL; + for (struct Node *node = head; node; node = node->next) { + next = node->next; + if (id && node->id != id) continue; + if (strcmp(node->str, old)) continue; + if (match == node) match = NULL; + free(node->str); + node->str = strdup(new); + if (!node->str) err(EX_OSERR, "strdup"); + prepend(detach(node)); + } +} + void completeRemove(size_t id, const char *str) { struct Node *next = NULL; for (struct Node *node = head; node; node = next) { diff --git a/handle.c b/handle.c index b73d200..fe64f33 100644 --- a/handle.c +++ b/handle.c @@ -261,6 +261,22 @@ static void handleTopic(struct Message *msg) { } } +static void handleNick(struct Message *msg) { + require(msg, true, 1); + if (self.nick && !strcmp(msg->nick, self.nick)) { + set(&self.nick, msg->params[0]); + } + size_t id; + completeReplace(None, msg->nick, msg->params[0]); + while (None != (id = completeID(msg->params[0]))) { + uiFormat( + id, Cold, tagTime(msg), + "\3%02d%s\3\tis now known as \3%02d%s\3", + hash(msg->user), msg->nick, hash(msg->user), msg->params[0] + ); + } +} + static bool isAction(struct Message *msg) { if (strncmp(msg->params[1], "\1ACTION ", 8)) return false; msg->params[1] += 8; @@ -354,6 +370,7 @@ static const struct Handler { { "CAP", handleCap }, { "ERROR", handleError }, { "JOIN", handleJoin }, + { "NICK", handleNick }, { "NOTICE", handlePrivmsg }, { "PART", handlePart }, { "PING", handlePing }, -- cgit 1.4.1 From b5707af4b842f521104c5fba07e5685612ff91f2 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 00:58:17 -0500 Subject: Handle KICK See I knew the color cache in complete would be useful in at least one place! --- chat.h | 3 ++- complete.c | 5 +++++ handle.c | 20 ++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 413cee4..bd36d27 100644 --- a/chat.h +++ b/chat.h @@ -151,12 +151,13 @@ char *editTail(void); const char *complete(size_t id, const char *prefix); void completeAccept(void); void completeReject(void); -size_t completeID(const char *str); void completeAdd(size_t id, const char *str, enum Color color); void completeTouch(size_t id, const char *str, enum Color color); void completeReplace(size_t id, const char *old, const char *new); void completeRemove(size_t id, const char *str); void completeClear(size_t id); +size_t completeID(const char *str); +enum Color completeColor(size_t id, const char *str); FILE *configOpen(const char *path, const char *mode); int getopt_config( diff --git a/complete.c b/complete.c index c194536..2f5275f 100644 --- a/complete.c +++ b/complete.c @@ -90,6 +90,11 @@ void completeTouch(size_t id, const char *str, enum Color color) { prepend(node ? detach(node) : alloc(id, str, color)); } +enum Color completeColor(size_t id, const char *str) { + struct Node *node = find(id, str); + return (node ? node->color : Default); +} + static struct Node *match; const char *complete(size_t id, const char *prefix) { diff --git a/handle.c b/handle.c index de9e73a..8ebc3b1 100644 --- a/handle.c +++ b/handle.c @@ -226,6 +226,25 @@ static void handlePart(struct Message *msg) { ); } +static void handleKick(struct Message *msg) { + require(msg, true, 2); + size_t id = idFor(msg->params[0]); + bool kicked = self.nick && !strcmp(msg->params[1], self.nick); + completeTouch(id, msg->nick, hash(msg->user)); + uiFormat( + id, (kicked ? Hot : Cold), tagTime(msg), + "%s\3%02d%s\17\tkicks \3%02d%s\3 out of \3%02d%s\3%s%s", + (kicked ? "\26" : ""), + hash(msg->user), msg->nick, + completeColor(id, msg->params[1]), msg->params[1], + hash(msg->params[0]), msg->params[0], + (msg->params[2] ? ": " : ""), + (msg->params[2] ? msg->params[2] : "") + ); + completeRemove(id, msg->params[1]); + if (kicked) completeClear(id); +} + static void handleNick(struct Message *msg) { require(msg, true, 1); if (self.nick && !strcmp(msg->nick, self.nick)) { @@ -413,6 +432,7 @@ static const struct Handler { { "CAP", handleCap }, { "ERROR", handleError }, { "JOIN", handleJoin }, + { "KICK", handleKick }, { "NICK", handleNick }, { "NOTICE", handlePrivmsg }, { "PART", handlePart }, -- cgit 1.4.1 From fda510b8761f487c36988eb70a6d95bd0d583aed Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 01:14:41 -0500 Subject: Handle ID overflow idk just shove it all in . --- chat.h | 1 + 1 file changed, 1 insertion(+) (limited to 'chat.h') diff --git a/chat.h b/chat.h index bd36d27..081370e 100644 --- a/chat.h +++ b/chat.h @@ -49,6 +49,7 @@ static inline size_t idFind(const char *name) { static inline size_t idFor(const char *name) { size_t id = idFind(name); if (id) return id; + if (idNext == IDCap) return Network; idNames[idNext] = strdup(name); if (!idNames[idNext]) err(EX_OSERR, "strdup"); idColors[idNext] = Default; -- cgit 1.4.1 From 943502ea82b3965b4f652146ca03262ac6390f83 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 02:26:00 -0500 Subject: Add /close --- catgirl.1 | 2 ++ chat.h | 2 ++ command.c | 12 ++++++++++++ ui.c | 28 ++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+) (limited to 'chat.h') diff --git a/catgirl.1 b/catgirl.1 index 0702f58..9314e7a 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -146,6 +146,8 @@ Send a raw IRC command. . .Ss UI Commands .Bl -tag -width Ds +.It Ic /close Op Ar name | num +Close the named, numbered or current window. .It Ic /window Ar name Switch to window by name. .It Ic /window Ar num , Ic / Ns Ar num diff --git a/chat.h b/chat.h index 081370e..9daa38c 100644 --- a/chat.h +++ b/chat.h @@ -128,6 +128,8 @@ void uiHide(void); void uiDraw(void); void uiShowID(size_t id); void uiShowNum(size_t num); +void uiCloseID(size_t id); +void uiCloseNum(size_t id); void uiRead(void); void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str); void uiFormat( diff --git a/command.c b/command.c index 9047e95..e33c57e 100644 --- a/command.c +++ b/command.c @@ -100,10 +100,22 @@ static void commandWindow(size_t id, char *params) { } } +static void commandClose(size_t id, char *params) { + if (!params) { + uiCloseID(id); + } else if (isdigit(params[0])) { + uiCloseNum(strtoul(params, NULL, 10)); + } else { + id = idFind(params); + if (id) uiCloseID(id); + } +} + static const struct Handler { const char *cmd; Command *fn; } Commands[] = { + { "/close", commandClose }, { "/join", commandJoin }, { "/me", commandMe }, { "/nick", commandNick }, diff --git a/ui.c b/ui.c index 6d1338b..c738617 100644 --- a/ui.c +++ b/ui.c @@ -573,6 +573,34 @@ void uiShowNum(size_t num) { windowShow(window); } +static void windowClose(struct Window *window) { + if (window->id == Network) return; + if (windows.active == window) { + windowShow(window->prev ? window->prev : window->next); + } + if (windows.other == window) windows.other = NULL; + windowRemove(window); + for (size_t i = 0; i < BufferCap; ++i) { + free(window->buffer.lines[i]); + } + delwin(window->pad); + free(window); + statusUpdate(); +} + +void uiCloseID(size_t id) { + windowClose(windowFor(id)); +} + +void uiCloseNum(size_t num) { + struct Window *window = windows.head; + for (size_t i = 0; i < num; ++i) { + window = window->next; + if (!window) return; + } + windowClose(window); +} + static void keyCode(int code) { size_t id = windows.active->id; switch (code) { -- cgit 1.4.1 From b6bf6d62b0bb6d203ce41e4b375c415ca8fde719 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 03:15:17 -0500 Subject: Only show expected topic/names replies --- chat.h | 5 +++++ command.c | 8 ++++++++ handle.c | 23 ++++++++++++++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 9daa38c..34e1812 100644 --- a/chat.h +++ b/chat.h @@ -114,6 +114,11 @@ void ircSend(const char *ptr, size_t len); void ircFormat(const char *format, ...) __attribute__((format(printf, 1, 2))); +extern struct Replies { + size_t topic; + size_t names; +} replies; + void handle(struct Message msg); void command(size_t id, char *input); const char *commandIsPrivmsg(size_t id, const char *input); diff --git a/command.c b/command.c index 1d1c756..9879dbe 100644 --- a/command.c +++ b/command.c @@ -71,7 +71,15 @@ static void commandMe(size_t id, char *params) { } static void commandJoin(size_t id, char *params) { + size_t count = 1; + if (params) { + for (char *ch = params; *ch && *ch != ' '; ++ch) { + if (*ch == ',') count++; + } + } ircFormat("JOIN %s\r\n", (params ? params : idNames[id])); + replies.topic += count; + replies.names += count; } static void commandPart(size_t id, char *params) { diff --git a/handle.c b/handle.c index 8ebc3b1..0780767 100644 --- a/handle.c +++ b/handle.c @@ -25,6 +25,8 @@ #include "chat.h" +struct Replies replies; + static const char *CapNames[] = { #define X(name, id) [id##Bit] = name, ENUM_CAP @@ -156,7 +158,15 @@ static void handleReplyWelcome(struct Message *msg) { require(msg, false, 1); set(&self.nick, msg->params[0]); completeTouch(None, self.nick, Default); - if (self.join) ircFormat("JOIN %s\r\n", self.join); + if (self.join) { + size_t count = 1; + for (const char *ch = self.join; *ch && *ch != ' '; ++ch) { + if (*ch == ',') count++; + } + ircFormat("JOIN %s\r\n", self.join); + replies.topic += count; + replies.names += count; + } } static void handleReplyISupport(struct Message *msg) { @@ -278,6 +288,7 @@ static void handleQuit(struct Message *msg) { static void handleReplyNames(struct Message *msg) { require(msg, false, 4); + if (!replies.names) return; size_t id = idFor(msg->params[2]); char buf[1024]; size_t len = 0; @@ -302,8 +313,15 @@ static void handleReplyNames(struct Message *msg) { ); } +static void handleReplyEndOfNames(struct Message *msg) { + (void)msg; + if (replies.names) replies.names--; +} + static void handleReplyNoTopic(struct Message *msg) { require(msg, false, 2); + if (!replies.topic) return; + replies.topic--; uiFormat( idFor(msg->params[1]), Cold, tagTime(msg), "There is no sign in \3%02d%s\3", @@ -313,6 +331,8 @@ static void handleReplyNoTopic(struct Message *msg) { static void handleReplyTopic(struct Message *msg) { require(msg, false, 3); + if (!replies.topic) return; + replies.topic--; uiFormat( idFor(msg->params[1]), Cold, tagTime(msg), "The sign in \3%02d%s\3 reads: %s", @@ -421,6 +441,7 @@ static const struct Handler { { "331", handleReplyNoTopic }, { "332", handleReplyTopic }, { "353", handleReplyNames }, + { "366", handleReplyEndOfNames }, { "372", handleReplyMOTD }, { "432", handleErrorErroneousNickname }, { "433", handleErrorNicknameInUse }, -- 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.h') 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 f502260dd0aa73b09bfbb7289b50a67592866166 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 18:29:01 -0500 Subject: Scan messages for URLs --- Makefile | 1 + catgirl.1 | 9 ++++++ chat.h | 4 +++ command.c | 11 ++++++++ handle.c | 15 ++++++++-- url.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 url.c (limited to 'chat.h') diff --git a/Makefile b/Makefile index 48aba7b..bcbb0d8 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ OBJS += edit.o OBJS += handle.o OBJS += irc.o OBJS += ui.o +OBJS += url.o dev: tags all diff --git a/catgirl.1 b/catgirl.1 index 5394d33..f489d07 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -156,6 +156,15 @@ Close the named, numbered or current window. Toggle logging in the .Sy window. +.It Ic /open Op Ar count +Open each of +.Ar count +most recent URLs. +.It Ic /open Ar nick | substring +Open the most recent URL from +.Ar nick +or matching +.Ar substring . .It Ic /window Ar name Switch to window by name. .It Ic /window Ar num , Ic / Ns Ar num diff --git a/chat.h b/chat.h index 909527e..583107a 100644 --- a/chat.h +++ b/chat.h @@ -169,6 +169,10 @@ void completeClear(size_t id); size_t completeID(const char *str); enum Color completeColor(size_t id, const char *str); +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); + 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 eaabc9c..4100928 100644 --- a/command.c +++ b/command.c @@ -144,6 +144,16 @@ static void commandClose(size_t id, char *params) { } } +static void commandOpen(size_t id, char *params) { + if (!params) { + urlOpenCount(id, 1); + } else if (isdigit(params[0])) { + urlOpenCount(id, strtoul(params, NULL, 10)); + } else { + urlOpenMatch(id, params); + } +} + static const struct Handler { const char *cmd; Command *fn; @@ -155,6 +165,7 @@ static const struct Handler { { "/names", commandNames }, { "/nick", commandNick }, { "/notice", commandNotice }, + { "/open", commandOpen }, { "/part", commandPart }, { "/query", commandQuery }, { "/quit", commandQuit }, diff --git a/handle.c b/handle.c index 0780767..f919fcb 100644 --- a/handle.c +++ b/handle.c @@ -193,6 +193,7 @@ static void handleReplyISupport(struct Message *msg) { static void handleReplyMOTD(struct Message *msg) { require(msg, false, 2); char *line = msg->params[1]; + urlScan(Network, msg->nick, line); if (!strncmp(line, "- ", 2)) { uiFormat(Network, Cold, tagTime(msg), "\3%d-\3\t%s", Gray, &line[2]); } else { @@ -227,6 +228,7 @@ static void handlePart(struct Message *msg) { completeClear(id); } completeRemove(id, msg->nick); + urlScan(id, msg->nick, msg->params[1]); uiFormat( id, Cold, tagTime(msg), "\3%02d%s\3\tleaves \3%02d%s\3%s%s", @@ -241,6 +243,7 @@ static void handleKick(struct Message *msg) { size_t id = idFor(msg->params[0]); bool kicked = self.nick && !strcmp(msg->params[1], self.nick); completeTouch(id, msg->nick, hash(msg->user)); + urlScan(id, msg->nick, msg->params[2]); uiFormat( id, (kicked ? Hot : Cold), tagTime(msg), "%s\3%02d%s\17\tkicks \3%02d%s\3 out of \3%02d%s\3%s%s", @@ -275,6 +278,7 @@ static void handleQuit(struct Message *msg) { require(msg, true, 0); size_t id; while (None != (id = completeID(msg->nick))) { + urlScan(id, msg->nick, msg->params[0]); uiFormat( id, Cold, tagTime(msg), "\3%02d%s\3\tleaves%s%s", @@ -333,8 +337,10 @@ static void handleReplyTopic(struct Message *msg) { require(msg, false, 3); if (!replies.topic) return; replies.topic--; + size_t id = idFor(msg->params[1]); + urlScan(id, NULL, msg->params[2]); uiFormat( - idFor(msg->params[1]), Cold, tagTime(msg), + id, Cold, tagTime(msg), "The sign in \3%02d%s\3 reads: %s", hash(msg->params[1]), msg->params[1], msg->params[2] ); @@ -342,16 +348,18 @@ static void handleReplyTopic(struct Message *msg) { static void handleTopic(struct Message *msg) { require(msg, true, 2); + size_t id = idFor(msg->params[0]); if (msg->params[1][0]) { + urlScan(id, msg->nick, msg->params[1]); uiFormat( - idFor(msg->params[0]), Warm, tagTime(msg), + id, Warm, tagTime(msg), "\3%02d%s\3\tplaces a new sign in \3%02d%s\3: %s", hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0], msg->params[1] ); } else { uiFormat( - idFor(msg->params[0]), Warm, tagTime(msg), + id, Warm, tagTime(msg), "\3%02d%s\3\tremoves the sign in \3%02d%s\3", hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0] ); @@ -400,6 +408,7 @@ static void handlePrivmsg(struct Message *msg) { bool action = isAction(msg); bool mention = !mine && isMention(msg); if (!notice && !mine) completeTouch(id, msg->nick, hash(msg->user)); + urlScan(id, msg->nick, msg->params[1]); if (notice) { uiFormat( id, Warm, tagTime(msg), diff --git a/url.c b/url.c new file mode 100644 index 0000000..7790461 --- /dev/null +++ b/url.c @@ -0,0 +1,96 @@ +/* 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" + +static const char *Pattern = { + "(" + "cvs|" + "ftp|" + "git|" + "gopher|" + "http|" + "https|" + "irc|" + "ircs|" + "magnet|" + "sftp|" + "ssh|" + "svn|" + "telnet|" + "vnc" + ")" + ":[^[:space:]>\"]+" +}; +static regex_t Regex; + +static void compile(void) { + static bool compiled; + if (compiled) return; + compiled = true; + int error = regcomp(&Regex, Pattern, REG_EXTENDED); + if (!error) return; + char buf[256]; + regerror(error, &Regex, buf, sizeof(buf)); + errx(EX_SOFTWARE, "regcomp: %s: %s", buf, Pattern); +} + +enum { Cap = 32 }; +static struct { + size_t ids[Cap]; + char *nicks[Cap]; + char *urls[Cap]; + size_t len; +} ring; + +static void push(size_t id, const char *nick, const char *url, size_t len) { + size_t i = ring.len++ % Cap; + free(ring.nicks[i]); + free(ring.urls[i]); + ring.ids[i] = id; + ring.nicks[i] = NULL; + if (nick) { + ring.nicks[i] = strdup(nick); + if (!ring.nicks[i]) err(EX_OSERR, "strdup"); + } + ring.urls[i] = strndup(url, len); + if (!ring.urls[i]) err(EX_OSERR, "strndup"); +} + +void urlScan(size_t id, const char *nick, const char *mesg) { + if (!mesg) return; + compile(); + regmatch_t match = {0}; + for (const char *ptr = mesg; *ptr; ptr += match.rm_eo) { + if (regexec(&Regex, ptr, 1, &match, 0)) break; + push(id, nick, &ptr[match.rm_so], match.rm_eo - match.rm_so); + } +} + +void urlOpenCount(size_t id, size_t count) { + // TODO +} + +void urlOpenMatch(size_t id, const char *str) { + // TODO +} -- 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.h') 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.h') 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 a212a7ae2c93092068c8a9c483c4575cc65e7491 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 00:53:55 -0500 Subject: Show realname on JOIN if it is different from nick --- catgirl.1 | 10 +++++++++- chat.h | 1 + handle.c | 11 +++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) (limited to 'chat.h') diff --git a/catgirl.1 b/catgirl.1 index 4dabb4f..fd00105 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -1,4 +1,4 @@ -.Dd February 8, 2020 +.Dd February 9, 2020 .Dt CATGIRL 1 .Os . @@ -287,6 +287,14 @@ join = #ascii.town .Bl -item .It .Rs +.%A Kiyoshi Aman +.%T IRCv3.1 extended-join Extension +.%I IRCv3 Working Group +.%U https://ircv3.net/specs/extensions/extended-join-3.1 +.Re +. +.It +.Rs .%A Waldo Bastian .%A Ryan Lortie .%A Lennart Poettering diff --git a/chat.h b/chat.h index 8bc8e81..896549e 100644 --- a/chat.h +++ b/chat.h @@ -59,6 +59,7 @@ static inline size_t idFor(const char *name) { } #define ENUM_CAP \ + X("extended-join", CapExtendedJoin) \ X("sasl", CapSASL) \ X("server-time", CapServerTime) \ X("userhost-in-names", CapUserhostInNames) diff --git a/handle.c b/handle.c index cf0e853..0297595 100644 --- a/handle.c +++ b/handle.c @@ -214,10 +214,17 @@ static void handleJoin(struct Message *msg) { uiShowID(id); } completeTouch(id, msg->nick, hash(msg->user)); + if (msg->params[2] && !strcasecmp(msg->params[2], msg->nick)) { + msg->params[2] = NULL; + } uiFormat( id, Cold, tagTime(msg), - "\3%02d%s\3\tarrives in \3%02d%s\3", - hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0] + "\3%02d%s\3\t%s%s%sarrives in \3%02d%s\3", + hash(msg->user), msg->nick, + (msg->params[2] ? "(" : ""), + (msg->params[2] ? msg->params[2] : ""), + (msg->params[2] ? ") " : ""), + hash(msg->params[0]), msg->params[0] ); } -- cgit 1.4.1 From 2d62ea9e30e7249e7f3e5bc3e60f5c8e97b3e2cc Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 01:28:24 -0500 Subject: Simplify edit buffer conversion and input rendering --- chat.h | 3 +-- edit.c | 25 +++++++++++----------- ui.c | 76 ++++++++++++++++++++++++++++++++++++------------------------------ 3 files changed, 55 insertions(+), 49 deletions(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 896549e..914fde6 100644 --- a/chat.h +++ b/chat.h @@ -156,8 +156,7 @@ enum Edit { EditEnter, }; void edit(size_t id, enum Edit op, wchar_t ch); -char *editHead(void); -char *editTail(void); +char *editBuffer(size_t *pos); const char *complete(size_t id, const char *prefix); void completeAccept(void); diff --git a/edit.c b/edit.c index 0c50f33..38dadcd 100644 --- a/edit.c +++ b/edit.c @@ -27,21 +27,22 @@ static wchar_t buf[Cap]; static size_t len; static size_t pos; -char *editHead(void) { +char *editBuffer(size_t *mbsPos) { static char mbs[MB_LEN_MAX * Cap]; + const wchar_t *ptr = buf; - size_t n = wcsnrtombs(mbs, &ptr, pos, sizeof(mbs) - 1, NULL); - assert(n != (size_t)-1); - mbs[n] = '\0'; - return mbs; -} + size_t mbsLen = wcsnrtombs(mbs, &ptr, pos, sizeof(mbs) - 1, NULL); + assert(mbsLen != (size_t)-1); + if (mbsPos) *mbsPos = mbsLen; -char *editTail(void) { - static char mbs[MB_LEN_MAX * Cap]; - const wchar_t *ptr = &buf[pos]; - size_t n = wcsnrtombs(mbs, &ptr, len - pos, sizeof(mbs) - 1, NULL); + ptr = &buf[pos]; + size_t n = wcsnrtombs( + &mbs[mbsLen], &ptr, len - pos, sizeof(mbs) - mbsLen - 1, NULL + ); assert(n != (size_t)-1); - mbs[n] = '\0'; + mbsLen += n; + + mbs[mbsLen] = '\0'; return mbs; } @@ -78,7 +79,7 @@ void edit(size_t id, enum Edit op, wchar_t ch) { } break; case EditEnter: { pos = 0; - command(id, editTail()); + command(id, editBuffer(NULL)); len = 0; } } diff --git a/ui.c b/ui.c index 23bf929..c342339 100644 --- a/ui.c +++ b/ui.c @@ -507,48 +507,54 @@ static void inputAdd(struct Style *style, const char *str) { static void inputUpdate(void) { size_t id = windows.active->id; - const char *nick = self.nick; - const char *head = editHead(); + size_t pos; + char *buf = editBuffer(&pos); + const char *skip = NULL; - const char *pre = ""; - const char *suf = " "; - struct Style style = { .fg = self.color, .bg = Default }; - struct Style reset = Reset; - if (NULL != (skip = commandIsPrivmsg(id, head))) { - pre = "<"; - suf = "> "; - } else if (NULL != (skip = commandIsNotice(id, head))) { - pre = "-"; - suf = "- "; - reset.fg = LightGray; - } else if (NULL != (skip = commandIsAction(id, head))) { - style.attr |= A_ITALIC; - pre = "* "; - reset.attr |= A_ITALIC; + struct Style init = { .fg = self.color, .bg = Default }; + struct Style rest = Reset; + const char *prefix = ""; + const char *prompt = (self.nick ? self.nick : ""); + const char *suffix = ""; + if (NULL != (skip = commandIsPrivmsg(id, buf))) { + prefix = "<"; suffix = "> "; + } else if (NULL != (skip = commandIsNotice(id, buf))) { + prefix = "-"; suffix = "- "; + rest.fg = LightGray; + } else if (NULL != (skip = commandIsAction(id, buf))) { + init.attr |= A_ITALIC; + prefix = "* "; suffix = " "; + rest.attr |= A_ITALIC; } else if (id == Debug) { - skip = head; - style.fg = Gray; - pre = "<<"; - nick = NULL; + skip = buf; + init.fg = Gray; + prompt = "<< "; + } else { + prompt = ""; + } + if (skip && skip > &buf[pos]) { + skip = NULL; + prefix = prompt = suffix = ""; } int y, x; wmove(input, 0, 0); - if (skip) { - wattr_set( - input, - style.attr | colorAttr(mapColor(style.fg)), - colorPair(mapColor(style.fg), mapColor(style.bg)), - NULL - ); - waddstr(input, pre); - if (nick) waddstr(input, nick); - waddstr(input, suf); - } - style = reset; - inputAdd(&style, (skip ? skip : head)); + wattr_set( + input, + init.attr | colorAttr(mapColor(init.fg)), + colorPair(mapColor(init.fg), mapColor(init.bg)), + NULL + ); + waddstr(input, prefix); + waddstr(input, prompt); + waddstr(input, suffix); + struct Style style = rest; + char p = buf[pos]; + buf[pos] = '\0'; + inputAdd(&style, (skip ? skip : buf)); getyx(input, y, x); - inputAdd(&style, editTail()); + buf[pos] = p; + inputAdd(&style, &buf[pos]); wclrtoeol(input); wmove(input, y, x); } -- cgit 1.4.1 From 282de9af30793f9935fe521ad95ffc253bd4f474 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 03:45:44 -0500 Subject: Add C-d --- chat.h | 1 + edit.c | 5 +++-- ui.c | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 914fde6..d6d9e1c 100644 --- a/chat.h +++ b/chat.h @@ -151,6 +151,7 @@ enum Edit { EditRight, EditKill, EditErase, + EditDelete, EditInsert, EditComplete, EditEnter, diff --git a/edit.c b/edit.c index f058f0a..c30e725 100644 --- a/edit.c +++ b/edit.c @@ -138,8 +138,9 @@ void edit(size_t id, enum Edit op, wchar_t ch) { break; case EditLeft: if (pos) pos--; break; case EditRight: if (pos < len) pos++; - break; case EditKill: len = pos = 0; - break; case EditErase: if (pos) delete(--pos, 1); + break; case EditKill: len = pos = 0; + break; case EditErase: if (pos) delete(--pos, 1); + break; case EditDelete: delete(pos, 1); break; case EditInsert: { reserve(pos, 1); diff --git a/ui.c b/ui.c index 4478478..8e502ca 100644 --- a/ui.c +++ b/ui.c @@ -625,6 +625,7 @@ static void keyCode(int code) { break; case KeyMetaM: waddch(windows.active->pad, '\n'); break; case KEY_BACKSPACE: edit(id, EditErase, 0); + break; case KEY_DC: edit(id, EditDelete, 0); break; case KEY_END: edit(id, EditEnd, 0); break; case KEY_ENTER: edit(id, EditEnter, 0); break; case KEY_HOME: edit(id, EditHome, 0); @@ -645,6 +646,7 @@ static void keyCtrl(wchar_t ch) { break; case L'?': edit(id, EditErase, 0); break; case L'A': edit(id, EditHome, 0); break; case L'B': edit(id, EditLeft, 0); + break; case L'D': edit(id, EditDelete, 0); break; case L'E': edit(id, EditEnd, 0); break; case L'F': edit(id, EditRight, 0); break; case L'H': edit(id, EditErase, 0); -- cgit 1.4.1 From d7c96fc81b71b77b30511d6526fe3acaa84c39ee Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 03:56:18 -0500 Subject: Add C-k Also rename all the edit ops to something consistent. --- catgirl.1 | 6 +++++- chat.h | 15 ++++++++------- edit.c | 17 +++++++++-------- ui.c | 29 +++++++++++++++-------------- 4 files changed, 37 insertions(+), 30 deletions(-) (limited to 'chat.h') diff --git a/catgirl.1 b/catgirl.1 index f68e6c3..a356fe0 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -201,12 +201,16 @@ Switch to window by number. Move to beginning of line. .It Ic C-b Move left. +.It Ic C-d +Delete next character. .It Ic C-e Move to end of line. .It Ic C-f Move right. +.It Ic C-k +Delete to end of line. .It Ic C-u -Delete line. +Delete to beginning of line. .El . .Ss Window Keys diff --git a/chat.h b/chat.h index d6d9e1c..aa1bcc1 100644 --- a/chat.h +++ b/chat.h @@ -145,13 +145,14 @@ void uiFormat( ) __attribute__((format(printf, 4, 5))); enum Edit { - EditHome, - EditEnd, - EditLeft, - EditRight, - EditKill, - EditErase, - EditDelete, + EditHead, + EditTail, + EditPrev, + EditNext, + EditKillPrev, + EditKillNext, + EditDeletePrev, + EditDeleteNext, EditInsert, EditComplete, EditEnter, diff --git a/edit.c b/edit.c index c30e725..7fcff40 100644 --- a/edit.c +++ b/edit.c @@ -133,14 +133,15 @@ static void tabReject(void) { void edit(size_t id, enum Edit op, wchar_t ch) { size_t init = pos; switch (op) { - break; case EditHome: pos = 0; - break; case EditEnd: pos = len; - break; case EditLeft: if (pos) pos--; - break; case EditRight: if (pos < len) pos++; - - break; case EditKill: len = pos = 0; - break; case EditErase: if (pos) delete(--pos, 1); - break; case EditDelete: delete(pos, 1); + break; case EditHead: pos = 0; + break; case EditTail: pos = len; + break; case EditPrev: if (pos) pos--; + break; case EditNext: if (pos < len) pos++; + + break; case EditDeletePrev: if (pos) delete(--pos, 1); + break; case EditDeleteNext: delete(pos, 1); + break; case EditKillPrev: delete(0, pos); pos = 0; + break; case EditKillNext: delete(pos, len - pos); break; case EditInsert: { reserve(pos, 1); diff --git a/ui.c b/ui.c index 8e502ca..d83a1f3 100644 --- a/ui.c +++ b/ui.c @@ -624,13 +624,13 @@ static void keyCode(int code) { break; case KeyMetaM: waddch(windows.active->pad, '\n'); - break; case KEY_BACKSPACE: edit(id, EditErase, 0); - break; case KEY_DC: edit(id, EditDelete, 0); - break; case KEY_END: edit(id, EditEnd, 0); + break; case KEY_BACKSPACE: edit(id, EditDeletePrev, 0); + break; case KEY_DC: edit(id, EditDeleteNext, 0); + break; case KEY_END: edit(id, EditTail, 0); break; case KEY_ENTER: edit(id, EditEnter, 0); - break; case KEY_HOME: edit(id, EditHome, 0); - break; case KEY_LEFT: edit(id, EditLeft, 0); - break; case KEY_RIGHT: edit(id, EditRight, 0); + break; case KEY_HOME: edit(id, EditHead, 0); + break; case KEY_LEFT: edit(id, EditPrev, 0); + break; case KEY_RIGHT: edit(id, EditNext, 0); break; default: { if (code >= KeyMeta0 && code <= KeyMeta9) { @@ -643,17 +643,18 @@ static void keyCode(int code) { static void keyCtrl(wchar_t ch) { size_t id = windows.active->id; switch (ch ^ L'@') { - break; case L'?': edit(id, EditErase, 0); - break; case L'A': edit(id, EditHome, 0); - break; case L'B': edit(id, EditLeft, 0); - break; case L'D': edit(id, EditDelete, 0); - break; case L'E': edit(id, EditEnd, 0); - break; case L'F': edit(id, EditRight, 0); - break; case L'H': edit(id, EditErase, 0); + break; case L'?': edit(id, EditDeletePrev, 0); + break; case L'A': edit(id, EditHead, 0); + break; case L'B': edit(id, EditPrev, 0); + break; case L'D': edit(id, EditDeleteNext, 0); + break; case L'E': edit(id, EditTail, 0); + break; case L'F': edit(id, EditNext, 0); + break; case L'H': edit(id, EditDeletePrev, 0); break; case L'I': edit(id, EditComplete, 0); break; case L'J': edit(id, EditEnter, 0); + break; case L'K': edit(id, EditKillNext, 0); break; case L'L': clearok(curscr, true); - break; case L'U': edit(id, EditKill, 0); + break; case L'U': edit(id, EditKillPrev, 0); } } -- cgit 1.4.1 From b08c2d03efa08bd319a0665d12bef34df08ab283 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 04:20:07 -0500 Subject: Add M-b and M-f --- catgirl.1 | 4 ++++ chat.h | 2 ++ edit.c | 8 ++++++++ ui.c | 4 ++++ 4 files changed, 18 insertions(+) (limited to 'chat.h') diff --git a/catgirl.1 b/catgirl.1 index a356fe0..9cb208e 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -211,6 +211,10 @@ Move right. Delete to end of line. .It Ic C-u Delete to beginning of line. +.It Ic M-b +Move to previous word. +.It Ic M-f +Move to next word. .El . .Ss Window Keys diff --git a/chat.h b/chat.h index aa1bcc1..6b68eae 100644 --- a/chat.h +++ b/chat.h @@ -149,6 +149,8 @@ enum Edit { EditTail, EditPrev, EditNext, + EditPrevWord, + EditNextWord, EditKillPrev, EditKillNext, EditDeletePrev, diff --git a/edit.c b/edit.c index 7fcff40..38b2dea 100644 --- a/edit.c +++ b/edit.c @@ -137,6 +137,14 @@ void edit(size_t id, enum Edit op, wchar_t ch) { break; case EditTail: pos = len; break; case EditPrev: if (pos) pos--; break; case EditNext: if (pos < len) pos++; + break; case EditPrevWord: { + if (pos) pos--; + while (pos && buf[pos - 1] != L' ') pos--; + } + break; case EditNextWord: { + if (pos < len) pos++; + while (pos < len && buf[pos] != L' ') pos++; + } break; case EditDeletePrev: if (pos) delete(--pos, 1); break; case EditDeleteNext: delete(pos, 1); diff --git a/ui.c b/ui.c index d83a1f3..7811e88 100644 --- a/ui.c +++ b/ui.c @@ -192,6 +192,8 @@ static void errExit(void) { X(KeyMeta7, "\0337") \ X(KeyMeta8, "\0338") \ X(KeyMeta9, "\0339") \ + X(KeyMetaB, "\033b") \ + X(KeyMetaF, "\033f") \ X(KeyMetaM, "\33m") \ X(KeyFocusIn, "\33[I") \ X(KeyFocusOut, "\33[O") \ @@ -622,6 +624,8 @@ static void keyCode(int code) { break; case KeyPasteOn:; // TODO break; case KeyPasteOff:; // TODO + break; case KeyMetaB: edit(id, EditPrevWord, 0); + break; case KeyMetaF: edit(id, EditNextWord, 0); break; case KeyMetaM: waddch(windows.active->pad, '\n'); break; case KEY_BACKSPACE: edit(id, EditDeletePrev, 0); -- cgit 1.4.1 From 3cd830681e25022a8a3936ca9fe58d149fbe493a Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 04:22:41 -0500 Subject: Rename kill ops --- chat.h | 4 ++-- edit.c | 4 ++-- ui.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 6b68eae..ac56f51 100644 --- a/chat.h +++ b/chat.h @@ -151,8 +151,8 @@ enum Edit { EditNext, EditPrevWord, EditNextWord, - EditKillPrev, - EditKillNext, + EditDeleteHead, + EditDeleteTail, EditDeletePrev, EditDeleteNext, EditInsert, diff --git a/edit.c b/edit.c index 38b2dea..7b20079 100644 --- a/edit.c +++ b/edit.c @@ -148,8 +148,8 @@ void edit(size_t id, enum Edit op, wchar_t ch) { break; case EditDeletePrev: if (pos) delete(--pos, 1); break; case EditDeleteNext: delete(pos, 1); - break; case EditKillPrev: delete(0, pos); pos = 0; - break; case EditKillNext: delete(pos, len - pos); + break; case EditDeleteHead: delete(0, pos); pos = 0; + break; case EditDeleteTail: delete(pos, len - pos); break; case EditInsert: { reserve(pos, 1); diff --git a/ui.c b/ui.c index 7811e88..e3b9cb5 100644 --- a/ui.c +++ b/ui.c @@ -656,9 +656,9 @@ static void keyCtrl(wchar_t ch) { break; case L'H': edit(id, EditDeletePrev, 0); break; case L'I': edit(id, EditComplete, 0); break; case L'J': edit(id, EditEnter, 0); - break; case L'K': edit(id, EditKillNext, 0); + break; case L'K': edit(id, EditDeleteTail, 0); break; case L'L': clearok(curscr, true); - break; case L'U': edit(id, EditKillPrev, 0); + break; case L'U': edit(id, EditDeleteHead, 0); } } -- cgit 1.4.1 From 5e637324c9f2b16a602c1b66081390624598c703 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 04:32:32 -0500 Subject: Add C-w and M-d --- catgirl.1 | 4 ++++ chat.h | 2 ++ edit.c | 17 +++++++++++++++-- ui.c | 7 +++++-- 4 files changed, 26 insertions(+), 4 deletions(-) (limited to 'chat.h') diff --git a/catgirl.1 b/catgirl.1 index 9cb208e..6f8256b 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -211,8 +211,12 @@ Move right. Delete to end of line. .It Ic C-u Delete to beginning of line. +.It Ic C-w +Delete previous word. .It Ic M-b Move to previous word. +.It Ic M-d +Delete next word. .It Ic M-f Move to next word. .El diff --git a/chat.h b/chat.h index ac56f51..fc18b15 100644 --- a/chat.h +++ b/chat.h @@ -155,6 +155,8 @@ enum Edit { EditDeleteTail, EditDeletePrev, EditDeleteNext, + EditDeletePrevWord, + EditDeleteNextWord, EditInsert, EditComplete, EditEnter, diff --git a/edit.c b/edit.c index 7b20079..47478ec 100644 --- a/edit.c +++ b/edit.c @@ -146,10 +146,23 @@ void edit(size_t id, enum Edit op, wchar_t ch) { while (pos < len && buf[pos] != L' ') pos++; } - break; case EditDeletePrev: if (pos) delete(--pos, 1); - break; case EditDeleteNext: delete(pos, 1); break; case EditDeleteHead: delete(0, pos); pos = 0; break; case EditDeleteTail: delete(pos, len - pos); + break; case EditDeletePrev: if (pos) delete(--pos, 1); + break; case EditDeleteNext: delete(pos, 1); + break; case EditDeletePrevWord: { + if (!pos) break; + size_t word = pos - 1; + while (word && buf[word - 1] != L' ') word--; + delete(word, pos - word); + pos = word; + } + break; case EditDeleteNextWord: { + if (pos == len) break; + size_t word = pos + 1; + while (word < len && buf[word] != L' ') word++; + delete(pos, word - pos); + } break; case EditInsert: { reserve(pos, 1); diff --git a/ui.c b/ui.c index e3b9cb5..65b4760 100644 --- a/ui.c +++ b/ui.c @@ -192,8 +192,9 @@ static void errExit(void) { X(KeyMeta7, "\0337") \ X(KeyMeta8, "\0338") \ X(KeyMeta9, "\0339") \ - X(KeyMetaB, "\033b") \ - X(KeyMetaF, "\033f") \ + X(KeyMetaB, "\33b") \ + X(KeyMetaD, "\33d") \ + X(KeyMetaF, "\33f") \ X(KeyMetaM, "\33m") \ X(KeyFocusIn, "\33[I") \ X(KeyFocusOut, "\33[O") \ @@ -625,6 +626,7 @@ static void keyCode(int code) { break; case KeyPasteOff:; // TODO break; case KeyMetaB: edit(id, EditPrevWord, 0); + break; case KeyMetaD: edit(id, EditDeleteNextWord, 0); break; case KeyMetaF: edit(id, EditNextWord, 0); break; case KeyMetaM: waddch(windows.active->pad, '\n'); @@ -659,6 +661,7 @@ static void keyCtrl(wchar_t ch) { break; case L'K': edit(id, EditDeleteTail, 0); break; case L'L': clearok(curscr, true); break; case L'U': edit(id, EditDeleteHead, 0); + break; case L'W': edit(id, EditDeletePrevWord, 0); } } -- cgit 1.4.1 From 2aa2005339750e64a587f6117ae21960e975e211 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 07:09:51 -0500 Subject: Add C-y This is weechat's binding for it. --- catgirl.1 | 2 ++ chat.h | 1 + edit.c | 26 ++++++++++++++++++++++---- ui.c | 7 +++++-- 4 files changed, 30 insertions(+), 6 deletions(-) (limited to 'chat.h') diff --git a/catgirl.1 b/catgirl.1 index 752a9d2..2a3828d 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -226,6 +226,8 @@ Delete to end of line. Delete to beginning of line. .It Ic C-w Delete previous word. +.It Ic C-y +Paste previously deleted text. .It Ic M-b Move to previous word. .It Ic M-d diff --git a/chat.h b/chat.h index fc18b15..24360f0 100644 --- a/chat.h +++ b/chat.h @@ -157,6 +157,7 @@ enum Edit { EditDeleteNext, EditDeletePrevWord, EditDeleteNextWord, + EditPaste, EditInsert, EditComplete, EditEnter, diff --git a/edit.c b/edit.c index 47478ec..16fa910 100644 --- a/edit.c +++ b/edit.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -46,14 +47,24 @@ char *editBuffer(size_t *mbsPos) { return mbs; } -static void reserve(size_t index, size_t count) { - if (len + count > Cap) return; +static struct { + wchar_t buf[Cap]; + size_t len; +} cut; + +static bool reserve(size_t index, size_t count) { + if (len + count > Cap) return false; memmove(&buf[index + count], &buf[index], sizeof(*buf) * (len - index)); len += count; + return true; } static void delete(size_t index, size_t count) { if (index + count > len) return; + if (count > 1) { + memcpy(cut.buf, &buf[index], sizeof(*buf) * count); + cut.len = count; + } memmove( &buf[index], &buf[index + count], sizeof(*buf) * (len - index - count) ); @@ -163,10 +174,17 @@ void edit(size_t id, enum Edit op, wchar_t ch) { while (word < len && buf[word] != L' ') word++; delete(pos, word - pos); } + break; case EditPaste: { + if (reserve(pos, cut.len)) { + memcpy(&buf[pos], cut.buf, sizeof(*buf) * cut.len); + pos += cut.len; + } + } break; case EditInsert: { - reserve(pos, 1); - if (pos < Cap) buf[pos++] = ch; + if (reserve(pos, 1)) { + buf[pos++] = ch; + } } break; case EditComplete: { tabComplete(id); diff --git a/ui.c b/ui.c index 65b4760..d946854 100644 --- a/ui.c +++ b/ui.c @@ -166,12 +166,14 @@ void uiHide(void) { endwin(); } -static void disableFlowControl(void) { +// Gain use of C-q, C-s, C-z, C-y, C-o. +static void acquireKeys(void) { struct termios term; int error = tcgetattr(STDOUT_FILENO, &term); if (error) err(EX_OSERR, "tcgetattr"); term.c_iflag &= ~IXON; term.c_cc[VSUSP] = _POSIX_VDISABLE; + term.c_cc[VDSUSP] = _POSIX_VDISABLE; term.c_cc[VDISCARD] = _POSIX_VDISABLE; error = tcsetattr(STDOUT_FILENO, TCSADRAIN, &term); if (error) err(EX_OSERR, "tcsetattr"); @@ -212,7 +214,7 @@ void uiInit(void) { initscr(); cbreak(); noecho(); - disableFlowControl(); + acquireKeys(); def_prog_mode(); atexit(errExit); colorInit(); @@ -662,6 +664,7 @@ static void keyCtrl(wchar_t ch) { break; case L'L': clearok(curscr, true); break; case L'U': edit(id, EditDeleteHead, 0); break; case L'W': edit(id, EditDeletePrevWord, 0); + break; case L'Y': edit(id, EditPaste, 0); } } -- cgit 1.4.1 From 3436cd1068ca37cf4043bc8dc83e3b8890edcb2b Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 16:45:49 -0500 Subject: Add /whois --- catgirl.1 | 2 ++ chat.h | 1 + command.c | 8 +++++ handle.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+) (limited to 'chat.h') diff --git a/catgirl.1 b/catgirl.1 index 8679f22..ac558a9 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -166,6 +166,8 @@ Quit IRC. Send a raw IRC command. .It Ic /topic Op Ar topic Show or set the topic of the channel. +.It Ic /whois Ar nick +Query information about a user. .El . .Ss UI Commands diff --git a/chat.h b/chat.h index 24360f0..f79cc70 100644 --- a/chat.h +++ b/chat.h @@ -120,6 +120,7 @@ void ircFormat(const char *format, ...) extern struct Replies { size_t topic; size_t names; + size_t whois; } replies; void handle(struct Message msg); diff --git a/command.c b/command.c index 6d9ef9b..3e201cc 100644 --- a/command.c +++ b/command.c @@ -124,6 +124,13 @@ static void commandNames(size_t id, char *params) { replies.names++; } +static void commandWhois(size_t id, char *params) { + (void)id; + if (!params) return; + ircFormat("WHOIS :%s\r\n", params); + replies.whois++; +} + static void commandQuery(size_t id, char *params) { if (!params) return; size_t query = idFor(params); @@ -203,6 +210,7 @@ static const struct Handler { { "/quit", commandQuit }, { "/quote", commandQuote }, { "/topic", commandTopic }, + { "/whois", commandWhois }, { "/window", commandWindow }, }; diff --git a/handle.c b/handle.c index 2cc7a25..2ef2477 100644 --- a/handle.c +++ b/handle.c @@ -374,6 +374,97 @@ static void handleTopic(struct Message *msg) { } } +static void handleReplyWhoisUser(struct Message *msg) { + require(msg, false, 6); + if (!replies.whois) return; + completeTouch(Network, msg->params[1], hash(msg->params[2])); + uiFormat( + Network, Warm, tagTime(msg), + "\3%02d%s\3\tis %s!%s@%s (%s)", + hash(msg->params[2]), msg->params[1], + msg->params[1], msg->params[2], msg->params[3], msg->params[5] + ); +} + +static void handleReplyWhoisServer(struct Message *msg) { + require(msg, false, 4); + if (!replies.whois) return; + uiFormat( + Network, Warm, tagTime(msg), + "\3%02d%s\3\tis connected to %s (%s)", + completeColor(Network, msg->params[1]), msg->params[1], + msg->params[2], msg->params[3] + ); +} + +static void handleReplyWhoisIdle(struct Message *msg) { + require(msg, false, 3); + if (!replies.whois) return; + unsigned long idle = strtoul(msg->params[2], NULL, 10); + const char *unit = "second"; + if (idle / 60) { idle /= 60; unit = "minute"; } + if (idle / 60) { idle /= 60; unit = "hour"; } + if (idle / 24) { idle /= 24; unit = "day"; } + time_t signon = (msg->params[3] ? strtoul(msg->params[3], NULL, 10) : 0); + uiFormat( + Network, Warm, tagTime(msg), + "\3%02d%s\3\tis idle for %lu %s%s%s%.*s", + completeColor(Network, msg->params[1]), msg->params[1], + idle, unit, (idle != 1 ? "s" : ""), + (signon ? ", signed on " : ""), + 24, (signon ? ctime(&signon) : "") + ); +} + +static void handleReplyWhoisChannels(struct Message *msg) { + require(msg, false, 3); + if (!replies.whois) return; + char buf[1024]; + size_t len = 0; + while (msg->params[2]) { + char *channel = strsep(&msg->params[2], " "); + channel += strspn(channel, self.prefixes); + int n = snprintf( + &buf[len], sizeof(buf) - len, + "%s\3%02d%s\3", (len ? ", " : ""), hash(channel), channel + ); + assert(n > 0 && len + n < sizeof(buf)); + len += n; + } + uiFormat( + Network, Warm, tagTime(msg), + "\3%02d%s\3\tis in %s", + completeColor(Network, msg->params[1]), msg->params[1], buf + ); +} + +static void handleReplyWhoisGeneric(struct Message *msg) { + require(msg, false, 3); + if (!replies.whois) return; + if (msg->params[3]) { + msg->params[0] = msg->params[2]; + msg->params[2] = msg->params[3]; + msg->params[3] = msg->params[0]; + } + uiFormat( + Network, Warm, tagTime(msg), + "\3%02d%s\3\t%s%s%s", + completeColor(Network, msg->params[1]), msg->params[1], + msg->params[2], + (msg->params[3] ? " " : ""), + (msg->params[3] ? msg->params[3] : "") + ); +} + +static void handleReplyEndOfWhois(struct Message *msg) { + require(msg, false, 2); + if (!replies.whois) return; + if (!self.nick || strcmp(msg->params[1], self.nick)) { + completeRemove(Network, msg->params[1]); + } + replies.whois--; +} + static bool isAction(struct Message *msg) { if (strncmp(msg->params[1], "\1ACTION ", 8)) return false; msg->params[1] += 8; @@ -495,6 +586,15 @@ static const struct Handler { } Handlers[] = { { "001", handleReplyWelcome }, { "005", handleReplyISupport }, + { "276", handleReplyWhoisGeneric }, + { "307", handleReplyWhoisGeneric }, + { "311", handleReplyWhoisUser }, + { "312", handleReplyWhoisServer }, + { "313", handleReplyWhoisGeneric }, + { "317", handleReplyWhoisIdle }, + { "318", handleReplyEndOfWhois }, + { "319", handleReplyWhoisChannels }, + { "330", handleReplyWhoisGeneric }, { "331", handleReplyNoTopic }, { "332", handleReplyTopic }, { "353", handleReplyNames }, @@ -502,6 +602,7 @@ static const struct Handler { { "372", handleReplyMOTD }, { "432", handleErrorErroneousNickname }, { "433", handleErrorNicknameInUse }, + { "671", handleReplyWhoisGeneric }, { "900", handleReplyLoggedIn }, { "904", handleErrorSASLFail }, { "905", handleErrorSASLFail }, -- cgit 1.4.1 From a91f975e9b86859f3605c75e42d52e73b1511101 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 05:37:47 -0500 Subject: Hash to colors in the range 2-75 Colors 76-87 seem too light, some almost appearing white. Colors 88-98 are shades of gray. --- chat.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index f79cc70..e7bb9cc 100644 --- a/chat.h +++ b/chat.h @@ -198,7 +198,16 @@ static inline enum Color hash(const char *str) { hash ^= *str; hash *= 0x27220A95; } - return 2 + hash % 14; + static const enum Color colors[] = { + Blue, Green, Red, Brown, Magenta, Orange, Yellow, + LightGreen, Cyan, LightCyan, LightBlue, Pink, Gray, LightGray, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + }; + return colors[hash % ARRAY_LEN(colors)]; } #define BASE64_SIZE(len) (1 + ((len) + 2) / 3 * 4) -- 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.h') 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 c8a6564670839cc526073a90e573939ec30cfcce Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 17:03:13 -0500 Subject: Eliminate array in hash I expected to have to remove some arbitrary colors, but it seems like just the range 2-75 works fine. --- chat.h | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 16cc683..13319da 100644 --- a/chat.h +++ b/chat.h @@ -199,16 +199,7 @@ static inline enum Color hash(const char *str) { hash ^= *str; hash *= 0x27220A95; } - static const enum Color colors[] = { - Blue, Green, Red, Brown, Magenta, Orange, Yellow, - LightGreen, Cyan, LightCyan, LightBlue, Pink, Gray, LightGray, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, - 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, - }; - return colors[hash % ARRAY_LEN(colors)]; + return 2 + hash % 74; } #define BASE64_SIZE(len) (1 + ((len) + 2) / 3 * 4) -- 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.h') 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.h') 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 80a79467efca8f17e440cb63009c60dd8e78cc63 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 20:24:07 -0500 Subject: Only automatically switch to expected joins --- chat.h | 1 + command.c | 1 + handle.c | 6 +++++- 3 files changed, 7 insertions(+), 1 deletion(-) (limited to 'chat.h') diff --git a/chat.h b/chat.h index 03a0a50..f47b244 100644 --- a/chat.h +++ b/chat.h @@ -120,6 +120,7 @@ void ircFormat(const char *format, ...) __attribute__((format(printf, 1, 2))); extern struct Replies { + size_t join; size_t topic; size_t names; size_t whois; diff --git a/command.c b/command.c index cab1d26..5cb43cf 100644 --- a/command.c +++ b/command.c @@ -86,6 +86,7 @@ static void commandJoin(size_t id, char *params) { } } ircFormat("JOIN %s\r\n", (params ? params : idNames[id])); + replies.join += count; replies.topic += count; replies.names += count; } diff --git a/handle.c b/handle.c index fd2a67f..0db7fd9 100644 --- a/handle.c +++ b/handle.c @@ -164,6 +164,7 @@ static void handleReplyWelcome(struct Message *msg) { if (*ch == ',') count++; } ircFormat("JOIN %s\r\n", self.join); + replies.join += count; replies.topic += count; replies.names += count; } @@ -211,7 +212,10 @@ static void handleJoin(struct Message *msg) { } idColors[id] = hash(msg->params[0]); completeTouch(None, msg->params[0], idColors[id]); - uiShowID(id); + if (replies.join) { + uiShowID(id); + replies.join--; + } } completeTouch(id, msg->nick, hash(msg->user)); if (msg->params[2] && !strcasecmp(msg->params[2], msg->nick)) { -- cgit 1.4.1