diff options
Diffstat (limited to 'chat.h')
-rw-r--r-- | chat.h | 594 |
1 files changed, 418 insertions, 176 deletions
diff --git a/chat.h b/chat.h index ee7a087..2a41cf6 100644 --- a/chat.h +++ b/chat.h @@ -1,220 +1,462 @@ -/* Copyright (C) 2018 C. McEnroe <june@causal.agency> +/* Copyright (C) 2020 June McEnroe <june@causal.agency> * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by + * 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 Affero General Public License for more details. + * GNU General Public License for more details. * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this Program, or any covered work, by linking or + * combining it with OpenSSL (or a modified version of that library), + * containing parts covered by the terms of the OpenSSL License and the + * original SSLeay license, the licensors of this Program grant you + * additional permission to convey the resulting work. Corresponding + * Source for a non-source form of such a combination shall include the + * source code for the parts of OpenSSL used as well as that of the + * covered work. */ -#define SOURCE_URL "https://git.causal.agency/catgirl" - +#include <assert.h> +#include <ctype.h> +#include <err.h> +#include <getopt.h> #include <stdarg.h> #include <stdbool.h> +#include <stdint.h> #include <stdio.h> -#include <stdlib.h> -#include <stdnoreturn.h> +#include <string.h> +#include <strings.h> +#include <sysexits.h> #include <time.h> #include <wchar.h> -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define MAX(a, b) ((a) > (b) ? (a) : (b)) - -#define err(...) do { uiHide(); err(__VA_ARGS__); } while (0) -#define errx(...) do { uiHide(); errx(__VA_ARGS__); } while (0) +#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) +#define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit typedef unsigned uint; typedef unsigned char byte; -struct { - char *host; - char *port; - char *auth; - char *pass; +static inline char *seprintf(char *ptr, char *end, const char *fmt, ...) + __attribute__((format(printf, 3, 4))); +static inline char *seprintf(char *ptr, char *end, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(ptr, end - ptr, fmt, ap); + va_end(ap); + if (n < 0) return NULL; + if (n > end - ptr) return end; + return ptr + n; +} + +enum Attr { + BIT(Bold), + BIT(Reverse), + BIT(Italic), + BIT(Underline), +}; +enum Color { + White, Black, Blue, Green, Red, Brown, Magenta, Orange, + Yellow, LightGreen, Cyan, LightCyan, LightBlue, Pink, Gray, LightGray, + Default = 99, + ColorCap, +}; +struct Style { + enum Attr attr; + enum Color fg, bg; +}; + +static const struct Style StyleDefault = { 0, Default, Default }; +enum { B = '\2', C = '\3', O = '\17', R = '\26', I = '\35', U = '\37' }; + +static inline size_t styleParse(struct Style *style, const char **str) { + switch (**str) { + break; case B: (*str)++; style->attr ^= Bold; + break; case O: (*str)++; *style = StyleDefault; + break; case R: (*str)++; style->attr ^= Reverse; + break; case I: (*str)++; style->attr ^= Italic; + break; case U: (*str)++; style->attr ^= Underline; + break; case C: { + (*str)++; + if (!isdigit(**str)) { + style->fg = Default; + style->bg = Default; + break; + } + style->fg = *(*str)++ - '0'; + if (isdigit(**str)) style->fg = style->fg * 10 + *(*str)++ - '0'; + if ((*str)[0] != ',' || !isdigit((*str)[1])) break; + (*str)++; + style->bg = *(*str)++ - '0'; + if (isdigit(**str)) style->bg = style->bg * 10 + *(*str)++ - '0'; + } + } + return strcspn(*str, (const char[]) { B, C, O, R, I, U, '\0' }); +} + +static inline void styleStrip(char *buf, size_t cap, const char *str) { + *buf = '\0'; + char *ptr = buf, *end = &buf[cap]; + struct Style style = StyleDefault; + while (*str) { + size_t len = styleParse(&style, &str); + ptr = seprintf(ptr, end, "%.*s", (int)len, str); + str += len; + } +} + +enum { None, Debug, Network, IDCap = 256 }; +extern char *idNames[IDCap]; +extern enum Color idColors[IDCap]; +extern uint idNext; + +static inline uint idFind(const char *name) { + for (uint id = 0; id < idNext; ++id) { + if (!strcasecmp(idNames[id], name)) return id; + } + return None; +} + +static inline uint idFor(const char *name) { + uint id = idFind(name); + if (id) return id; + if (idNext == IDCap) return Network; + idNames[idNext] = strdup(name); + idColors[idNext] = Default; + if (!idNames[idNext]) err(EX_OSERR, "strdup"); + return idNext++; +} + +extern uint32_t hashInit; +extern uint32_t hashBound; +static inline uint32_t _hash(const char *str) { + if (*str == '~') str++; + uint32_t hash = hashInit; + for (; *str; ++str) { + hash = (hash << 5) | (hash >> 27); + hash ^= *str; + hash *= 0x27220A95; + } + return hash; +} +static inline enum Color hash(const char *str) { + if (hashBound < Blue) return Default; + return Blue + _hash(str) % (hashBound + 1 - Blue); +} + +extern struct Network { + char *name; + uint userLen; + uint hostLen; + char *chanTypes; + char *statusmsg; + char *prefixes; + char *prefixModes; + char *listModes; + char *paramModes; + char *setParamModes; + char *channelModes; + char excepts; + char invex; +} network; + +static inline uint prefixBit(char p) { + char *s = strchr(network.prefixes, p); + if (!s) return 0; + return 1 << (s - network.prefixes); +} + +static inline char bitPrefix(uint p) { + for (uint i = 0; network.prefixes[i]; ++i) { + if (p & (1 << i)) return network.prefixes[i]; + } + return '\0'; +} + +#define ENUM_CAP \ + X("causal.agency/consumer", CapConsumer) \ + X("chghost", CapChghost) \ + X("extended-join", CapExtendedJoin) \ + X("invite-notify", CapInviteNotify) \ + X("message-tags", CapMessageTags) \ + X("multi-prefix", CapMultiPrefix) \ + X("sasl", CapSASL) \ + X("server-time", CapServerTime) \ + X("setname", CapSetname) \ + X("userhost-in-names", CapUserhostInNames) \ + X("znc.in/self-message", CapSelfMessage) + +enum Cap { +#define X(name, id) BIT(id), + ENUM_CAP +#undef X +}; + +extern struct Self { + bool debug; + bool restricted; + size_t pos; + enum Cap caps; + const char *plainUser; + char *plainPass; + const char *nicks[8]; + const char *mode; + const char *join; char *nick; char *user; - char *real; - char *join; - char *keys; - bool limit; - bool raw; - bool notify; - bool quit; + char *host; + enum Color color; + char *invited; + char *quit; } self; -void eventWait(const char *argv[static 2]); -void eventPipe(const char *argv[static 2]); -noreturn void eventLoop(void); +static inline void set(char **field, const char *value) { + free(*field); + *field = strdup(value); + if (!*field) err(EX_OSERR, "strdup"); +} + +#define ENUM_TAG \ + X("+draft/reply", TagReply) \ + X("causal.agency/pos", TagPos) \ + X("msgid", TagMsgID) \ + X("time", TagTime) -struct Tag { - size_t id; - const char *name; +enum Tag { +#define X(name, id) id, + ENUM_TAG +#undef X + TagCap, }; -enum { TagsLen = 256 }; -const struct Tag TagNone; -const struct Tag TagStatus; -const struct Tag TagRaw; -struct Tag tagFind(const char *name); -struct Tag tagFor(const char *name); - -enum IRCColor { - IRCWhite, - IRCBlack, - IRCBlue, - IRCGreen, - IRCRed, - IRCBrown, - IRCMagenta, - IRCOrange, - IRCYellow, - IRCLightGreen, - IRCCyan, - IRCLightCyan, - IRCLightBlue, - IRCPink, - IRCGray, - IRCLightGray, - IRCDefault = 99, +enum { ParamCap = 254 }; +struct Message { + char *tags[TagCap]; + char *nick; + char *user; + char *host; + char *cmd; + char *params[ParamCap]; }; -enum { - IRCBold = 002, - IRCColor = 003, - IRCReverse = 026, - IRCReset = 017, - IRCItalic = 035, - IRCUnderline = 037, + +void ircConfig( + bool insecure, const char *trust, const char *cert, const char *priv +); +int ircConnect(const char *bind, const char *host, const char *port); +void ircHandshake(void); +void ircPrintCert(void); +void ircRecv(void); +void ircSend(const char *ptr, size_t len); +void ircFormat(const char *format, ...) + __attribute__((format(printf, 1, 2))); +void ircClose(void); + +extern uint execID; +extern int execPipe[2]; +extern int utilPipe[2]; + +enum { UtilCap = 16 }; +struct Util { + uint argc; + const char *argv[UtilCap]; }; -struct Format { - const wchar_t *str; - size_t len; - bool split; - bool bold, italic, underline, reverse; - enum IRCColor fg, bg; +static inline void utilPush(struct Util *util, const char *arg) { + if (1 + util->argc < UtilCap) { + util->argv[util->argc++] = arg; + } else { + errx(EX_CONFIG, "too many utility arguments"); + } +} + +enum Reply { + ReplyAway = 1, + ReplyBan, + ReplyExcepts, + ReplyHelp, + ReplyInvex, + ReplyJoin, + ReplyList, + ReplyMode, + ReplyNames, + ReplyNamesAuto, + ReplyTopic, + ReplyTopicAuto, + ReplyWhois, + ReplyWhowas, + ReplyCap, }; -void formatReset(struct Format *format); -bool formatParse(struct Format *format, const wchar_t *split); -enum IRCColor colorGen(const char *str); -struct Tag colorTag(struct Tag tag, const char *gen); -enum IRCColor colorFor(struct Tag tag); +extern uint replies[ReplyCap]; -void handle(char *line); -void input(struct Tag tag, char *line); -void inputTab(void); +void handle(struct Message *msg); +void command(uint id, char *input); +const char *commandIsPrivmsg(uint id, const char *input); +const char *commandIsNotice(uint id, const char *input); +const char *commandIsAction(uint id, const char *input); +size_t commandWillSplit(uint id, const char *input); +void commandCompletion(void); -int ircConnect(void); -void ircRead(void); -void ircWrite(const char *ptr, size_t len); -void ircFmt(const char *format, ...) __attribute__((format(printf, 1, 2))); -void ircQuit(const char *mesg); +enum Heat { + Ice, + Cold, + Warm, + Hot, +}; +enum { + TitleCap = 256, + StatusLines = 1, + MarkerLines = 1, + SplitLines = 5, + InputLines = 1, + InputCols = 1024, +}; +extern char uiTitle[TitleCap]; +extern struct _win_st *uiStatus; +extern struct _win_st *uiMain; +extern struct _win_st *uiInput; +extern bool uiSpoilerReveal; +extern struct Util uiNotifyUtil; void uiInit(void); +uint uiAttr(struct Style style); +short uiPair(struct Style style); void uiShow(void); void uiHide(void); void uiDraw(void); -void uiRead(void); -void uiExit(int status); - -void uiPrompt(bool nickChanged); -void uiShowTag(struct Tag tag); -void uiShowNum(int num, bool relative); -void uiMoveTag(struct Tag tag, int num, bool relative); -void uiCloseTag(struct Tag tag); - -enum UIHeat { - UICold, - UIWarm, - UIHot, -}; -void uiLog(struct Tag tag, enum UIHeat heat, const wchar_t *str); -void uiFmt(struct Tag tag, enum UIHeat heat, const wchar_t *format, ...); +void uiResize(void); +void uiWrite(uint id, enum Heat heat, const time_t *time, const char *str); +void uiFormat( + uint id, enum Heat heat, const time_t *time, const char *format, ... +) __attribute__((format(printf, 4, 5))); +void uiLoad(const char *name); +int uiSave(void); -enum TermMode { - TermFocus, - TermPaste, +void inputInit(void); +void inputWait(void); +void inputUpdate(void); +bool inputPending(uint id); +void inputRead(void); +void inputCompletion(void); +int inputSave(FILE *file); +void inputLoad(FILE *file, size_t version); + +enum Scroll { + ScrollOne, + ScrollPage, + ScrollAll, + ScrollUnread, + ScrollHot, }; -enum TermEvent { - TermNone, - TermFocusIn, - TermFocusOut, - TermPasteStart, - TermPasteEnd, +extern struct Time { + bool enable; + const char *format; + int width; +} windowTime; +extern enum Heat windowThreshold; +void windowInit(void); +void windowUpdate(void); +void windowResize(void); +bool windowWrite(uint id, enum Heat heat, const time_t *time, const char *str); +void windowBare(void); +uint windowID(void); +uint windowNum(void); +uint windowFor(uint id); +void windowShow(uint num); +void windowAuto(void); +void windowSwap(void); +void windowMove(uint from, uint to); +void windowClose(uint num); +void windowList(void); +void windowMark(void); +void windowUnmark(void); +void windowToggleMute(void); +void windowToggleTime(void); +void windowToggleThresh(int n); +bool windowTimeEnable(void); +void windowScroll(enum Scroll by, int n); +void windowSearch(const char *str, int dir); +int windowSave(FILE *file); +void windowLoad(FILE *file, size_t version); + +enum { BufferCap = 1024 }; +struct Buffer; +struct Line { + uint num; + enum Heat heat; + time_t time; + char *str; }; -void termInit(void); -void termNoFlow(void); -void termTitle(const char *title); -void termMode(enum TermMode mode, bool set); -enum TermEvent termEvent(char ch); - -enum Edit { - EditLeft, - EditRight, - EditHome, - EditEnd, - EditBackWord, - EditForeWord, - EditInsert, - EditBackspace, - EditDelete, - EditKill, - EditKillBackWord, - EditKillForeWord, - EditKillEnd, - EditComplete, - EditEnter, +struct Buffer *bufferAlloc(void); +void bufferFree(struct Buffer *buffer); +const struct Line *bufferSoft(const struct Buffer *buffer, size_t i); +const struct Line *bufferHard(const struct Buffer *buffer, size_t i); +int bufferPush( + struct Buffer *buffer, int cols, enum Heat thresh, + enum Heat heat, time_t time, const char *str +); +int bufferReflow( + struct Buffer *buffer, int cols, enum Heat thresh, size_t tail +); + +struct Cursor { + uint gen; + struct Node *node; }; -void edit(struct Tag tag, enum Edit op, wchar_t ch); -const wchar_t *editHead(void); -const wchar_t *editTail(void); - -void tabTouch(struct Tag tag, const char *word); -void tabAdd(struct Tag tag, const char *word); -void tabRemove(struct Tag tag, const char *word); -void tabReplace(struct Tag tag, const char *prev, const char *next); -void tabClear(struct Tag tag); -struct Tag tabTag(const char *word); -const char *tabNext(struct Tag tag, const char *prefix); -void tabAccept(void); -void tabReject(void); - -void urlScan(struct Tag tag, const char *str); -void urlList(struct Tag tag); -void urlOpenMatch(struct Tag tag, const char *substr); -void urlOpenRange(struct Tag tag, size_t at, size_t to); - -void logOpen(const char *path); -void logFmt( - struct Tag tag, const time_t *ts, const char *format, ... -) __attribute__((format(printf, 3, 4))); -void logList(struct Tag tag); -void logReplay(struct Tag tag); - -wchar_t *wcsnchr(const wchar_t *wcs, size_t len, wchar_t chr); -wchar_t *wcsnrchr(const wchar_t *wcs, size_t len, wchar_t chr); -wchar_t *ambstowcs(const char *src); -char *awcstombs(const wchar_t *src); -char *awcsntombs(const wchar_t *src, size_t nwc); -int vaswprintf(wchar_t **ret, const wchar_t *format, va_list ap); -int aswprintf(wchar_t **ret, const wchar_t *format, ...); - -size_t base64Size(size_t len); -void base64(char *dst, const byte *src, size_t len); - -// HACK: clang won't check wchar_t *format strings. -#ifdef NDEBUG -#define uiFmt(tag, heat, format, ...) uiFmt(tag, heat, L##format, __VA_ARGS__) -#else -#define uiFmt(tag, heat, format, ...) do { \ - snprintf(NULL, 0, format, __VA_ARGS__); \ - uiFmt(tag, heat, L##format, __VA_ARGS__); \ -} while(0) -#endif +void completePush(uint id, const char *str, enum Color color); +void completePull(uint id, const char *str, enum Color color); +void completeReplace(const char *old, const char *new); +void completeRemove(uint id, const char *str); +enum Color completeColor(uint id, const char *str); +uint *completeBits(uint id, const char *str); +const char *completePrefix(struct Cursor *curs, uint id, const char *prefix); +const char *completeSubstr(struct Cursor *curs, uint id, const char *substr); +const char *completeEach(struct Cursor *curs, uint id); +uint completeEachID(struct Cursor *curs, const char *str); +void completeAccept(struct Cursor *curs); +void completeReject(struct Cursor *curs); + +extern struct Util urlOpenUtil; +extern struct Util urlCopyUtil; +void urlScan(uint id, const char *nick, const char *mesg); +void urlOpenCount(uint id, uint count); +void urlOpenMatch(uint id, const char *str); +void urlCopyMatch(uint id, const char *str); +int urlSave(FILE *file); +void urlLoad(FILE *file, size_t version); + +enum { FilterCap = 64 }; +extern struct Filter { + enum Heat heat; + char *mask; + char *cmd; + char *chan; + char *mesg; +} filters[FilterCap]; +struct Filter filterParse(enum Heat heat, char *pattern); +struct Filter filterAdd(enum Heat heat, const char *pattern); +bool filterRemove(struct Filter filter); +enum Heat filterCheck(enum Heat heat, uint id, const struct Message *msg); + +void logOpen(void); +void logFormat(uint id, const time_t *time, const char *format, ...) + __attribute__((format(printf, 3, 4))); +void logClose(void); + +char *configPath(char *buf, size_t cap, const char *path, int i); +char *dataPath(char *buf, size_t cap, const char *path, int i); +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 +); |