From 397b4ce6bd9064aa36f8b9c264e4bbc226822d6f Mon Sep 17 00:00:00 2001 From: June McEnroe Date: Sat, 12 Feb 2022 13:26:38 -0500 Subject: Prompt for empty server or SASL passwords --- handle.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index e460c7c..d663ca1 100644 --- a/handle.c +++ b/handle.c @@ -164,7 +164,9 @@ static void handleCap(struct Message *msg) { } else if (!strcmp(msg->params[1], "ACK")) { self.caps |= caps; if (caps & CapSASL) { - ircFormat("AUTHENTICATE %s\r\n", (self.plain ? "PLAIN" : "EXTERNAL")); + ircFormat( + "AUTHENTICATE %s\r\n", (self.plainUser ? "PLAIN" : "EXTERNAL") + ); } if (!(self.caps & CapSASL)) ircFormat("CAP END\r\n"); } else if (!strcmp(msg->params[1], "NAK")) { @@ -203,18 +205,18 @@ static void base64(char *dst, const byte *src, size_t len) { static void handleAuthenticate(struct Message *msg) { (void)msg; - if (!self.plain) { + if (!self.plainUser) { ircFormat("AUTHENTICATE +\r\n"); return; } byte buf[299] = {0}; - size_t len = 1 + strlen(self.plain); + size_t userLen = strlen(self.plainUser); + size_t passLen = strlen(self.plainPass); + size_t len = 1 + userLen + 1 + passLen; if (sizeof(buf) < len) errx(EX_USAGE, "SASL PLAIN is too long"); - memcpy(&buf[1], self.plain, len - 1); - byte *sep = memchr(buf, ':', len); - if (!sep) errx(EX_USAGE, "SASL PLAIN missing colon"); - *sep = 0; + memcpy(&buf[1], self.plainUser, userLen); + memcpy(&buf[1 + userLen + 1], self.plainPass, passLen); char b64[BASE64_SIZE(sizeof(buf))]; base64(b64, buf, len); @@ -224,7 +226,7 @@ static void handleAuthenticate(struct Message *msg) { explicit_bzero(b64, sizeof(b64)); explicit_bzero(buf, sizeof(buf)); - explicit_bzero(self.plain, strlen(self.plain)); + explicit_bzero(self.plainPass, strlen(self.plainPass)); } static void handleReplyLoggedIn(struct Message *msg) { -- cgit 1.4.1 From 3359a5d69b0fe3c08812f7db83e27958ffec820f Mon Sep 17 00:00:00 2001 From: June McEnroe Date: Sat, 19 Feb 2022 18:28:45 -0500 Subject: Factor out window management to window.c --- Makefile | 1 + README.7 | 4 +- chat.c | 8 +- chat.h | 74 +++++-- command.c | 22 +- handle.c | 2 +- ui.c | 735 ++++++++------------------------------------------------------ window.c | 656 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 828 insertions(+), 674 deletions(-) create mode 100644 window.c (limited to 'handle.c') diff --git a/Makefile b/Makefile index fd06260..dffe4e8 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ OBJS += irc.o OBJS += log.o OBJS += ui.o OBJS += url.o +OBJS += window.o OBJS += xdg.o TESTS += edit.t diff --git a/README.7 b/README.7 index 8f13f7c..b98b589 100644 --- a/README.7 +++ b/README.7 @@ -1,5 +1,5 @@ .\" To view this file, run: man ./README.7 -.Dd February 12, 2022 +.Dd February 19, 2022 .Dt README 7 .Os "Causal Agency" . @@ -181,6 +181,8 @@ startup and event loop IRC connection and parsing .It Pa ui.c curses interface +.It Pa window.c +window management .It Pa handle.c IRC message handling .It Pa command.c diff --git a/chat.c b/chat.c index 454ae31..57cae6d 100644 --- a/chat.c +++ b/chat.c @@ -293,8 +293,8 @@ int main(int argc, char *argv[]) { break; case 'R': self.restricted = true; break; case 'S': bind = optarg; break; case 'T': { - uiTime.enable = true; - if (optarg) uiTime.format = optarg; + windowTime.enable = true; + if (optarg) windowTime.format = optarg; } break; case 'a': sasl = true; parsePlain(optarg); break; case 'c': cert = optarg; @@ -309,7 +309,7 @@ int main(int argc, char *argv[]) { break; case 'n': nick = optarg; break; case 'o': printCert = true; break; case 'p': port = optarg; - break; case 'q': uiThreshold = Warm; + break; case 'q': windowThreshold = Warm; break; case 'r': real = optarg; break; case 's': save = optarg; break; case 't': trust = optarg; @@ -381,7 +381,7 @@ int main(int argc, char *argv[]) { uiLoad(save); atexit(exitSave); } - uiShowID(Network); + windowShow(windowFor(Network)); uiFormat( Network, Cold, NULL, "\3%dcatgirl\3\tis GPLv3 fwee softwawe ^w^ " diff --git a/chat.h b/chat.h index 8a9a48f..e9a2926 100644 --- a/chat.h +++ b/chat.h @@ -293,26 +293,34 @@ const char *commandIsAction(uint id, const char *input); size_t commandWillSplit(uint id, const char *input); void commandCompleteAdd(void); -enum Heat { Ice, Cold, Warm, Hot }; -enum { TimeCap = 64 }; -extern enum Heat uiThreshold; -extern struct Time { - bool enable; - const char *format; - int width; -} uiTime; +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 Util uiNotifyUtil; void uiInitEarly(void); void uiInitLate(void); +uint uiAttr(struct Style style); +short uiPair(struct Style style); +void uiUpdate(void); void uiShow(void); void uiHide(void); +void uiWait(void); void uiDraw(void); -void uiWindows(void); -void uiShowID(uint id); -void uiShowNum(uint num); -void uiMoveID(uint id, uint num); -void uiCloseID(uint id); -void uiCloseNum(uint id); void uiRead(void); void uiWrite(uint id, enum Heat heat, const time_t *time, const char *str); void uiFormat( @@ -321,6 +329,44 @@ void uiFormat( void uiLoad(const char *name); int uiSave(void); +enum Scroll { + ScrollOne, + ScrollPage, + ScrollAll, + ScrollUnread, + ScrollHot, +}; +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 { diff --git a/command.c b/command.c index 335c396..a127af3 100644 --- a/command.c +++ b/command.c @@ -144,7 +144,7 @@ static void commandMsg(uint id, char *params) { if (params) { splitMessage("PRIVMSG", msg, params); } else { - uiShowID(msg); + windowShow(windowFor(msg)); } } @@ -376,25 +376,25 @@ static void commandQuery(uint id, char *params) { if (idColors[query] == Default) { idColors[query] = completeColor(id, params); } - uiShowID(query); + windowShow(windowFor(query)); } static void commandWindow(uint id, char *params) { if (!params) { - uiWindows(); + windowList(); } else if (isdigit(params[0])) { - uiShowNum(strtoul(params, NULL, 10)); + windowShow(strtoul(params, NULL, 10)); } else { id = idFind(params); if (id) { - uiShowID(id); + windowShow(windowFor(id)); return; } for (const char *match; (match = completeSubstr(None, params));) { id = idFind(match); if (!id) continue; completeAccept(); - uiShowID(id); + windowShow(windowFor(id)); break; } } @@ -405,20 +405,20 @@ static void commandMove(uint id, char *params) { char *name = strsep(¶ms, " "); if (params) { id = idFind(name); - if (id) uiMoveID(id, strtoul(params, NULL, 10)); + if (id) windowMove(windowFor(id), strtoul(params, NULL, 10)); } else { - uiMoveID(id, strtoul(name, NULL, 10)); + windowMove(windowFor(id), strtoul(name, NULL, 10)); } } static void commandClose(uint id, char *params) { if (!params) { - uiCloseID(id); + windowClose(windowFor(id)); } else if (isdigit(params[0])) { - uiCloseNum(strtoul(params, NULL, 10)); + windowClose(strtoul(params, NULL, 10)); } else { id = idFind(params); - if (id) uiCloseID(id); + if (id) windowClose(windowFor(id)); } } diff --git a/handle.c b/handle.c index d663ca1..f4cf68e 100644 --- a/handle.c +++ b/handle.c @@ -343,7 +343,7 @@ static void handleJoin(struct Message *msg) { idColors[id] = hash(msg->params[0]); completeTouch(None, msg->params[0], idColors[id]); if (replies[ReplyJoin]) { - uiShowID(id); + windowShow(windowFor(id)); replies[ReplyJoin]--; } } diff --git a/ui.c b/ui.c index 212d205..85b5a72 100644 --- a/ui.c +++ b/ui.c @@ -33,15 +33,15 @@ #include #include #include -#include +#include #include #include #include #include #include #include -#include #include +#include #include #include #include @@ -60,102 +60,14 @@ #undef lines #undef tab -enum { - StatusLines = 1, - MarkerLines = 1, - SplitLines = 5, - InputLines = 1, - InputCols = 1024, -}; - #define BOTTOM (LINES - 1) #define RIGHT (COLS - 1) #define MAIN_LINES (LINES - StatusLines - InputLines) -static WINDOW *status; -static WINDOW *main; +WINDOW *uiStatus; +WINDOW *uiMain; static WINDOW *input; -struct Window { - uint id; - int scroll; - bool mark; - bool mute; - bool time; - enum Heat thresh; - enum Heat heat; - uint unreadSoft; - uint unreadHard; - uint unreadWarm; - struct Buffer *buffer; -}; - -static struct { - struct Window *ptrs[IDCap]; - uint len; - uint show; - uint swap; - uint user; -} windows; - -static uint windowPush(struct Window *window) { - assert(windows.len < IDCap); - windows.ptrs[windows.len] = window; - return windows.len++; -} - -static uint windowInsert(uint num, struct Window *window) { - assert(windows.len < IDCap); - assert(num <= windows.len); - memmove( - &windows.ptrs[num + 1], - &windows.ptrs[num], - sizeof(*windows.ptrs) * (windows.len - num) - ); - windows.ptrs[num] = window; - windows.len++; - return num; -} - -static struct Window *windowRemove(uint num) { - assert(num < windows.len); - struct Window *window = windows.ptrs[num]; - windows.len--; - memmove( - &windows.ptrs[num], - &windows.ptrs[num + 1], - sizeof(*windows.ptrs) * (windows.len - num) - ); - return window; -} - -enum Heat uiThreshold = Cold; - -static uint windowFor(uint id) { - for (uint num = 0; num < windows.len; ++num) { - if (windows.ptrs[num]->id == id) return num; - } - struct Window *window = calloc(1, sizeof(*window)); - if (!window) err(EX_OSERR, "malloc"); - window->id = id; - window->mark = true; - window->time = uiTime.enable; - if (id == Network || id == Debug) { - window->thresh = Cold; - } else { - window->thresh = uiThreshold; - } - window->buffer = bufferAlloc(); - completeAdd(None, idNames[id], idColors[id]); - return windowPush(window); -} - -static void windowFree(struct Window *window) { - completeRemove(None, idNames[window->id]); - bufferFree(window->buffer); - free(window); -} - static short colorPairs; static void colorInit(void) { @@ -240,8 +152,6 @@ enum { static const char *FocusMode[2] = { "\33[?1004l", "\33[?1004h" }; static const char *PasteMode[2] = { "\33[?2004l", "\33[?2004h" }; -struct Time uiTime = { .format = "%X" }; - static void errExit(void) { putp(FocusMode[false]); putp(PasteMode[false]); @@ -271,30 +181,18 @@ void uiInitEarly(void) { ENUM_KEY #undef X - status = newwin(StatusLines, COLS, 0, 0); - if (!status) err(EX_OSERR, "newwin"); - - main = newwin(MAIN_LINES, COLS, StatusLines, 0); - if (!main) err(EX_OSERR, "newwin"); - - int y; - char fmt[TimeCap]; - char buf[TimeCap]; - styleStrip(fmt, sizeof(fmt), uiTime.format); - struct tm *time = localtime(&(time_t) { -22100400 }); - size_t len = strftime(buf, sizeof(buf), fmt, time); - if (!len) errx(EX_CONFIG, "invalid timestamp format: %s", fmt); - waddstr(main, buf); - waddch(main, ' '); - getyx(main, y, uiTime.width); - (void)y; + uiStatus = newwin(StatusLines, COLS, 0, 0); + if (!uiStatus) err(EX_OSERR, "newwin"); + + uiMain = newwin(MAIN_LINES, COLS, StatusLines, 0); + if (!uiMain) err(EX_OSERR, "newwin"); input = newpad(InputLines, InputCols); if (!input) err(EX_OSERR, "newpad"); keypad(input, true); nodelay(input, true); - windowFor(Network); + windowInit(); uiShow(); } @@ -323,13 +221,13 @@ void uiInitLate(void) { static bool hidden = true; static bool waiting; -static char title[256]; -static char prevTitle[sizeof(title)]; +char uiTitle[TitleCap]; +static char prevTitle[TitleCap]; void uiDraw(void) { if (hidden) return; - wnoutrefresh(status); - wnoutrefresh(main); + wnoutrefresh(uiStatus); + wnoutrefresh(uiMain); int y, x; getyx(input, y, x); pnoutrefresh( @@ -342,10 +240,10 @@ void uiDraw(void) { doupdate(); if (!to_status_line) return; - if (!strcmp(title, prevTitle)) return; - strcpy(prevTitle, title); + if (!strcmp(uiTitle, prevTitle)) return; + strcpy(prevTitle, uiTitle); putp(to_status_line); - putp(title); + putp(uiTitle); putp(from_status_line); fflush(stdout); } @@ -377,7 +275,7 @@ static const short Colors[ColorCap] = { 16, 233, 235, 237, 239, 241, 244, 247, 250, 254, 231, }; -static attr_t styleAttr(struct Style style) { +uint uiAttr(struct Style style) { attr_t attr = A_NORMAL; if (style.attr & Bold) attr |= A_BOLD; if (style.attr & Reverse) attr |= A_REVERSE; @@ -388,96 +286,13 @@ static attr_t styleAttr(struct Style style) { static bool spoilerReveal; -static short stylePair(struct Style style) { +short uiPair(struct Style style) { if (spoilerReveal && style.fg == style.bg) { return colorPair(Colors[Default], Colors[style.bg]); } return colorPair(Colors[style.fg], Colors[style.bg]); } -static int styleAdd(WINDOW *win, struct Style init, const char *str) { - struct Style style = init; - while (*str) { - size_t len = styleParse(&style, &str); - wattr_set(win, styleAttr(style), stylePair(style), NULL); - if (waddnstr(win, str, len) == ERR) - return -1; - str += len; - } - return 0; -} - -static void statusUpdate(void) { - struct { - uint unread; - enum Heat heat; - } others = { 0, Cold }; - - wmove(status, 0, 0); - for (uint num = 0; num < windows.len; ++num) { - const struct Window *window = windows.ptrs[num]; - if (num != windows.show && !window->scroll) { - if (window->heat < Warm) continue; - if (window->mute && window->heat < Hot) continue; - } - if (num != windows.show) { - others.unread += window->unreadWarm; - if (window->heat > others.heat) others.heat = window->heat; - } - char buf[256], *end = &buf[sizeof(buf)]; - char *ptr = seprintf( - buf, end, "\3%d%s %u%s%s %s ", - idColors[window->id], (num == windows.show ? "\26" : ""), - num, window->thresh[(const char *[]) { "-", "", "+", "++" }], - &"="[!window->mute], idNames[window->id] - ); - if (window->mark && window->unreadWarm) { - ptr = seprintf( - ptr, end, "\3%d+%d\3%d%s", - (window->heat > Warm ? White : idColors[window->id]), - window->unreadWarm, idColors[window->id], - (window->scroll ? "" : " ") - ); - } - if (window->scroll) { - ptr = seprintf(ptr, end, "~%d ", window->scroll); - } - if (styleAdd(status, StyleDefault, buf) < 0) break; - } - wclrtoeol(status); - - const struct Window *window = windows.ptrs[windows.show]; - char *end = &title[sizeof(title)]; - char *ptr = seprintf( - title, end, "%s %s", network.name, idNames[window->id] - ); - if (window->mark && window->unreadWarm) { - ptr = seprintf( - ptr, end, " +%d%s", window->unreadWarm, &"!"[window->heat < Hot] - ); - } - if (others.unread) { - ptr = seprintf( - ptr, end, " (+%d%s)", others.unread, &"!"[others.heat < Hot] - ); - } -} - -static void mark(struct Window *window) { - if (window->scroll) return; - window->mark = true; - window->unreadSoft = 0; - window->unreadWarm = 0; -} - -static void unmark(struct Window *window) { - if (!window->scroll) { - window->mark = false; - window->heat = Cold; - } - statusUpdate(); -} - void uiShow(void) { if (!hidden) return; prevTitle[0] = '\0'; @@ -485,86 +300,20 @@ void uiShow(void) { putp(PasteMode[true]); fflush(stdout); hidden = false; - unmark(windows.ptrs[windows.show]); + windowUnmark(); } void uiHide(void) { if (hidden) return; - mark(windows.ptrs[windows.show]); + windowMark(); hidden = true; putp(FocusMode[false]); putp(PasteMode[false]); endwin(); } -static size_t windowTop(const struct Window *window) { - size_t top = BufferCap - MAIN_LINES - window->scroll; - if (window->scroll) top += MarkerLines; - return top; -} - -static size_t windowBottom(const struct Window *window) { - size_t bottom = BufferCap - (window->scroll ?: 1); - if (window->scroll) bottom -= SplitLines + MarkerLines; - return bottom; -} - -static int windowCols(const struct Window *window) { - return COLS - (window->time ? uiTime.width : 0); -} - -static void mainAdd(int y, bool time, const struct Line *line) { - int ny, nx; - wmove(main, y, 0); - if (!line || !line->str[0]) { - wclrtoeol(main); - return; - } - if (time && line->time) { - char buf[TimeCap]; - strftime(buf, sizeof(buf), uiTime.format, localtime(&line->time)); - struct Style init = { .fg = Gray, .bg = Default }; - styleAdd(main, init, buf); - waddch(main, ' '); - } else if (time) { - whline(main, ' ', uiTime.width); - wmove(main, y, uiTime.width); - } - styleAdd(main, StyleDefault, line->str); - getyx(main, ny, nx); - if (ny != y) return; - wclrtoeol(main); - (void)nx; -} - -static void mainUpdate(void) { - struct Window *window = windows.ptrs[windows.show]; - - int y = 0; - int marker = MAIN_LINES - SplitLines - MarkerLines; - for (size_t i = windowTop(window); i < BufferCap; ++i) { - mainAdd(y++, window->time, bufferHard(window->buffer, i)); - if (window->scroll && y == marker) break; - } - if (!window->scroll) return; - - y = MAIN_LINES - SplitLines; - for (size_t i = BufferCap - SplitLines; i < BufferCap; ++i) { - mainAdd(y++, window->time, bufferHard(window->buffer, i)); - } - wattr_set(main, A_NORMAL, 0, NULL); - mvwhline(main, marker, 0, ACS_BULLET, COLS); -} - -static void windowScroll(struct Window *window, int n) { - mark(window); - window->scroll += n; - if (window->scroll > BufferCap - MAIN_LINES) { - window->scroll = BufferCap - MAIN_LINES; - } - if (window->scroll < 0) window->scroll = 0; - unmark(window); - if (window == windows.ptrs[windows.show]) mainUpdate(); +void uiWait(void) { + waiting = true; } struct Util uiNotifyUtil; @@ -593,36 +342,8 @@ static void notify(uint id, const char *str) { } void uiWrite(uint id, enum Heat heat, const time_t *src, const char *str) { - struct Window *window = windows.ptrs[windowFor(id)]; - time_t ts = (src ? *src : time(NULL)); - - if (heat >= window->thresh) { - if (!window->unreadSoft++) window->unreadHard = 0; - } - if (window->mark && heat > Cold) { - if (!window->unreadWarm++) { - int lines = bufferPush( - window->buffer, windowCols(window), - window->thresh, Warm, ts, "" - ); - if (window->scroll) windowScroll(window, lines); - if (window->unreadSoft > 1) { - window->unreadSoft++; - window->unreadHard += lines; - } - } - if (heat > window->heat) window->heat = heat; - statusUpdate(); - } - int lines = bufferPush( - window->buffer, windowCols(window), - window->thresh, heat, ts, str - ); - window->unreadHard += lines; - if (window->scroll) windowScroll(window, lines); - if (window == windows.ptrs[windows.show]) mainUpdate(); - - if (window->mark && heat > Warm) { + bool note = windowWrite(id, heat, src, str); + if (note) { beep(); notify(id, str); } @@ -640,78 +361,10 @@ void uiFormat( uiWrite(id, heat, time, buf); } -static void scrollTo(struct Window *window, int top) { - window->scroll = 0; - windowScroll(window, top - MAIN_LINES + MarkerLines); -} - -static void windowReflow(struct Window *window) { - uint num = 0; - const struct Line *line = bufferHard(window->buffer, windowTop(window)); - if (line) num = line->num; - window->unreadHard = bufferReflow( - window->buffer, windowCols(window), - window->thresh, window->unreadSoft - ); - if (!window->scroll || !num) return; - for (size_t i = 0; i < BufferCap; ++i) { - line = bufferHard(window->buffer, i); - if (!line || line->num != num) continue; - scrollTo(window, BufferCap - i); - break; - } -} - static void resize(void) { - wclear(main); - wresize(main, MAIN_LINES, COLS); - for (uint num = 0; num < windows.len; ++num) { - windowReflow(windows.ptrs[num]); - } - statusUpdate(); - mainUpdate(); -} - -static void windowList(const struct Window *window) { - uiHide(); - waiting = true; - - uint num = 0; - const struct Line *line = bufferHard(window->buffer, windowBottom(window)); - if (line) num = line->num; - for (size_t i = 0; i < BufferCap; ++i) { - line = bufferSoft(window->buffer, i); - if (!line) continue; - if (line->num > num) break; - if (!line->str[0]) { - printf("\n"); - continue; - } - - char buf[TimeCap]; - strftime(buf, sizeof(buf), uiTime.format, localtime(&line->time)); - vid_attr(colorAttr(Colors[Gray]), colorPair(Colors[Gray], -1), NULL); - printf("%s ", buf); - - bool align = false; - struct Style style = StyleDefault; - for (const char *str = line->str; *str;) { - if (*str == '\t') { - printf("%c", (align ? '\t' : ' ')); - align = true; - str++; - } - - size_t len = styleParse(&style, &str); - size_t tab = strcspn(str, "\t"); - if (tab < len) len = tab; - - vid_attr(styleAttr(style), stylePair(style), NULL); - printf("%.*s", (int)len, str); - str += len; - } - printf("\n"); - } + wclear(uiMain); + wresize(uiMain, MAIN_LINES, COLS); + windowResize(); } static void inputAdd(struct Style reset, struct Style *style, const char *str) { @@ -736,7 +389,7 @@ static void inputAdd(struct Style reset, struct Style *style, const char *str) { } size_t nl = strcspn(str, "\n"); if (nl < len) len = nl; - wattr_set(input, styleAttr(*style), stylePair(*style), NULL); + wattr_set(input, uiAttr(*style), uiPair(*style), NULL); waddnstr(input, str, len); str += len; } @@ -755,9 +408,9 @@ static char *inputStop( static struct Edit edit; -static void inputUpdate(void) { +void uiUpdate(void) { char *buf = editString(&edit); - struct Window *window = windows.ptrs[windows.show]; + uint id = windowID(); const char *prefix = ""; const char *prompt = self.nick; @@ -766,10 +419,10 @@ static void inputUpdate(void) { struct Style stylePrompt = { .fg = self.color, .bg = Default }; struct Style styleInput = StyleDefault; - size_t split = commandWillSplit(window->id, buf); - const char *privmsg = commandIsPrivmsg(window->id, buf); - const char *notice = commandIsNotice(window->id, buf); - const char *action = commandIsAction(window->id, buf); + size_t split = commandWillSplit(id, buf); + const char *privmsg = commandIsPrivmsg(id, buf); + const char *notice = commandIsNotice(id, buf); + const char *action = commandIsAction(id, buf); if (privmsg) { prefix = "<"; suffix = "> "; skip = privmsg; @@ -782,7 +435,7 @@ static void inputUpdate(void) { stylePrompt.attr |= Italic; styleInput.attr |= Italic; skip = action; - } else if (window->id == Debug && buf[0] != '/') { + } else if (id == Debug && buf[0] != '/') { prompt = "<< "; stylePrompt.fg = Gray; } else { @@ -795,11 +448,11 @@ static void inputUpdate(void) { int y, x; wmove(input, 0, 0); - if (window->time && window->id != Network) { - whline(input, ' ', uiTime.width); - wmove(input, 0, uiTime.width); + if (windowTimeEnable() && id != Network) { + whline(input, ' ', windowTime.width); + wmove(input, 0, windowTime.width); } - wattr_set(input, styleAttr(stylePrompt), stylePair(stylePrompt), NULL); + wattr_set(input, uiAttr(stylePrompt), uiPair(stylePrompt), NULL); waddstr(input, prefix); waddstr(input, prompt); waddstr(input, suffix); @@ -823,209 +476,61 @@ static void inputUpdate(void) { wmove(input, y, pos); } -void uiWindows(void) { - for (uint num = 0; num < windows.len; ++num) { - const struct Window *window = windows.ptrs[num]; - uiFormat( - Network, Warm, NULL, "\3%02d%u %s", - idColors[window->id], num, idNames[window->id] - ); - } -} - -static void windowShow(uint num) { - if (num != windows.show) { - windows.swap = windows.show; - mark(windows.ptrs[windows.swap]); - } - windows.show = num; - windows.user = num; - unmark(windows.ptrs[windows.show]); - mainUpdate(); - inputUpdate(); -} - -void uiShowID(uint id) { - windowShow(windowFor(id)); -} - -void uiShowNum(uint num) { - if (num < windows.len) windowShow(num); -} - -void uiMoveID(uint id, uint num) { - struct Window *window = windowRemove(windowFor(id)); - if (num < windows.len) { - windowShow(windowInsert(num, window)); - } else { - windowShow(windowPush(window)); - } -} - -static void windowClose(uint num) { - if (windows.ptrs[num]->id == Network) return; - struct Window *window = windowRemove(num); - completeClear(window->id); - windowFree(window); - if (windows.swap >= num) windows.swap--; - if (windows.show == num) { - windowShow(windows.swap); - windows.swap = windows.show; - } else if (windows.show > num) { - windows.show--; - mainUpdate(); - } - statusUpdate(); -} - -void uiCloseID(uint id) { - windowClose(windowFor(id)); -} - -void uiCloseNum(uint num) { - if (num < windows.len) windowClose(num); -} - -static void scrollPage(struct Window *window, int n) { - windowScroll(window, n * (MAIN_LINES - SplitLines - MarkerLines - 1)); -} - -static void scrollTop(struct Window *window) { - for (size_t i = 0; i < BufferCap; ++i) { - if (!bufferHard(window->buffer, i)) continue; - scrollTo(window, BufferCap - i); - break; - } -} - -static void scrollHot(struct Window *window, int dir) { - for (size_t i = windowTop(window) + dir; i < BufferCap; i += dir) { - const struct Line *line = bufferHard(window->buffer, i); - const struct Line *prev = bufferHard(window->buffer, i - 1); - if (!line || line->heat < Hot) continue; - if (prev && prev->heat > Warm) continue; - scrollTo(window, BufferCap - i); - break; - } -} - -static void scrollSearch(struct Window *window, const char *str, int dir) { - for (size_t i = windowTop(window) + dir; i < BufferCap; i += dir) { - const struct Line *line = bufferHard(window->buffer, i); - if (!line || !strcasestr(line->str, str)) continue; - scrollTo(window, BufferCap - i); - break; - } -} - -static void toggleTime(struct Window *window) { - window->time ^= true; - windowReflow(window); - statusUpdate(); - mainUpdate(); - inputUpdate(); -} - -static void incThresh(struct Window *window, int n) { - if (n > 0 && window->thresh == Hot) return; - if (n < 0 && window->thresh == Ice) { - window->thresh = Cold; - } else { - window->thresh += n; - } - windowReflow(window); - statusUpdate(); - mainUpdate(); - statusUpdate(); -} - -static void showAuto(void) { - uint minHot = UINT_MAX, numHot = 0; - uint minWarm = UINT_MAX, numWarm = 0; - for (uint num = 0; num < windows.len; ++num) { - struct Window *window = windows.ptrs[num]; - if (window->heat >= Hot) { - if (window->unreadWarm >= minHot) continue; - minHot = window->unreadWarm; - numHot = num; - } - if (window->heat >= Warm && !window->mute) { - if (window->unreadWarm >= minWarm) continue; - minWarm = window->unreadWarm; - numWarm = num; - } - } - uint user = windows.user; - if (minHot < UINT_MAX) { - windowShow(numHot); - windows.user = user; - } else if (minWarm < UINT_MAX) { - windowShow(numWarm); - windows.user = user; - } else if (user != windows.show) { - windowShow(user); - } -} - -static void inputEnter(uint id) { - command(id, editString(&edit)); +static void inputEnter(void) { + command(windowID(), editString(&edit)); editFn(&edit, EditClear); } static void keyCode(int code) { - struct Window *window = windows.ptrs[windows.show]; - uint id = window->id; switch (code) { break; case KEY_RESIZE: resize(); - break; case KeyFocusIn: unmark(window); - break; case KeyFocusOut: mark(window); + break; case KeyFocusIn: windowUnmark(); + break; case KeyFocusOut: windowMark(); break; case KeyMetaEnter: editInsert(&edit, L'\n'); - break; case KeyMetaEqual: window->mute ^= true; statusUpdate(); - break; case KeyMetaMinus: incThresh(window, -1); - break; case KeyMetaPlus: incThresh(window, +1); - break; case KeyMetaSlash: windowShow(windows.swap); + break; case KeyMetaEqual: windowToggleMute(); + break; case KeyMetaMinus: windowToggleThresh(-1); + break; case KeyMetaPlus: windowToggleThresh(+1); + break; case KeyMetaSlash: windowSwap(); - break; case KeyMetaGt: scrollTo(window, 0); - break; case KeyMetaLt: scrollTop(window); + break; case KeyMetaGt: windowScroll(ScrollAll, -1); + break; case KeyMetaLt: windowScroll(ScrollAll, +1); - break; case KeyMeta0 ... KeyMeta9: uiShowNum(code - KeyMeta0); - break; case KeyMetaA: showAuto(); + break; case KeyMeta0 ... KeyMeta9: windowShow(code - KeyMeta0); + break; case KeyMetaA: windowAuto(); break; case KeyMetaB: editFn(&edit, EditPrevWord); break; case KeyMetaD: editFn(&edit, EditDeleteNextWord); break; case KeyMetaF: editFn(&edit, EditNextWord); - break; case KeyMetaL: windowList(window); - break; case KeyMetaM: uiWrite(id, Warm, NULL, ""); - break; case KeyMetaN: scrollHot(window, +1); - break; case KeyMetaP: scrollHot(window, -1); + break; case KeyMetaL: windowBare(); + break; case KeyMetaM: uiWrite(windowID(), Warm, NULL, ""); + break; case KeyMetaN: windowScroll(ScrollHot, +1); + break; case KeyMetaP: windowScroll(ScrollHot, -1); break; case KeyMetaQ: editFn(&edit, EditCollapse); - break; case KeyMetaS: spoilerReveal ^= true; mainUpdate(); - break; case KeyMetaT: toggleTime(window); - break; case KeyMetaU: scrollTo(window, window->unreadHard); - break; case KeyMetaV: scrollPage(window, +1); + break; case KeyMetaS: spoilerReveal ^= true; windowUpdate(); + break; case KeyMetaT: windowToggleTime(); + break; case KeyMetaU: windowScroll(ScrollUnread, 0); + break; case KeyMetaV: windowScroll(ScrollPage, +1); break; case KeyCtrlLeft: editFn(&edit, EditPrevWord); break; case KeyCtrlRight: editFn(&edit, EditNextWord); break; case KEY_BACKSPACE: editFn(&edit, EditDeletePrev); break; case KEY_DC: editFn(&edit, EditDeleteNext); - break; case KEY_DOWN: windowScroll(window, -1); + break; case KEY_DOWN: windowScroll(ScrollOne, -1); break; case KEY_END: editFn(&edit, EditTail); - break; case KEY_ENTER: inputEnter(id); + break; case KEY_ENTER: inputEnter(); break; case KEY_HOME: editFn(&edit, EditHead); break; case KEY_LEFT: editFn(&edit, EditPrev); - break; case KEY_NPAGE: scrollPage(window, -1); - break; case KEY_PPAGE: scrollPage(window, +1); + break; case KEY_NPAGE: windowScroll(ScrollPage, -1); + break; case KEY_PPAGE: windowScroll(ScrollPage, +1); break; case KEY_RIGHT: editFn(&edit, EditNext); - break; case KEY_SEND: scrollTo(window, 0); - break; case KEY_SHOME: scrollTo(window, BufferCap); - break; case KEY_UP: windowScroll(window, +1); + break; case KEY_SEND: windowScroll(ScrollAll, -1); + break; case KEY_SHOME: windowScroll(ScrollAll, +1); + break; case KEY_UP: windowScroll(ScrollOne, +1); } } static void keyCtrl(wchar_t ch) { - struct Window *window = windows.ptrs[windows.show]; - uint id = window->id; switch (ch ^ L'@') { break; case L'?': editFn(&edit, EditDeletePrev); break; case L'A': editFn(&edit, EditHead); @@ -1035,16 +540,16 @@ static void keyCtrl(wchar_t ch) { break; case L'E': editFn(&edit, EditTail); break; case L'F': editFn(&edit, EditNext); break; case L'H': editFn(&edit, EditDeletePrev); - break; case L'J': inputEnter(id); + break; case L'J': inputEnter(); break; case L'K': editFn(&edit, EditDeleteTail); break; case L'L': clearok(curscr, true); - break; case L'N': uiShowNum(windows.show + 1); - break; case L'P': uiShowNum(windows.show - 1); - break; case L'R': scrollSearch(window, editString(&edit), -1); - break; case L'S': scrollSearch(window, editString(&edit), +1); + break; case L'N': windowShow(windowNum() + 1); + break; case L'P': windowShow(windowNum() - 1); + break; case L'R': windowSearch(editString(&edit), -1); + break; case L'S': windowSearch(editString(&edit), +1); break; case L'T': editFn(&edit, EditTranspose); break; case L'U': editFn(&edit, EditDeleteHead); - break; case L'V': scrollPage(window, -1); + break; case L'V': windowScroll(ScrollPage, -1); break; case L'W': editFn(&edit, EditDeletePrevWord); break; case L'Y': editFn(&edit, EditPaste); } @@ -1127,13 +632,15 @@ void uiRead(void) { literal = false; if (spr) { spoilerReveal = false; - mainUpdate(); + windowUpdate(); } } - inputUpdate(); + uiUpdate(); } -static const time_t Signatures[] = { +static FILE *saveFile; + +static const uint64_t Signatures[] = { 0x6C72696774616301, // no heat, unread, unreadWarm 0x6C72696774616302, // no self.pos 0x6C72696774616303, // no buffer line heat @@ -1144,68 +651,33 @@ static const time_t Signatures[] = { 0x6C72696774616308, }; -static size_t signatureVersion(time_t signature) { +static size_t signatureVersion(uint64_t signature) { for (size_t i = 0; i < ARRAY_LEN(Signatures); ++i) { if (signature == Signatures[i]) return i; } - errx(EX_DATAERR, "unknown file signature %jX", (uintmax_t)signature); + errx(EX_DATAERR, "unknown file signature %" PRIX64, signature); } -static int writeTime(FILE *file, time_t time) { - return (fwrite(&time, sizeof(time), 1, file) ? 0 : -1); +static int writeUint64(FILE *file, uint64_t u) { + return (fwrite(&u, sizeof(u), 1, file) ? 0 : -1); } -static int writeString(FILE *file, const char *str) { - return (fwrite(str, strlen(str) + 1, 1, file) ? 0 : -1); -} - -static FILE *saveFile; int uiSave(void) { - int error = 0 - || ftruncate(fileno(saveFile), 0) - || writeTime(saveFile, Signatures[7]) - || writeTime(saveFile, self.pos); - if (error) return error; - for (uint num = 0; num < windows.len; ++num) { - const struct Window *window = windows.ptrs[num]; - error = 0 - || writeString(saveFile, idNames[window->id]) - || writeTime(saveFile, window->mute) - || writeTime(saveFile, window->time) - || writeTime(saveFile, window->thresh) - || writeTime(saveFile, window->heat) - || writeTime(saveFile, window->unreadSoft) - || writeTime(saveFile, window->unreadWarm); - if (error) return error; - for (size_t i = 0; i < BufferCap; ++i) { - const struct Line *line = bufferSoft(window->buffer, i); - if (!line) continue; - error = 0 - || writeTime(saveFile, line->time) - || writeTime(saveFile, line->heat) - || writeString(saveFile, line->str); - if (error) return error; - } - error = writeTime(saveFile, 0); - if (error) return error; - } return 0 - || writeString(saveFile, "") + || ftruncate(fileno(saveFile), 0) + || writeUint64(saveFile, Signatures[7]) + || writeUint64(saveFile, self.pos) + || windowSave(saveFile) || urlSave(saveFile) || fclose(saveFile); } -static time_t readTime(FILE *file) { - time_t time; - fread(&time, sizeof(time), 1, file); +static uint64_t readUint64(FILE *file) { + uint64_t u; + fread(&u, sizeof(u), 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; + return u; } void uiLoad(const char *name) { @@ -1235,31 +707,8 @@ void uiLoad(const char *name) { size_t version = signatureVersion(signature); if (version > 1) { - self.pos = readTime(saveFile); - } - - char *buf = NULL; - size_t cap = 0; - while (0 < readString(saveFile, &buf, &cap) && buf[0]) { - struct Window *window = windows.ptrs[windowFor(idFor(buf))]; - if (version > 3) window->mute = readTime(saveFile); - if (version > 6) window->time = readTime(saveFile); - if (version > 5) window->thresh = readTime(saveFile); - if (version > 0) { - window->heat = readTime(saveFile); - window->unreadSoft = readTime(saveFile); - window->unreadWarm = readTime(saveFile); - } - for (;;) { - time_t time = readTime(saveFile); - if (!time) break; - enum Heat heat = (version > 2 ? readTime(saveFile) : Cold); - readString(saveFile, &buf, &cap); - bufferPush(window->buffer, COLS, window->thresh, heat, time, buf); - } - windowReflow(window); + self.pos = readUint64(saveFile); } + windowLoad(saveFile, version); urlLoad(saveFile, version); - - free(buf); } diff --git a/window.c b/window.c new file mode 100644 index 0000000..ce3ec4d --- /dev/null +++ b/window.c @@ -0,0 +1,656 @@ +/* Copyright (C) 2020 June 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 . + * + * 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 _XOPEN_SOURCE_EXTENDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "chat.h" + +#define MAIN_LINES (LINES - StatusLines - InputLines) + +static struct Window { + uint id; + int scroll; + bool mark; + bool mute; + bool time; + enum Heat thresh; + enum Heat heat; + uint unreadSoft; + uint unreadHard; + uint unreadWarm; + struct Buffer *buffer; +} *windows[IDCap]; + +static uint count; +static uint show; +static uint swap; +static uint user; + +static uint windowPush(struct Window *window) { + assert(count < IDCap); + windows[count] = window; + return count++; +} + +static uint windowInsert(uint num, struct Window *window) { + assert(count < IDCap); + assert(num <= count); + memmove( + &windows[num + 1], + &windows[num], + sizeof(*windows) * (count - num) + ); + windows[num] = window; + count++; + return num; +} + +static struct Window *windowRemove(uint num) { + assert(num < count); + struct Window *window = windows[num]; + count--; + memmove( + &windows[num], + &windows[num + 1], + sizeof(*windows) * (count - num) + ); + return window; +} + +static void windowFree(struct Window *window) { + completeRemove(None, idNames[window->id]); + bufferFree(window->buffer); + free(window); +} + +enum Heat windowThreshold = Cold; +struct Time windowTime = { .format = "%X" }; + +uint windowFor(uint id) { + for (uint num = 0; num < count; ++num) { + if (windows[num]->id == id) return num; + } + + struct Window *window = calloc(1, sizeof(*window)); + if (!window) err(EX_OSERR, "malloc"); + + window->id = id; + window->mark = true; + window->time = windowTime.enable; + if (id == Network || id == Debug) { + window->thresh = Cold; + } else { + window->thresh = windowThreshold; + } + window->buffer = bufferAlloc(); + completeAdd(None, idNames[id], idColors[id]); + + return windowPush(window); +} + +enum { TimeCap = 64 }; + +void windowInit(void) { + char fmt[TimeCap]; + char buf[TimeCap]; + styleStrip(fmt, sizeof(fmt), windowTime.format); + + struct tm *time = localtime(&(time_t) { -22100400 }); + size_t len = strftime(buf, sizeof(buf), fmt, time); + if (!len) errx(EX_CONFIG, "invalid timestamp format: %s", fmt); + + int y; + waddstr(uiMain, buf); + waddch(uiMain, ' '); + getyx(uiMain, y, windowTime.width); + (void)y; + + windowFor(Network); +} + +static int styleAdd(WINDOW *win, struct Style init, const char *str) { + struct Style style = init; + while (*str) { + size_t len = styleParse(&style, &str); + wattr_set(win, uiAttr(style), uiPair(style), NULL); + if (waddnstr(win, str, len) == ERR) + return -1; + str += len; + } + return 0; +} + +static void statusUpdate(void) { + struct { + uint unread; + enum Heat heat; + } others = { 0, Cold }; + + wmove(uiStatus, 0, 0); + for (uint num = 0; num < count; ++num) { + const struct Window *window = windows[num]; + if (num != show && !window->scroll) { + if (window->heat < Warm) continue; + if (window->mute && window->heat < Hot) continue; + } + if (num != show) { + others.unread += window->unreadWarm; + if (window->heat > others.heat) others.heat = window->heat; + } + char buf[256], *end = &buf[sizeof(buf)]; + char *ptr = seprintf( + buf, end, "\3%d%s %u%s%s %s ", + idColors[window->id], (num == show ? "\26" : ""), + num, window->thresh[(const char *[]) { "-", "", "+", "++" }], + &"="[!window->mute], idNames[window->id] + ); + if (window->mark && window->unreadWarm) { + ptr = seprintf( + ptr, end, "\3%d+%d\3%d%s", + (window->heat > Warm ? White : idColors[window->id]), + window->unreadWarm, idColors[window->id], + (window->scroll ? "" : " ") + ); + } + if (window->scroll) { + ptr = seprintf(ptr, end, "~%d ", window->scroll); + } + if (styleAdd(uiStatus, StyleDefault, buf) < 0) break; + } + wclrtoeol(uiStatus); + + const struct Window *window = windows[show]; + char *end = &uiTitle[sizeof(uiTitle)]; + char *ptr = seprintf( + uiTitle, end, "%s %s", network.name, idNames[window->id] + ); + if (window->mark && window->unreadWarm) { + ptr = seprintf( + ptr, end, " +%d%s", window->unreadWarm, &"!"[window->heat < Hot] + ); + } + if (others.unread) { + ptr = seprintf( + ptr, end, " (+%d%s)", others.unread, &"!"[others.heat < Hot] + ); + } +} + +static size_t windowTop(const struct Window *window) { + size_t top = BufferCap - MAIN_LINES - window->scroll; + if (window->scroll) top += MarkerLines; + return top; +} + +static size_t windowBottom(const struct Window *window) { + size_t bottom = BufferCap - (window->scroll ?: 1); + if (window->scroll) bottom -= SplitLines + MarkerLines; + return bottom; +} + +static void mainAdd(int y, bool time, const struct Line *line) { + int ny, nx; + wmove(uiMain, y, 0); + if (!line || !line->str[0]) { + wclrtoeol(uiMain); + return; + } + if (time && line->time) { + char buf[TimeCap]; + strftime(buf, sizeof(buf), windowTime.format, localtime(&line->time)); + struct Style init = { .fg = Gray, .bg = Default }; + styleAdd(uiMain, init, buf); + waddch(uiMain, ' '); + } else if (time) { + whline(uiMain, ' ', windowTime.width); + wmove(uiMain, y, windowTime.width); + } + styleAdd(uiMain, StyleDefault, line->str); + getyx(uiMain, ny, nx); + if (ny != y) return; + wclrtoeol(uiMain); + (void)nx; +} + +static void mainUpdate(void) { + const struct Window *window = windows[show]; + + int y = 0; + int marker = MAIN_LINES - SplitLines - MarkerLines; + for (size_t i = windowTop(window); i < BufferCap; ++i) { + mainAdd(y++, window->time, bufferHard(window->buffer, i)); + if (window->scroll && y == marker) break; + } + if (!window->scroll) return; + + y = MAIN_LINES - SplitLines; + for (size_t i = BufferCap - SplitLines; i < BufferCap; ++i) { + mainAdd(y++, window->time, bufferHard(window->buffer, i)); + } + wattr_set(uiMain, A_NORMAL, 0, NULL); + mvwhline(uiMain, marker, 0, ACS_BULLET, COLS); +} + +void windowUpdate(void) { + statusUpdate(); + mainUpdate(); +} + +void windowBare(void) { + uiHide(); + uiWait(); + + const struct Window *window = windows[show]; + const struct Line *line = bufferHard(window->buffer, windowBottom(window)); + + uint num = 0; + if (line) num = line->num; + for (size_t i = 0; i < BufferCap; ++i) { + line = bufferSoft(window->buffer, i); + if (!line) continue; + if (line->num > num) break; + if (!line->str[0]) { + printf("\n"); + continue; + } + + char buf[TimeCap]; + struct Style style = { .fg = Gray, .bg = Default }; + strftime(buf, sizeof(buf), windowTime.format, localtime(&line->time)); + vid_attr(uiAttr(style), uiPair(style), NULL); + printf("%s ", buf); + + bool align = false; + style = StyleDefault; + for (const char *str = line->str; *str;) { + if (*str == '\t') { + printf("%c", (align ? '\t' : ' ')); + align = true; + str++; + } + + size_t len = styleParse(&style, &str); + size_t tab = strcspn(str, "\t"); + if (tab < len) len = tab; + + vid_attr(uiAttr(style), uiPair(style), NULL); + printf("%.*s", (int)len, str); + str += len; + } + printf("\n"); + } +} + +static void mark(struct Window *window) { + if (window->scroll) return; + window->mark = true; + window->unreadSoft = 0; + window->unreadWarm = 0; +} + +static void unmark(struct Window *window) { + if (!window->scroll) { + window->mark = false; + window->heat = Cold; + } + statusUpdate(); +} + +static void scrollN(struct Window *window, int n) { + mark(window); + window->scroll += n; + if (window->scroll > BufferCap - MAIN_LINES) { + window->scroll = BufferCap - MAIN_LINES; + } + if (window->scroll < 0) window->scroll = 0; + unmark(window); + if (window == windows[show]) mainUpdate(); +} + +static void scrollTo(struct Window *window, int top) { + window->scroll = 0; + scrollN(window, top - MAIN_LINES + MarkerLines); +} + +static int windowCols(const struct Window *window) { + return COLS - (window->time ? windowTime.width : 0); +} + +bool windowWrite(uint id, enum Heat heat, const time_t *src, const char *str) { + struct Window *window = windows[windowFor(id)]; + time_t ts = (src ? *src : time(NULL)); + + if (heat >= window->thresh) { + if (!window->unreadSoft++) window->unreadHard = 0; + } + if (window->mark && heat > Cold) { + if (!window->unreadWarm++) { + int lines = bufferPush( + window->buffer, windowCols(window), + window->thresh, Warm, ts, "" + ); + if (window->scroll) scrollN(window, lines); + if (window->unreadSoft > 1) { + window->unreadSoft++; + window->unreadHard += lines; + } + } + if (heat > window->heat) window->heat = heat; + statusUpdate(); + } + int lines = bufferPush( + window->buffer, windowCols(window), + window->thresh, heat, ts, str + ); + window->unreadHard += lines; + if (window->scroll) scrollN(window, lines); + if (window == windows[show]) mainUpdate(); + + return window->mark && heat > Warm; +} + +static void reflow(struct Window *window) { + uint num = 0; + const struct Line *line = bufferHard(window->buffer, windowTop(window)); + if (line) num = line->num; + window->unreadHard = bufferReflow( + window->buffer, windowCols(window), + window->thresh, window->unreadSoft + ); + if (!window->scroll || !num) return; + for (size_t i = 0; i < BufferCap; ++i) { + line = bufferHard(window->buffer, i); + if (!line || line->num != num) continue; + scrollTo(window, BufferCap - i); + break; + } +} + +void windowResize(void) { + for (uint num = 0; num < count; ++num) { + reflow(windows[num]); + } + windowUpdate(); +} + +uint windowID(void) { + return windows[show]->id; +} + +uint windowNum(void) { + return show; +} + +void windowShow(uint num) { + if (num >= count) return; + if (num != show) { + swap = show; + mark(windows[swap]); + } + show = num; + user = num; + unmark(windows[show]); + mainUpdate(); + uiUpdate(); +} + +void windowAuto(void) { + uint minHot = UINT_MAX, numHot = 0; + uint minWarm = UINT_MAX, numWarm = 0; + for (uint num = 0; num < count; ++num) { + struct Window *window = windows[num]; + if (window->heat >= Hot) { + if (window->unreadWarm >= minHot) continue; + minHot = window->unreadWarm; + numHot = num; + } + if (window->heat >= Warm && !window->mute) { + if (window->unreadWarm >= minWarm) continue; + minWarm = window->unreadWarm; + numWarm = num; + } + } + uint oldUser = user; + if (minHot < UINT_MAX) { + windowShow(numHot); + user = oldUser; + } else if (minWarm < UINT_MAX) { + windowShow(numWarm); + user = oldUser; + } else if (user != show) { + windowShow(user); + } +} + +void windowSwap(void) { + windowShow(swap); +} + +void windowMove(uint from, uint to) { + if (from >= count) return; + struct Window *window = windowRemove(from); + if (to < count) { + windowShow(windowInsert(to, window)); + } else { + windowShow(windowPush(window)); + } +} + +void windowClose(uint num) { + if (num >= count) return; + if (windows[num]->id == Network) return; + struct Window *window = windowRemove(num); + completeClear(window->id); + windowFree(window); + if (swap >= num) swap--; + if (show == num) { + windowShow(swap); + swap = show; + } else if (show > num) { + show--; + mainUpdate(); + } + statusUpdate(); +} + +void windowList(void) { + for (uint num = 0; num < count; ++num) { + const struct Window *window = windows[num]; + uiFormat( + Network, Warm, NULL, "\3%02d%u %s", + idColors[window->id], num, idNames[window->id] + ); + } +} + +void windowMark(void) { + mark(windows[show]); +} + +void windowUnmark(void) { + unmark(windows[show]); +} + +void windowToggleMute(void) { + windows[show]->mute ^= true; + statusUpdate(); +} + +void windowToggleTime(void) { + windows[show]->time ^= true; + reflow(windows[show]); + windowUpdate(); + uiUpdate(); +} + +void windowToggleThresh(int n) { + struct Window *window = windows[show]; + if (n > 0 && window->thresh == Hot) return; + if (n < 0 && window->thresh == Ice) { + window->thresh = Cold; + } else { + window->thresh += n; + } + reflow(window); + windowUpdate(); +} + +bool windowTimeEnable(void) { + return windows[show]->time; +} + +void windowScroll(enum Scroll by, int n) { + struct Window *window = windows[show]; + switch (by) { + break; case ScrollOne: { + scrollN(window, n); + } + break; case ScrollPage: { + scrollN(window, n * (MAIN_LINES - SplitLines - MarkerLines - 1)); + } + break; case ScrollAll: { + if (n < 0) { + scrollTo(window, 0); + break; + } + for (size_t i = 0; i < BufferCap; ++i) { + if (!bufferHard(window->buffer, i)) continue; + scrollTo(window, BufferCap - i); + break; + } + } + break; case ScrollUnread: { + scrollTo(window, window->unreadHard); + } + break; case ScrollHot: { + for (size_t i = windowTop(window) + n; i < BufferCap; i += n) { + const struct Line *line = bufferHard(window->buffer, i); + const struct Line *prev = bufferHard(window->buffer, i - 1); + if (!line || line->heat < Hot) continue; + if (prev && prev->heat > Warm) continue; + scrollTo(window, BufferCap - i); + break; + } + } + } +} + +void windowSearch(const char *str, int dir) { + struct Window *window = windows[show]; + for (size_t i = windowTop(window) + dir; i < BufferCap; i += dir) { + const struct Line *line = bufferHard(window->buffer, i); + if (!line || !strcasestr(line->str, str)) continue; + scrollTo(window, BufferCap - i); + break; + } +} + +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 windowSave(FILE *file) { + int error; + for (uint num = 0; num < count; ++num) { + const struct Window *window = windows[num]; + error = 0 + || writeString(file, idNames[window->id]) + || writeTime(file, window->mute) + || writeTime(file, window->time) + || writeTime(file, window->thresh) + || writeTime(file, window->heat) + || writeTime(file, window->unreadSoft) + || writeTime(file, window->unreadWarm); + if (error) return error; + for (size_t i = 0; i < BufferCap; ++i) { + const struct Line *line = bufferSoft(window->buffer, i); + if (!line) continue; + error = 0 + || writeTime(file, line->time) + || writeTime(file, line->heat) + || writeString(file, line->str); + if (error) return error; + } + error = writeTime(file, 0); + if (error) return error; + } + return writeString(file, ""); +} + +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 windowLoad(FILE *file, size_t version) { + size_t cap = 0; + char *buf = NULL; + while (0 < readString(file, &buf, &cap) && buf[0]) { + struct Window *window = windows[windowFor(idFor(buf))]; + if (version > 3) window->mute = readTime(file); + if (version > 6) window->time = readTime(file); + if (version > 5) window->thresh = readTime(file); + if (version > 0) { + window->heat = readTime(file); + window->unreadSoft = readTime(file); + window->unreadWarm = readTime(file); + } + for (;;) { + time_t time = readTime(file); + if (!time) break; + enum Heat heat = (version > 2 ? readTime(file) : Cold); + readString(file, &buf, &cap); + bufferPush(window->buffer, COLS, window->thresh, heat, time, buf); + } + reflow(window); + } + free(buf); +} -- cgit 1.4.1 From 073cebec7a5a07ab2b829e40ce47ec3b1d774bd9 Mon Sep 17 00:00:00 2001 From: June McEnroe Date: Sat, 19 Feb 2022 20:20:19 -0500 Subject: Factor out input handling to input.c --- Makefile | 5 +- README.7 | 4 +- chat.c | 9 +- chat.h | 14 ++- handle.c | 2 +- input.c | 390 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ui.c | 369 ++--------------------------------------------------------- window.c | 6 +- 8 files changed, 424 insertions(+), 375 deletions(-) create mode 100644 input.c (limited to 'handle.c') diff --git a/Makefile b/Makefile index dffe4e8..3abba03 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ OBJS += config.o OBJS += edit.o OBJS += filter.o OBJS += handle.o +OBJS += input.o OBJS += irc.o OBJS += log.o OBJS += ui.o @@ -36,7 +37,9 @@ all: catgirl catgirl: ${OBJS} ${CC} ${LDFLAGS} ${OBJS} ${LDLIBS} -o $@ -${OBJS} ${TESTS}: chat.h edit.h +${OBJS}: chat.h + +edit.o edit.t input.o: edit.h check: ${TESTS} diff --git a/README.7 b/README.7 index b98b589..dd0020d 100644 --- a/README.7 +++ b/README.7 @@ -183,10 +183,12 @@ IRC connection and parsing curses interface .It Pa window.c window management +.It Pa input.c +input handling .It Pa handle.c IRC message handling .It Pa command.c -input command handling +command handling .It Pa buffer.c line wrapping .It Pa edit.c diff --git a/chat.c b/chat.c index 57cae6d..c4b256c 100644 --- a/chat.c +++ b/chat.c @@ -375,7 +375,7 @@ int main(int argc, char *argv[]) { ircConfig(insecure, trust, cert, priv); - uiInitEarly(); + uiInit(); sig_t cursesWinch = signal(SIGWINCH, signalHandler); if (save) { uiLoad(save); @@ -407,7 +407,8 @@ int main(int argc, char *argv[]) { ircFormat("NICK :%s\r\n", nick); ircFormat("USER %s 0 * :%s\r\n", user, real); - uiInitLate(); + // Avoid disabling VINTR until main loop. + inputInit(); signal(SIGHUP, signalHandler); signal(SIGINT, signalHandler); signal(SIGALRM, signalHandler); @@ -436,7 +437,7 @@ int main(int argc, char *argv[]) { int nfds = poll(fds, (pipes ? ARRAY_LEN(fds) : 2), -1); if (nfds < 0 && errno != EINTR) err(EX_IOERR, "poll"); if (nfds > 0) { - if (fds[0].revents) uiRead(); + if (fds[0].revents) inputRead(); if (fds[1].revents) ircRecv(); if (fds[2].revents) utilRead(); if (fds[3].revents) execRead(); @@ -488,7 +489,7 @@ int main(int argc, char *argv[]) { cursesWinch(SIGWINCH); // doupdate(3) needs to be called for KEY_RESIZE to be picked up. uiDraw(); - uiRead(); + inputRead(); } uiDraw(); diff --git a/chat.h b/chat.h index e9a2926..1bf1edd 100644 --- a/chat.h +++ b/chat.h @@ -311,17 +311,16 @@ enum { 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 uiInitEarly(void); -void uiInitLate(void); +void uiInit(void); uint uiAttr(struct Style style); short uiPair(struct Style style); -void uiUpdate(void); void uiShow(void); void uiHide(void); -void uiWait(void); void uiDraw(void); -void uiRead(void); +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, ... @@ -329,6 +328,11 @@ void uiFormat( void uiLoad(const char *name); int uiSave(void); +void inputInit(void); +void inputWait(void); +void inputUpdate(void); +void inputRead(void); + enum Scroll { ScrollOne, ScrollPage, diff --git a/handle.c b/handle.c index f4cf68e..9f051c7 100644 --- a/handle.c +++ b/handle.c @@ -425,7 +425,7 @@ static void handleNick(struct Message *msg) { require(msg, true, 1); if (!strcmp(msg->nick, self.nick)) { set(&self.nick, msg->params[0]); - uiRead(); // Update prompt. + inputUpdate(); } for (uint id; (id = completeID(msg->nick));) { if (!strcmp(idNames[id], msg->nick)) { diff --git a/input.c b/input.c new file mode 100644 index 0000000..963bd4e --- /dev/null +++ b/input.c @@ -0,0 +1,390 @@ +/* Copyright (C) 2020 June 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 . + * + * 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 _XOPEN_SOURCE_EXTENDED + +#include +#include +#include +#include +#include +#include +#include + +#include "chat.h" +#include "edit.h" + +#define ENUM_KEY \ + X(KeyCtrlLeft, "\33[1;5D", NULL) \ + X(KeyCtrlRight, "\33[1;5C", NULL) \ + X(KeyMeta0, "\0330", "\33)") \ + X(KeyMeta1, "\0331", "\33!") \ + X(KeyMeta2, "\0332", "\33@") \ + X(KeyMeta3, "\0333", "\33#") \ + X(KeyMeta4, "\0334", "\33$") \ + X(KeyMeta5, "\0335", "\33%") \ + X(KeyMeta6, "\0336", "\33^") \ + X(KeyMeta7, "\0337", "\33&") \ + X(KeyMeta8, "\0338", "\33*") \ + X(KeyMeta9, "\0339", "\33(") \ + X(KeyMetaA, "\33a", NULL) \ + X(KeyMetaB, "\33b", NULL) \ + X(KeyMetaD, "\33d", NULL) \ + X(KeyMetaF, "\33f", NULL) \ + X(KeyMetaL, "\33l", NULL) \ + X(KeyMetaM, "\33m", NULL) \ + X(KeyMetaN, "\33n", NULL) \ + X(KeyMetaP, "\33p", NULL) \ + X(KeyMetaQ, "\33q", NULL) \ + X(KeyMetaS, "\33s", NULL) \ + X(KeyMetaT, "\33t", NULL) \ + X(KeyMetaU, "\33u", NULL) \ + X(KeyMetaV, "\33v", NULL) \ + X(KeyMetaEnter, "\33\r", "\33\n") \ + X(KeyMetaGt, "\33>", "\33.") \ + X(KeyMetaLt, "\33<", "\33,") \ + X(KeyMetaEqual, "\33=", NULL) \ + X(KeyMetaMinus, "\33-", "\33_") \ + X(KeyMetaPlus, "\33+", NULL) \ + X(KeyMetaSlash, "\33/", "\33?") \ + X(KeyFocusIn, "\33[I", NULL) \ + X(KeyFocusOut, "\33[O", NULL) \ + X(KeyPasteOn, "\33[200~", NULL) \ + X(KeyPasteOff, "\33[201~", NULL) \ + X(KeyPasteManual, "\32p", "\32\20") + +enum { + KeyMax = KEY_MAX, +#define X(id, seq, alt) id, + ENUM_KEY +#undef X +}; + +static struct Edit edit; + +void inputInit(void) { + struct termios term; + int error = tcgetattr(STDOUT_FILENO, &term); + if (error) err(EX_OSERR, "tcgetattr"); + + // Gain use of C-q, C-s, C-c, C-z, C-y, C-v, C-o. + term.c_iflag &= ~IXON; + term.c_cc[VINTR] = _POSIX_VDISABLE; + term.c_cc[VSUSP] = _POSIX_VDISABLE; +#ifdef VDSUSP + term.c_cc[VDSUSP] = _POSIX_VDISABLE; +#endif + term.c_cc[VLNEXT] = _POSIX_VDISABLE; + term.c_cc[VDISCARD] = _POSIX_VDISABLE; + + error = tcsetattr(STDOUT_FILENO, TCSANOW, &term); + if (error) err(EX_OSERR, "tcsetattr"); + + def_prog_mode(); + +#define X(id, seq, alt) define_key(seq, id); if (alt) define_key(alt, id); + ENUM_KEY +#undef X + + keypad(uiInput, true); + nodelay(uiInput, true); +} + +static void inputAdd(struct Style reset, struct Style *style, const char *str) { + while (*str) { + const char *code = str; + size_t len = styleParse(style, &str); + wattr_set(uiInput, A_BOLD | A_REVERSE, 0, NULL); + switch (*code) { + break; case B: waddch(uiInput, 'B'); + break; case C: waddch(uiInput, 'C'); + break; case O: waddch(uiInput, 'O'); + break; case R: waddch(uiInput, 'R'); + break; case I: waddch(uiInput, 'I'); + break; case U: waddch(uiInput, 'U'); + break; case '\n': waddch(uiInput, 'N'); + } + if (str - code > 1) waddnstr(uiInput, &code[1], str - &code[1]); + if (str[0] == '\n') { + *style = reset; + str++; + len--; + } + size_t nl = strcspn(str, "\n"); + if (nl < len) len = nl; + wattr_set(uiInput, uiAttr(*style), uiPair(*style), NULL); + waddnstr(uiInput, str, len); + str += len; + } +} + +static char *inputStop( + struct Style reset, struct Style *style, + const char *str, char *stop +) { + char ch = *stop; + *stop = '\0'; + inputAdd(reset, style, str); + *stop = ch; + return stop; +} + +void inputUpdate(void) { + uint id = windowID(); + char *buf = editString(&edit); + + const char *prefix = ""; + const char *prompt = self.nick; + const char *suffix = ""; + const char *skip = buf; + struct Style stylePrompt = { .fg = self.color, .bg = Default }; + struct Style styleInput = StyleDefault; + + size_t split = commandWillSplit(id, buf); + const char *privmsg = commandIsPrivmsg(id, buf); + const char *notice = commandIsNotice(id, buf); + const char *action = commandIsAction(id, buf); + if (privmsg) { + prefix = "<"; suffix = "> "; + skip = privmsg; + } else if (notice) { + prefix = "-"; suffix = "- "; + styleInput.fg = LightGray; + skip = notice; + } else if (action) { + prefix = "* "; suffix = " "; + stylePrompt.attr |= Italic; + styleInput.attr |= Italic; + skip = action; + } else if (id == Debug && buf[0] != '/') { + prompt = "<< "; + stylePrompt.fg = Gray; + } else { + prompt = ""; + } + if (skip > &buf[edit.mbs.pos]) { + prefix = prompt = suffix = ""; + skip = buf; + } + + int y, x; + wmove(uiInput, 0, 0); + if (windowTimeEnable() && id != Network) { + whline(uiInput, ' ', windowTime.width); + wmove(uiInput, 0, windowTime.width); + } + wattr_set(uiInput, uiAttr(stylePrompt), uiPair(stylePrompt), NULL); + waddstr(uiInput, prefix); + waddstr(uiInput, prompt); + waddstr(uiInput, suffix); + getyx(uiInput, y, x); + + int pos; + struct Style style = styleInput; + inputStop(styleInput, &style, skip, &buf[edit.mbs.pos]); + getyx(uiInput, y, pos); + wmove(uiInput, y, x); + + style = styleInput; + const char *ptr = skip; + if (split) { + ptr = inputStop(styleInput, &style, ptr, &buf[split]); + style = styleInput; + style.bg = Red; + } + inputAdd(styleInput, &style, ptr); + wclrtoeol(uiInput); + wmove(uiInput, y, pos); +} + +static void inputEnter(void) { + command(windowID(), editString(&edit)); + editFn(&edit, EditClear); +} + +static void keyCode(int code) { + switch (code) { + break; case KEY_RESIZE: uiResize(); + break; case KeyFocusIn: windowUnmark(); + break; case KeyFocusOut: windowMark(); + + break; case KeyMetaEnter: editInsert(&edit, L'\n'); + break; case KeyMetaEqual: windowToggleMute(); + break; case KeyMetaMinus: windowToggleThresh(-1); + break; case KeyMetaPlus: windowToggleThresh(+1); + break; case KeyMetaSlash: windowSwap(); + + break; case KeyMetaGt: windowScroll(ScrollAll, -1); + break; case KeyMetaLt: windowScroll(ScrollAll, +1); + + break; case KeyMeta0 ... KeyMeta9: windowShow(code - KeyMeta0); + break; case KeyMetaA: windowAuto(); + break; case KeyMetaB: editFn(&edit, EditPrevWord); + break; case KeyMetaD: editFn(&edit, EditDeleteNextWord); + break; case KeyMetaF: editFn(&edit, EditNextWord); + break; case KeyMetaL: windowBare(); + break; case KeyMetaM: uiWrite(windowID(), Warm, NULL, ""); + break; case KeyMetaN: windowScroll(ScrollHot, +1); + break; case KeyMetaP: windowScroll(ScrollHot, -1); + break; case KeyMetaQ: editFn(&edit, EditCollapse); + break; case KeyMetaS: uiSpoilerReveal ^= true; windowUpdate(); + break; case KeyMetaT: windowToggleTime(); + break; case KeyMetaU: windowScroll(ScrollUnread, 0); + break; case KeyMetaV: windowScroll(ScrollPage, +1); + + break; case KeyCtrlLeft: editFn(&edit, EditPrevWord); + break; case KeyCtrlRight: editFn(&edit, EditNextWord); + + break; case KEY_BACKSPACE: editFn(&edit, EditDeletePrev); + break; case KEY_DC: editFn(&edit, EditDeleteNext); + break; case KEY_DOWN: windowScroll(ScrollOne, -1); + break; case KEY_END: editFn(&edit, EditTail); + break; case KEY_ENTER: inputEnter(); + break; case KEY_HOME: editFn(&edit, EditHead); + break; case KEY_LEFT: editFn(&edit, EditPrev); + break; case KEY_NPAGE: windowScroll(ScrollPage, -1); + break; case KEY_PPAGE: windowScroll(ScrollPage, +1); + break; case KEY_RIGHT: editFn(&edit, EditNext); + break; case KEY_SEND: windowScroll(ScrollAll, -1); + break; case KEY_SHOME: windowScroll(ScrollAll, +1); + break; case KEY_UP: windowScroll(ScrollOne, +1); + } +} + +static void keyCtrl(wchar_t ch) { + switch (ch ^ L'@') { + break; case L'?': editFn(&edit, EditDeletePrev); + break; case L'A': editFn(&edit, EditHead); + break; case L'B': editFn(&edit, EditPrev); + break; case L'C': raise(SIGINT); + break; case L'D': editFn(&edit, EditDeleteNext); + break; case L'E': editFn(&edit, EditTail); + break; case L'F': editFn(&edit, EditNext); + break; case L'H': editFn(&edit, EditDeletePrev); + break; case L'J': inputEnter(); + break; case L'K': editFn(&edit, EditDeleteTail); + break; case L'L': clearok(curscr, true); + break; case L'N': windowShow(windowNum() + 1); + break; case L'P': windowShow(windowNum() - 1); + break; case L'R': windowSearch(editString(&edit), -1); + break; case L'S': windowSearch(editString(&edit), +1); + break; case L'T': editFn(&edit, EditTranspose); + break; case L'U': editFn(&edit, EditDeleteHead); + break; case L'V': windowScroll(ScrollPage, -1); + break; case L'W': editFn(&edit, EditDeletePrevWord); + break; case L'Y': editFn(&edit, EditPaste); + } +} + +static void keyStyle(wchar_t ch) { + if (iswcntrl(ch)) ch = towlower(ch ^ L'@'); + char buf[8] = {0}; + enum Color color = Default; + switch (ch) { + break; case L'A': color = Gray; + break; case L'B': color = Blue; + break; case L'C': color = Cyan; + break; case L'G': color = Green; + break; case L'K': color = Black; + break; case L'M': color = Magenta; + break; case L'N': color = Brown; + break; case L'O': color = Orange; + break; case L'P': color = Pink; + break; case L'R': color = Red; + break; case L'W': color = White; + break; case L'Y': color = Yellow; + break; case L'b': buf[0] = B; + break; case L'c': buf[0] = C; + break; case L'i': buf[0] = I; + break; case L'o': buf[0] = O; + break; case L'r': buf[0] = R; + break; case L's': { + snprintf(buf, sizeof(buf), "%c%02d,%02d", C, Black, Black); + } + break; case L'u': buf[0] = U; + } + if (color != Default) { + snprintf(buf, sizeof(buf), "%c%02d", C, color); + } + for (char *ch = buf; *ch; ++ch) { + editInsert(&edit, *ch); + } +} + +static bool waiting; + +void inputWait(void) { + waiting = true; +} + +void inputRead(void) { + if (isendwin()) { + if (waiting) { + uiShow(); + flushinp(); + waiting = false; + } else { + return; + } + } + + wint_t ch; + static bool paste, style, literal; + for (int ret; ERR != (ret = wget_wch(uiInput, &ch));) { + bool spr = uiSpoilerReveal; + if (ret == KEY_CODE_YES && ch == KeyPasteOn) { + paste = true; + } else if (ret == KEY_CODE_YES && ch == KeyPasteOff) { + paste = false; + } else if (ret == KEY_CODE_YES && ch == KeyPasteManual) { + paste ^= true; + } else if (paste || literal) { + editInsert(&edit, ch); + } else if (ret == KEY_CODE_YES) { + keyCode(ch); + } else if (ch == (L'Z' ^ L'@')) { + style = true; + continue; + } else if (style && ch == (L'V' ^ L'@')) { + literal = true; + continue; + } else if (style) { + keyStyle(ch); + } else if (iswcntrl(ch)) { + keyCtrl(ch); + } else { + editInsert(&edit, ch); + } + style = false; + literal = false; + if (spr) { + uiSpoilerReveal = false; + windowUpdate(); + } + } + inputUpdate(); +} diff --git a/ui.c b/ui.c index 85b5a72..d24f7a7 100644 --- a/ui.c +++ b/ui.c @@ -54,7 +54,6 @@ #endif #include "chat.h" -#include "edit.h" // Annoying stuff from : #undef lines @@ -66,7 +65,7 @@ WINDOW *uiStatus; WINDOW *uiMain; -static WINDOW *input; +WINDOW *uiInput; static short colorPairs; @@ -101,52 +100,6 @@ static short colorPair(short fg, short bg) { return colorPairs++; } -#define ENUM_KEY \ - X(KeyCtrlLeft, "\33[1;5D", NULL) \ - X(KeyCtrlRight, "\33[1;5C", NULL) \ - X(KeyMeta0, "\0330", "\33)") \ - X(KeyMeta1, "\0331", "\33!") \ - X(KeyMeta2, "\0332", "\33@") \ - X(KeyMeta3, "\0333", "\33#") \ - X(KeyMeta4, "\0334", "\33$") \ - X(KeyMeta5, "\0335", "\33%") \ - X(KeyMeta6, "\0336", "\33^") \ - X(KeyMeta7, "\0337", "\33&") \ - X(KeyMeta8, "\0338", "\33*") \ - X(KeyMeta9, "\0339", "\33(") \ - X(KeyMetaA, "\33a", NULL) \ - X(KeyMetaB, "\33b", NULL) \ - X(KeyMetaD, "\33d", NULL) \ - X(KeyMetaF, "\33f", NULL) \ - X(KeyMetaL, "\33l", NULL) \ - X(KeyMetaM, "\33m", NULL) \ - X(KeyMetaN, "\33n", NULL) \ - X(KeyMetaP, "\33p", NULL) \ - X(KeyMetaQ, "\33q", NULL) \ - X(KeyMetaS, "\33s", NULL) \ - X(KeyMetaT, "\33t", NULL) \ - X(KeyMetaU, "\33u", NULL) \ - X(KeyMetaV, "\33v", NULL) \ - X(KeyMetaEnter, "\33\r", "\33\n") \ - X(KeyMetaGt, "\33>", "\33.") \ - X(KeyMetaLt, "\33<", "\33,") \ - X(KeyMetaEqual, "\33=", NULL) \ - X(KeyMetaMinus, "\33-", "\33_") \ - X(KeyMetaPlus, "\33+", NULL) \ - X(KeyMetaSlash, "\33/", "\33?") \ - X(KeyFocusIn, "\33[I", NULL) \ - X(KeyFocusOut, "\33[O", NULL) \ - X(KeyPasteOn, "\33[200~", NULL) \ - X(KeyPasteOff, "\33[201~", NULL) \ - X(KeyPasteManual, "\32p", "\32\20") - -enum { - KeyMax = KEY_MAX, -#define X(id, seq, alt) id, - ENUM_KEY -#undef X -}; - // XXX: Assuming terminals will be fine with these even if they're unsupported, // since they're "private" modes. static const char *FocusMode[2] = { "\33[?1004l", "\33[?1004h" }; @@ -158,7 +111,7 @@ static void errExit(void) { reset_shell_mode(); } -void uiInitEarly(void) { +void uiInit(void) { initscr(); cbreak(); noecho(); @@ -177,49 +130,20 @@ void uiInitEarly(void) { from_status_line = "\7"; } -#define X(id, seq, alt) define_key(seq, id); if (alt) define_key(alt, id); - ENUM_KEY -#undef X - uiStatus = newwin(StatusLines, COLS, 0, 0); if (!uiStatus) err(EX_OSERR, "newwin"); uiMain = newwin(MAIN_LINES, COLS, StatusLines, 0); if (!uiMain) err(EX_OSERR, "newwin"); - input = newpad(InputLines, InputCols); - if (!input) err(EX_OSERR, "newpad"); - keypad(input, true); - nodelay(input, true); + uiInput = newpad(InputLines, InputCols); + if (!uiInput) err(EX_OSERR, "newpad"); windowInit(); uiShow(); } -// Avoid disabling VINTR until main loop. -void uiInitLate(void) { - struct termios term; - int error = tcgetattr(STDOUT_FILENO, &term); - if (error) err(EX_OSERR, "tcgetattr"); - - // Gain use of C-q, C-s, C-c, C-z, C-y, C-v, C-o. - term.c_iflag &= ~IXON; - term.c_cc[VINTR] = _POSIX_VDISABLE; - term.c_cc[VSUSP] = _POSIX_VDISABLE; -#ifdef VDSUSP - term.c_cc[VDSUSP] = _POSIX_VDISABLE; -#endif - term.c_cc[VLNEXT] = _POSIX_VDISABLE; - term.c_cc[VDISCARD] = _POSIX_VDISABLE; - - error = tcsetattr(STDOUT_FILENO, TCSANOW, &term); - if (error) err(EX_OSERR, "tcsetattr"); - - def_prog_mode(); -} - static bool hidden = true; -static bool waiting; char uiTitle[TitleCap]; static char prevTitle[TitleCap]; @@ -229,9 +153,9 @@ void uiDraw(void) { wnoutrefresh(uiStatus); wnoutrefresh(uiMain); int y, x; - getyx(input, y, x); + getyx(uiInput, y, x); pnoutrefresh( - input, + uiInput, 0, (x + 1 > RIGHT ? x + 1 - RIGHT : 0), LINES - InputLines, 0, BOTTOM, RIGHT @@ -284,10 +208,10 @@ uint uiAttr(struct Style style) { return attr | colorAttr(Colors[style.fg]); } -static bool spoilerReveal; +bool uiSpoilerReveal; short uiPair(struct Style style) { - if (spoilerReveal && style.fg == style.bg) { + if (uiSpoilerReveal && style.fg == style.bg) { return colorPair(Colors[Default], Colors[style.bg]); } return colorPair(Colors[style.fg], Colors[style.bg]); @@ -312,10 +236,6 @@ void uiHide(void) { endwin(); } -void uiWait(void) { - waiting = true; -} - struct Util uiNotifyUtil; static void notify(uint id, const char *str) { if (self.restricted) return; @@ -361,283 +281,12 @@ void uiFormat( uiWrite(id, heat, time, buf); } -static void resize(void) { +void uiResize(void) { wclear(uiMain); wresize(uiMain, MAIN_LINES, COLS); windowResize(); } -static void inputAdd(struct Style reset, struct Style *style, const char *str) { - while (*str) { - const char *code = str; - size_t len = styleParse(style, &str); - wattr_set(input, A_BOLD | A_REVERSE, 0, NULL); - switch (*code) { - break; case B: waddch(input, 'B'); - break; case C: waddch(input, 'C'); - break; case O: waddch(input, 'O'); - break; case R: waddch(input, 'R'); - break; case I: waddch(input, 'I'); - break; case U: waddch(input, 'U'); - break; case '\n': waddch(input, 'N'); - } - if (str - code > 1) waddnstr(input, &code[1], str - &code[1]); - if (str[0] == '\n') { - *style = reset; - str++; - len--; - } - size_t nl = strcspn(str, "\n"); - if (nl < len) len = nl; - wattr_set(input, uiAttr(*style), uiPair(*style), NULL); - waddnstr(input, str, len); - str += len; - } -} - -static char *inputStop( - struct Style reset, struct Style *style, - const char *str, char *stop -) { - char ch = *stop; - *stop = '\0'; - inputAdd(reset, style, str); - *stop = ch; - return stop; -} - -static struct Edit edit; - -void uiUpdate(void) { - char *buf = editString(&edit); - uint id = windowID(); - - const char *prefix = ""; - const char *prompt = self.nick; - const char *suffix = ""; - const char *skip = buf; - struct Style stylePrompt = { .fg = self.color, .bg = Default }; - struct Style styleInput = StyleDefault; - - size_t split = commandWillSplit(id, buf); - const char *privmsg = commandIsPrivmsg(id, buf); - const char *notice = commandIsNotice(id, buf); - const char *action = commandIsAction(id, buf); - if (privmsg) { - prefix = "<"; suffix = "> "; - skip = privmsg; - } else if (notice) { - prefix = "-"; suffix = "- "; - styleInput.fg = LightGray; - skip = notice; - } else if (action) { - prefix = "* "; suffix = " "; - stylePrompt.attr |= Italic; - styleInput.attr |= Italic; - skip = action; - } else if (id == Debug && buf[0] != '/') { - prompt = "<< "; - stylePrompt.fg = Gray; - } else { - prompt = ""; - } - if (skip > &buf[edit.mbs.pos]) { - prefix = prompt = suffix = ""; - skip = buf; - } - - int y, x; - wmove(input, 0, 0); - if (windowTimeEnable() && id != Network) { - whline(input, ' ', windowTime.width); - wmove(input, 0, windowTime.width); - } - wattr_set(input, uiAttr(stylePrompt), uiPair(stylePrompt), NULL); - waddstr(input, prefix); - waddstr(input, prompt); - waddstr(input, suffix); - getyx(input, y, x); - - int pos; - struct Style style = styleInput; - inputStop(styleInput, &style, skip, &buf[edit.mbs.pos]); - getyx(input, y, pos); - wmove(input, y, x); - - style = styleInput; - const char *ptr = skip; - if (split) { - ptr = inputStop(styleInput, &style, ptr, &buf[split]); - style = styleInput; - style.bg = Red; - } - inputAdd(styleInput, &style, ptr); - wclrtoeol(input); - wmove(input, y, pos); -} - -static void inputEnter(void) { - command(windowID(), editString(&edit)); - editFn(&edit, EditClear); -} - -static void keyCode(int code) { - switch (code) { - break; case KEY_RESIZE: resize(); - break; case KeyFocusIn: windowUnmark(); - break; case KeyFocusOut: windowMark(); - - break; case KeyMetaEnter: editInsert(&edit, L'\n'); - break; case KeyMetaEqual: windowToggleMute(); - break; case KeyMetaMinus: windowToggleThresh(-1); - break; case KeyMetaPlus: windowToggleThresh(+1); - break; case KeyMetaSlash: windowSwap(); - - break; case KeyMetaGt: windowScroll(ScrollAll, -1); - break; case KeyMetaLt: windowScroll(ScrollAll, +1); - - break; case KeyMeta0 ... KeyMeta9: windowShow(code - KeyMeta0); - break; case KeyMetaA: windowAuto(); - break; case KeyMetaB: editFn(&edit, EditPrevWord); - break; case KeyMetaD: editFn(&edit, EditDeleteNextWord); - break; case KeyMetaF: editFn(&edit, EditNextWord); - break; case KeyMetaL: windowBare(); - break; case KeyMetaM: uiWrite(windowID(), Warm, NULL, ""); - break; case KeyMetaN: windowScroll(ScrollHot, +1); - break; case KeyMetaP: windowScroll(ScrollHot, -1); - break; case KeyMetaQ: editFn(&edit, EditCollapse); - break; case KeyMetaS: spoilerReveal ^= true; windowUpdate(); - break; case KeyMetaT: windowToggleTime(); - break; case KeyMetaU: windowScroll(ScrollUnread, 0); - break; case KeyMetaV: windowScroll(ScrollPage, +1); - - break; case KeyCtrlLeft: editFn(&edit, EditPrevWord); - break; case KeyCtrlRight: editFn(&edit, EditNextWord); - - break; case KEY_BACKSPACE: editFn(&edit, EditDeletePrev); - break; case KEY_DC: editFn(&edit, EditDeleteNext); - break; case KEY_DOWN: windowScroll(ScrollOne, -1); - break; case KEY_END: editFn(&edit, EditTail); - break; case KEY_ENTER: inputEnter(); - break; case KEY_HOME: editFn(&edit, EditHead); - break; case KEY_LEFT: editFn(&edit, EditPrev); - break; case KEY_NPAGE: windowScroll(ScrollPage, -1); - break; case KEY_PPAGE: windowScroll(ScrollPage, +1); - break; case KEY_RIGHT: editFn(&edit, EditNext); - break; case KEY_SEND: windowScroll(ScrollAll, -1); - break; case KEY_SHOME: windowScroll(ScrollAll, +1); - break; case KEY_UP: windowScroll(ScrollOne, +1); - } -} - -static void keyCtrl(wchar_t ch) { - switch (ch ^ L'@') { - break; case L'?': editFn(&edit, EditDeletePrev); - break; case L'A': editFn(&edit, EditHead); - break; case L'B': editFn(&edit, EditPrev); - break; case L'C': raise(SIGINT); - break; case L'D': editFn(&edit, EditDeleteNext); - break; case L'E': editFn(&edit, EditTail); - break; case L'F': editFn(&edit, EditNext); - break; case L'H': editFn(&edit, EditDeletePrev); - break; case L'J': inputEnter(); - break; case L'K': editFn(&edit, EditDeleteTail); - break; case L'L': clearok(curscr, true); - break; case L'N': windowShow(windowNum() + 1); - break; case L'P': windowShow(windowNum() - 1); - break; case L'R': windowSearch(editString(&edit), -1); - break; case L'S': windowSearch(editString(&edit), +1); - break; case L'T': editFn(&edit, EditTranspose); - break; case L'U': editFn(&edit, EditDeleteHead); - break; case L'V': windowScroll(ScrollPage, -1); - break; case L'W': editFn(&edit, EditDeletePrevWord); - break; case L'Y': editFn(&edit, EditPaste); - } -} - -static void keyStyle(wchar_t ch) { - if (iswcntrl(ch)) ch = towlower(ch ^ L'@'); - char buf[8] = {0}; - enum Color color = Default; - switch (ch) { - break; case L'A': color = Gray; - break; case L'B': color = Blue; - break; case L'C': color = Cyan; - break; case L'G': color = Green; - break; case L'K': color = Black; - break; case L'M': color = Magenta; - break; case L'N': color = Brown; - break; case L'O': color = Orange; - break; case L'P': color = Pink; - break; case L'R': color = Red; - break; case L'W': color = White; - break; case L'Y': color = Yellow; - break; case L'b': buf[0] = B; - break; case L'c': buf[0] = C; - break; case L'i': buf[0] = I; - break; case L'o': buf[0] = O; - break; case L'r': buf[0] = R; - break; case L's': { - snprintf(buf, sizeof(buf), "%c%02d,%02d", C, Black, Black); - } - break; case L'u': buf[0] = U; - } - if (color != Default) { - snprintf(buf, sizeof(buf), "%c%02d", C, color); - } - for (char *ch = buf; *ch; ++ch) { - editInsert(&edit, *ch); - } -} - -void uiRead(void) { - if (hidden) { - if (waiting) { - uiShow(); - flushinp(); - waiting = false; - } else { - return; - } - } - - wint_t ch; - static bool paste, style, literal; - for (int ret; ERR != (ret = wget_wch(input, &ch));) { - bool spr = spoilerReveal; - if (ret == KEY_CODE_YES && ch == KeyPasteOn) { - paste = true; - } else if (ret == KEY_CODE_YES && ch == KeyPasteOff) { - paste = false; - } else if (ret == KEY_CODE_YES && ch == KeyPasteManual) { - paste ^= true; - } else if (paste || literal) { - editInsert(&edit, ch); - } else if (ret == KEY_CODE_YES) { - keyCode(ch); - } else if (ch == (L'Z' ^ L'@')) { - style = true; - continue; - } else if (style && ch == (L'V' ^ L'@')) { - literal = true; - continue; - } else if (style) { - keyStyle(ch); - } else if (iswcntrl(ch)) { - keyCtrl(ch); - } else { - editInsert(&edit, ch); - } - style = false; - literal = false; - if (spr) { - spoilerReveal = false; - windowUpdate(); - } - } - uiUpdate(); -} - static FILE *saveFile; static const uint64_t Signatures[] = { diff --git a/window.c b/window.c index ce3ec4d..0395542 100644 --- a/window.c +++ b/window.c @@ -273,7 +273,7 @@ void windowUpdate(void) { void windowBare(void) { uiHide(); - uiWait(); + inputWait(); const struct Window *window = windows[show]; const struct Line *line = bufferHard(window->buffer, windowBottom(window)); @@ -426,7 +426,7 @@ void windowShow(uint num) { user = num; unmark(windows[show]); mainUpdate(); - uiUpdate(); + inputUpdate(); } void windowAuto(void) { @@ -515,7 +515,7 @@ void windowToggleTime(void) { windows[show]->time ^= true; reflow(windows[show]); windowUpdate(); - uiUpdate(); + inputUpdate(); } void windowToggleThresh(int n) { -- cgit 1.4.1 From b6c72806498e95cb08606a7ec46742e5439d3348 Mon Sep 17 00:00:00 2001 From: June McEnroe Date: Sat, 26 Feb 2022 15:51:42 -0500 Subject: Specify commands which depend on caps Currently only /setname. --- chat.c | 3 +- command.c | 96 +++++++++++++++++++++++++++++++++------------------------------ handle.c | 3 +- 3 files changed, 53 insertions(+), 49 deletions(-) (limited to 'handle.c') diff --git a/chat.c b/chat.c index b085165..b74fdc0 100644 --- a/chat.c +++ b/chat.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 C. McEnroe +/* Copyright (C) 2020 June 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 @@ -371,7 +371,6 @@ int main(int argc, char *argv[]) { set(&network.name, host); set(&self.nick, "*"); - commandCompleteAdd(); inputCompleteAdd(); ircConfig(insecure, trust, cert, priv); diff --git a/command.c b/command.c index 60281d9..f404498 100644 --- a/command.c +++ b/command.c @@ -537,53 +537,54 @@ static const struct Handler { const char *cmd; Command *fn; enum Flag flags; + enum Cap caps; } Commands[] = { - { "/away", commandAway, 0 }, - { "/ban", commandBan, 0 }, - { "/close", commandClose, 0 }, - { "/copy", commandCopy, Restrict | Kiosk }, - { "/cs", commandCS, 0 }, - { "/debug", commandDebug, Kiosk }, - { "/deop", commandDeop, 0 }, - { "/devoice", commandDevoice, 0 }, - { "/except", commandExcept, 0 }, - { "/exec", commandExec, Multiline | Restrict | Kiosk }, - { "/help", commandHelp, 0 }, // Restrict special case. - { "/highlight", commandHighlight, 0 }, - { "/ignore", commandIgnore, 0 }, - { "/invex", commandInvex, 0 }, - { "/invite", commandInvite, 0 }, - { "/join", commandJoin, Kiosk }, - { "/kick", commandKick, 0 }, - { "/list", commandList, Kiosk }, - { "/me", commandMe, Multiline }, - { "/mode", commandMode, 0 }, - { "/move", commandMove, 0 }, - { "/msg", commandMsg, Multiline | Kiosk }, - { "/names", commandNames, 0 }, - { "/nick", commandNick, 0 }, - { "/notice", commandNotice, Multiline }, - { "/ns", commandNS, 0 }, - { "/o", commandOpen, Restrict | Kiosk }, - { "/op", commandOp, 0 }, - { "/open", commandOpen, Restrict | Kiosk }, - { "/ops", commandOps, 0 }, - { "/part", commandPart, Kiosk }, - { "/query", commandQuery, Kiosk }, - { "/quit", commandQuit, 0 }, - { "/quote", commandQuote, Multiline | Kiosk }, - { "/say", commandPrivmsg, Multiline }, - { "/setname", commandSetname, 0 }, - { "/topic", commandTopic, 0 }, - { "/unban", commandUnban, 0 }, - { "/unexcept", commandUnexcept, 0 }, - { "/unhighlight", commandUnhighlight, 0 }, - { "/unignore", commandUnignore, 0 }, - { "/uninvex", commandUninvex, 0 }, - { "/voice", commandVoice, 0 }, - { "/whois", commandWhois, 0 }, - { "/whowas", commandWhowas, 0 }, - { "/window", commandWindow, 0 }, + { "/away", commandAway, 0, 0 }, + { "/ban", commandBan, 0, 0 }, + { "/close", commandClose, 0, 0 }, + { "/copy", commandCopy, Restrict | Kiosk, 0 }, + { "/cs", commandCS, 0, 0 }, + { "/debug", commandDebug, Kiosk, 0 }, + { "/deop", commandDeop, 0, 0 }, + { "/devoice", commandDevoice, 0, 0 }, + { "/except", commandExcept, 0, 0 }, + { "/exec", commandExec, Multiline | Restrict | Kiosk, 0 }, + { "/help", commandHelp, 0, 0 }, // Restrict special case. + { "/highlight", commandHighlight, 0, 0 }, + { "/ignore", commandIgnore, 0, 0 }, + { "/invex", commandInvex, 0, 0 }, + { "/invite", commandInvite, 0, 0 }, + { "/join", commandJoin, Kiosk, 0 }, + { "/kick", commandKick, 0, 0 }, + { "/list", commandList, Kiosk, 0 }, + { "/me", commandMe, Multiline, 0 }, + { "/mode", commandMode, 0, 0 }, + { "/move", commandMove, 0, 0 }, + { "/msg", commandMsg, Multiline | Kiosk, 0 }, + { "/names", commandNames, 0, 0 }, + { "/nick", commandNick, 0, 0 }, + { "/notice", commandNotice, Multiline, 0 }, + { "/ns", commandNS, 0, 0 }, + { "/o", commandOpen, Restrict | Kiosk, 0 }, + { "/op", commandOp, 0, 0 }, + { "/open", commandOpen, Restrict | Kiosk, 0 }, + { "/ops", commandOps, 0, 0 }, + { "/part", commandPart, Kiosk, 0 }, + { "/query", commandQuery, Kiosk, 0 }, + { "/quit", commandQuit, 0, 0 }, + { "/quote", commandQuote, Multiline | Kiosk, 0 }, + { "/say", commandPrivmsg, Multiline, 0 }, + { "/setname", commandSetname, 0, CapSetname }, + { "/topic", commandTopic, 0, 0 }, + { "/unban", commandUnban, 0, 0 }, + { "/unexcept", commandUnexcept, 0, 0 }, + { "/unhighlight", commandUnhighlight, 0, 0 }, + { "/unignore", commandUnignore, 0, 0 }, + { "/uninvex", commandUninvex, 0, 0 }, + { "/voice", commandVoice, 0, 0 }, + { "/whois", commandWhois, 0, 0 }, + { "/whowas", commandWhowas, 0, 0 }, + { "/window", commandWindow, 0, 0 }, }; static int compar(const void *cmd, const void *_handler) { @@ -642,6 +643,9 @@ size_t commandWillSplit(uint id, const char *input) { static bool commandAvailable(const struct Handler *handler) { if (handler->flags & Restrict && self.restricted) return false; if (handler->flags & Kiosk && self.kiosk) return false; + if (handler->caps && (handler->caps & self.caps) != handler->caps) { + return false; + } return true; } diff --git a/handle.c b/handle.c index 9f051c7..388a122 100644 --- a/handle.c +++ b/handle.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 C. McEnroe +/* Copyright (C) 2020 June 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 @@ -254,6 +254,7 @@ static void handleReplyWelcome(struct Message *msg) { replies[ReplyTopicAuto] += count; replies[ReplyNamesAuto] += count; } + commandCompleteAdd(); } static void handleReplyISupport(struct Message *msg) { -- cgit 1.4.1