diff --git a/chat.c b/chat.c index ed33fbd..5542c82 100644 --- a/chat.c +++ b/chat.c @@ -60,7 +60,7 @@ static union { void spawn(char *const argv[]) { if (fds.pipe.events) { - uiLog(TAG_DEFAULT, L"spawn: existing pipe"); + uiLog(TAG_STATUS, L"spawn: existing pipe"); return; } @@ -93,7 +93,7 @@ static void pipeRead(void) { if (len) { buf[len] = '\0'; len = strcspn(buf, "\n"); - uiFmt(TAG_DEFAULT, "%.*s", (int)len, buf); + uiFmt(TAG_STATUS, "spawn: %.*s", (int)len, buf); } else { close(fds.pipe.fd); fds.pipe.events = 0; @@ -124,15 +124,15 @@ static void sigchld(int sig) { pid_t pid = wait(&status); if (pid < 0) err(EX_OSERR, "wait"); if (WIFEXITED(status) && WEXITSTATUS(status)) { - uiFmt(TAG_DEFAULT, "spawn: exit %d", WEXITSTATUS(status)); + uiFmt(TAG_STATUS, "spawn: exit %d", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { - uiFmt(TAG_DEFAULT, "spawn: signal %d", WTERMSIG(status)); + uiFmt(TAG_STATUS, "spawn: signal %d", WTERMSIG(status)); } } static void sigint(int sig) { (void)sig; - input(TAG_DEFAULT, "/quit"); + input(TAG_STATUS, "/quit"); uiExit(); exit(EX_OK); } @@ -182,7 +182,7 @@ int main(int argc, char *argv[]) { inputTab(); uiInit(); - uiLog(TAG_DEFAULT, L"Traveling..."); + uiLog(TAG_STATUS, L"Traveling..."); uiDraw(); fds.irc.fd = ircConnect(host, port, pass, webirc); diff --git a/chat.h b/chat.h index f900172..be1e05b 100644 --- a/chat.h +++ b/chat.h @@ -42,12 +42,29 @@ struct Tag { }; enum { TAGS_LEN = 256 }; -const struct Tag TAG_ALL; -const struct Tag TAG_DEFAULT; +const struct Tag TAG_NONE; +const struct Tag TAG_STATUS; +const struct Tag TAG_VERBOSE; struct Tag tagFor(const char *name); -struct Tag tagName(const char *name); -struct Tag tagNum(size_t num); +enum { + IRC_WHITE, + IRC_BLACK, + IRC_BLUE, + IRC_GREEN, + IRC_RED, + IRC_BROWN, + IRC_MAGENTA, + IRC_ORANGE, + IRC_YELLOW, + IRC_LIGHT_GREEN, + IRC_CYAN, + IRC_LIGHT_CYAN, + IRC_LIGHT_BLUE, + IRC_PINK, + IRC_GRAY, + IRC_LIGHT_GRAY, +}; enum { IRC_BOLD = 002, IRC_COLOR = 003, @@ -72,9 +89,9 @@ void uiInit(void); void uiHide(void); void uiExit(void); void uiDraw(void); -void uiBeep(void); void uiRead(void); -void uiFocus(struct Tag tag); +void uiViewTag(struct Tag tag); +void uiViewNum(int num); void uiTopic(struct Tag tag, const char *topic); void uiLog(struct Tag tag, const wchar_t *line); void uiFmt(struct Tag tag, const wchar_t *format, ...); @@ -115,8 +132,9 @@ const wchar_t *editTail(void); void tabTouch(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); -void tabReplace(const char *prev, const char *next); +struct Tag tabTag(const char *word); const char *tabNext(struct Tag tag, const char *prefix); void tabAccept(void); void tabReject(void); diff --git a/handle.c b/handle.c index 29b354c..4d2d4b1 100644 --- a/handle.c +++ b/handle.c @@ -23,14 +23,17 @@ #include "chat.h" -static int color(const char *s) { - if (!s) return 0; - int x = 0; - for (; s[0]; ++s) { - x ^= s[0]; +// Adapted from . +static int color(const char *str) { + if (!str) return IRC_GRAY; + uint32_t hash = 0; + for (; str[0]; ++str) { + hash = (hash << 5) | (hash >> 27); + hash ^= str[0]; + hash *= 0x27220A95; } - x &= 15; - return (x == 1) ? 0 : x; + hash &= IRC_LIGHT_GRAY; + return (hash == IRC_BLACK) ? IRC_GRAY : hash; } static char *paramField(char **params) { @@ -90,44 +93,41 @@ static void handlePing(char *prefix, char *params) { ircFmt("PONG %s\r\n", params); } -static void handle432(char *prefix, char *params) { +static void handleReplyErroneousNickname(char *prefix, char *params) { char *mesg; shift(prefix, NULL, NULL, NULL, params, 3, 0, NULL, NULL, &mesg); - uiLog(TAG_DEFAULT, L"You can't use that name here"); - uiFmt(TAG_DEFAULT, "Sheriff says, \"%s\"", mesg); - uiLog(TAG_DEFAULT, L"Type /nick to choose a new one"); + uiLog(TAG_STATUS, L"You can't use that name here"); + uiFmt(TAG_STATUS, "Sheriff says, \"%s\"", mesg); + uiLog(TAG_STATUS, L"Type /nick to choose a new one"); } -static void handle001(char *prefix, char *params) { +static void handleReplyWelcome(char *prefix, char *params) { char *nick; shift(prefix, NULL, NULL, NULL, params, 1, 0, &nick); if (strcmp(nick, self.nick)) selfNick(nick); - tabTouch(TAG_DEFAULT, self.nick); + tabTouch(TAG_STATUS, self.nick); if (self.join) ircFmt("JOIN %s\r\n", self.join); - uiLog(TAG_DEFAULT, L"You have arrived"); + uiLog(TAG_STATUS, L"You have arrived"); } -static void handle372(char *prefix, char *params) { +static void handleReplyMOTD(char *prefix, char *params) { char *mesg; shift(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &mesg); if (mesg[0] == '-' && mesg[1] == ' ') mesg = &mesg[2]; - uiFmt(TAG_DEFAULT, "%s", mesg); + urlScan(TAG_STATUS, mesg); + uiFmt(TAG_STATUS, "%s", mesg); } static void handleJoin(char *prefix, char *params) { char *nick, *user, *chan; shift(prefix, &nick, &user, NULL, params, 1, 0, &chan); struct Tag tag = tagFor(chan); - if (isSelf(nick, user)) { - tabTouch(TAG_DEFAULT, chan); - uiFocus(tag); - } else { - tabTouch(tag, nick); - } + tabTouch(tag, nick); uiFmt( tag, "\3%d%s\3 arrives in \3%d%s\3", color(user), nick, color(chan), chan ); + if (isSelf(nick, user)) uiViewTag(tag); } static void handlePart(char *prefix, char *params) { @@ -136,6 +136,7 @@ static void handlePart(char *prefix, char *params) { struct Tag tag = tagFor(chan); (void)(isSelf(nick, user) ? tabClear(tag) : tabRemove(tag, nick)); if (mesg) { + urlScan(tag, mesg); uiFmt( tag, "\3%d%s\3 leaves \3%d%s\3, \"%s\"", color(user), nick, color(chan), chan, mesg @@ -154,6 +155,7 @@ static void handleKick(char *prefix, char *params) { struct Tag tag = tagFor(chan); (void)(isSelf(nick, user) ? tabClear(tag) : tabRemove(tag, nick)); if (mesg) { + urlScan(tag, mesg); uiFmt( tag, "\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3, \"%s\"", color(user), nick, color(kick), kick, color(chan), chan, mesg @@ -169,20 +171,23 @@ static void handleKick(char *prefix, char *params) { static void handleQuit(char *prefix, char *params) { char *nick, *user, *mesg; shift(prefix, &nick, &user, NULL, params, 0, 1, &mesg); - // TODO: Send to tags where nick is in tab. - tabRemove(TAG_ALL, nick); - if (mesg) { - char *quot = (mesg[0] == '"') ? "" : "\""; - uiFmt( - TAG_DEFAULT, "\3%d%s\3 leaves, %s%s%s", - color(user), nick, quot, mesg, quot - ); - } else { - uiFmt(TAG_DEFAULT, "\3%d%s\3 leaves", color(user), nick); + char *quot = (mesg && mesg[0] == '"') ? "" : "\""; + struct Tag tag; + while (TAG_NONE.id != (tag = tabTag(nick)).id) { + tabRemove(tag, nick); + if (mesg) { + urlScan(tag, mesg); + uiFmt( + tag, "\3%d%s\3 leaves, %s%s%s", + color(user), nick, quot, mesg, quot + ); + } else { + uiFmt(tag, "\3%d%s\3 leaves", color(user), nick); + } } } -static void handle332(char *prefix, char *params) { +static void handleReplyTopic(char *prefix, char *params) { char *chan, *topic; shift(prefix, NULL, NULL, NULL, params, 3, 0, NULL, &chan, &topic); struct Tag tag = tagFor(chan); @@ -207,7 +212,7 @@ static void handleTopic(char *prefix, char *params) { ); } -static void handle366(char *prefix, char *params) { +static void handleReplyEndOfNames(char *prefix, char *params) { char *chan; shift(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &chan); ircFmt("WHO %s\r\n", chan); @@ -219,14 +224,14 @@ static struct { size_t len; } who; -static void handle352(char *prefix, char *params) { +static void handleReplyWho(char *prefix, char *params) { char *chan, *user, *nick; shift( prefix, NULL, NULL, NULL, params, 6, 0, NULL, &chan, &user, NULL, NULL, &nick ); struct Tag tag = tagFor(chan); - if (!isSelf(nick, user)) tabTouch(tag, nick); + tabTouch(tag, nick); size_t cap = sizeof(who.buf) - who.len; int len = snprintf( &who.buf[who.len], cap, @@ -236,7 +241,7 @@ static void handle352(char *prefix, char *params) { if ((size_t)len < cap) who.len += len; } -static void handle315(char *prefix, char *params) { +static void handleReplyEndOfWho(char *prefix, char *params) { char *chan; shift(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &chan); struct Tag tag = tagFor(chan); @@ -251,12 +256,14 @@ static void handleNick(char *prefix, char *params) { char *prev, *user, *next; shift(prefix, &prev, &user, NULL, params, 1, 0, &next); if (isSelf(prev, user)) selfNick(next); - // TODO: Send to tags where prev is in tab. - tabReplace(prev, next); - uiFmt( - TAG_DEFAULT, "\3%d%s\3 is now known as \3%d%s\3", - color(user), prev, color(user), next - ); + struct Tag tag; + while (TAG_NONE.id != (tag = tabTag(prev)).id) { + tabReplace(tag, prev, next); + uiFmt( + tag, "\3%d%s\3 is now known as \3%d%s\3", + color(user), prev, color(user), next + ); + } } static void handleCTCP(struct Tag tag, char *nick, char *user, char *mesg) { @@ -288,13 +295,13 @@ static void handlePrivmsg(char *prefix, char *params) { tag, "%c\3%d%c%s%c\17 %s", ping["\17\26"], color(user), self["<("], nick, self[">)"], mesg ); - if (ping) uiBeep(); + // TODO: always be beeping. } static void handleNotice(char *prefix, char *params) { char *nick, *user, *chan, *mesg; shift(prefix, &nick, &user, NULL, params, 2, 0, &chan, &mesg); - struct Tag tag = TAG_DEFAULT; + struct Tag tag = TAG_STATUS; if (user) tag = (strcmp(chan, self.nick) ? tagFor(chan) : tagFor(nick)); if (!isSelf(nick, user)) tabTouch(tag, nick); urlScan(tag, mesg); @@ -308,15 +315,15 @@ static const struct { const char *command; Handler handler; } HANDLERS[] = { - { "001", handle001 }, - { "315", handle315 }, - { "332", handle332 }, - { "352", handle352 }, - { "366", handle366 }, - { "372", handle372 }, - { "375", handle372 }, - { "432", handle432 }, - { "433", handle432 }, + { "001", handleReplyWelcome }, + { "315", handleReplyEndOfWho }, + { "332", handleReplyTopic }, + { "352", handleReplyWho }, + { "366", handleReplyEndOfNames }, + { "372", handleReplyMOTD }, + { "375", handleReplyMOTD }, + { "432", handleReplyErroneousNickname }, + { "433", handleReplyErroneousNickname }, { "JOIN", handleJoin }, { "KICK", handleKick }, { "NICK", handleNick }, diff --git a/input.c b/input.c index f4e3106..cb7575f 100644 --- a/input.c +++ b/input.c @@ -24,7 +24,6 @@ #include "chat.h" static void privmsg(struct Tag tag, bool action, const char *mesg) { - if (tag.id == TAG_DEFAULT.id) return; char *line; int send; asprintf( @@ -50,7 +49,7 @@ static void inputNick(struct Tag tag, char *params) { if (nick) { ircFmt("NICK %s\r\n", nick); } else { - uiLog(TAG_DEFAULT, L"/nick requires a name"); + uiLog(TAG_STATUS, L"/nick requires a name"); } } @@ -60,7 +59,7 @@ static void inputJoin(struct Tag tag, char *params) { if (chan) { ircFmt("JOIN %s\r\n", chan); } else { - uiLog(TAG_DEFAULT, L"/join requires a channel"); + uiLog(TAG_STATUS, L"/join requires a channel"); } } @@ -104,9 +103,12 @@ static void inputOpen(struct Tag tag, char *params) { static void inputView(struct Tag tag, char *params) { char *view = strsep(¶ms, " "); if (!view) return; - size_t num = strtoul(view, &view, 0); - tag = (view[0] ? tagName(view) : tagNum(num)); - if (tag.name) uiFocus(tag); + int num = strtol(view, &view, 0); + if (view[0]) { + uiViewTag(tagFor(view)); + } else { + uiViewNum(num); + } } static const struct { @@ -128,7 +130,11 @@ static const size_t COMMANDS_LEN = sizeof(COMMANDS) / sizeof(COMMANDS[0]); void input(struct Tag tag, char *input) { if (input[0] != '/') { - privmsg(tag, false, input); + if (tag.id == TAG_VERBOSE.id) { + ircFmt("%s\r\n", input); + } else if (tag.id != TAG_STATUS.id) { + privmsg(tag, false, input); + } return; } char *command = strsep(&input, " "); @@ -138,11 +144,11 @@ void input(struct Tag tag, char *input) { COMMANDS[i].handler(tag, input); return; } - uiFmt(TAG_DEFAULT, "%s isn't a recognized command", command); + uiFmt(TAG_STATUS, "%s isn't a recognized command", command); } void inputTab(void) { for (size_t i = 0; i < COMMANDS_LEN; ++i) { - tabTouch(TAG_DEFAULT, COMMANDS[i].command); + tabTouch(TAG_NONE, COMMANDS[i].command); } } diff --git a/irc.c b/irc.c index 579f23b..0a131b2 100644 --- a/irc.c +++ b/irc.c @@ -106,15 +106,15 @@ void ircFmt(const char *format, ...) { int len = vasprintf(&buf, format, ap); va_end(ap); if (!buf) err(EX_OSERR, "vasprintf"); - if (self.verbose) uiFmt(tagFor("(irc)"), "\00314<<<\3 %.*s", len - 2, buf); + if (self.verbose) uiFmt(TAG_VERBOSE, "\3%d<<<\3 %.*s", IRC_WHITE, len - 2, buf); ircWrite(buf, len); free(buf); } -static char buf[4096]; -static size_t len; - void ircRead(void) { + static char buf[4096]; + static size_t len; + ssize_t read = tls_read(client, &buf[len], sizeof(buf) - len); if (read < 0) errx(EX_IOERR, "tls_read: %s", tls_error(client)); if (!read) { @@ -126,7 +126,7 @@ void ircRead(void) { char *crlf, *line = buf; while ((crlf = strnstr(line, "\r\n", &buf[len] - line))) { crlf[0] = '\0'; - if (self.verbose) uiFmt(tagFor("(irc)"), "\00314>>>\3 %s", line); + if (self.verbose) uiFmt(TAG_VERBOSE, "\3%d>>>\3 %s", IRC_GRAY, line); handle(line); line = &crlf[2]; } diff --git a/tab.c b/tab.c index c0a96a4..87e4f02 100644 --- a/tab.c +++ b/tab.c @@ -22,7 +22,7 @@ #include "chat.h" static struct Entry { - size_t tag; + struct Tag tag; char *word; struct Entry *prev; struct Entry *next; @@ -49,7 +49,7 @@ static void touch(struct Entry *entry) { void tabTouch(struct Tag tag, const char *word) { for (struct Entry *entry = head; entry; entry = entry->next) { - if (entry->tag != tag.id) continue; + if (entry->tag.id != tag.id) continue; if (strcmp(entry->word, word)) continue; touch(entry); return; @@ -58,30 +58,28 @@ void tabTouch(struct Tag tag, const char *word) { struct Entry *entry = malloc(sizeof(*entry)); if (!entry) err(EX_OSERR, "malloc"); - entry->tag = tag.id; + entry->tag = tag; entry->word = strdup(word); if (!entry->word) err(EX_OSERR, "strdup"); prepend(entry); } -void tabReplace(const char *prev, const char *next) { - for (struct Entry *entry = head; entry; entry = entry->next) { - if (strcmp(entry->word, prev)) continue; - free(entry->word); - entry->word = strdup(next); - if (!entry->word) err(EX_OSERR, "strdup"); - } +void tabReplace(struct Tag tag, const char *prev, const char *next) { + tabTouch(tag, prev); + free(head->word); + head->word = strdup(next); + if (!head->word) err(EX_OSERR, "strdup"); } -static struct Entry *match; +static struct Entry *iter; void tabRemove(struct Tag tag, const char *word) { for (struct Entry *entry = head; entry; entry = entry->next) { - if (tag.id != TAG_ALL.id && entry->tag != tag.id) continue; + if (entry->tag.id != tag.id) continue; if (strcmp(entry->word, word)) continue; + if (iter == entry) iter = entry->prev; unlink(entry); - if (match == entry) match = entry->prev; free(entry->word); free(entry); return; @@ -90,33 +88,44 @@ void tabRemove(struct Tag tag, const char *word) { void tabClear(struct Tag tag) { for (struct Entry *entry = head; entry; entry = entry->next) { - if (entry->tag != tag.id) continue; + if (entry->tag.id != tag.id) continue; + if (iter == entry) iter = entry->prev; unlink(entry); - if (match == entry) match = entry->prev; free(entry->word); free(entry); } } +struct Tag tabTag(const char *word) { + struct Entry *start = (iter ? iter->next : head); + for (struct Entry *entry = start; entry; entry = entry->next) { + if (strcmp(entry->word, word)) continue; + iter = entry; + return entry->tag; + } + iter = NULL; + return TAG_NONE; +} + const char *tabNext(struct Tag tag, const char *prefix) { size_t len = strlen(prefix); - struct Entry *start = (match ? match->next : head); + struct Entry *start = (iter ? iter->next : head); for (struct Entry *entry = start; entry; entry = entry->next) { - if (entry->tag != TAG_DEFAULT.id && entry->tag != tag.id) continue; + if (entry->tag.id != TAG_NONE.id && entry->tag.id != tag.id) continue; if (strncasecmp(entry->word, prefix, len)) continue; - match = entry; + iter = entry; return entry->word; } - if (!match) return NULL; - match = NULL; + if (!iter) return NULL; + iter = NULL; return tabNext(tag, prefix); } void tabAccept(void) { - if (match) touch(match); - match = NULL; + if (iter) touch(iter); + iter = NULL; } void tabReject(void) { - match = NULL; + iter = NULL; } diff --git a/tag.c b/tag.c index 014e84c..397c191 100644 --- a/tag.c +++ b/tag.c @@ -21,57 +21,30 @@ #include "chat.h" -const struct Tag TAG_ALL = { (size_t)-1, NULL }; -const struct Tag TAG_DEFAULT = { 0, "(status)" }; +const struct Tag TAG_NONE = { 0, "" }; +const struct Tag TAG_STATUS = { 1, "(status)" }; +const struct Tag TAG_VERBOSE = { 2, "(irc)" }; static struct { char *name[TAGS_LEN]; size_t len; - size_t gap; } tags = { - .name = { "(status)" }, - .len = 1, - .gap = 1, + .name = { "", "(status)", "(irc)" }, + .len = 3, }; static struct Tag Tag(size_t id) { return (struct Tag) { id, tags.name[id] }; } -struct Tag tagName(const char *name) { +struct Tag tagFor(const char *name) { for (size_t id = 0; id < tags.len; ++id) { - if (!tags.name[id] || strcmp(tags.name[id], name)) continue; + if (strcmp(tags.name[id], name)) continue; return Tag(id); } - return TAG_ALL; -} - -struct Tag tagNum(size_t num) { - if (num < tags.gap) return Tag(num); - num -= tags.gap; - for (size_t id = tags.gap; id < tags.len; ++id) { - if (!tags.name[id]) continue; - if (!num--) return Tag(id); - } - return TAG_ALL; -} - -struct Tag tagFor(const char *name) { - struct Tag tag = tagName(name); - if (tag.name) return tag; - - size_t id = tags.gap; + if (tags.len == TAGS_LEN) return TAG_STATUS; + size_t id = tags.len++; tags.name[id] = strdup(name); if (!tags.name[id]) err(EX_OSERR, "strdup"); - - if (tags.gap == tags.len) { - tags.gap++; - tags.len++; - } else { - for (tags.gap++; tags.gap < tags.len; ++tags.gap) { - if (!tags.name[tags.gap]) break; - } - } - return Tag(id); } diff --git a/ui.c b/ui.c index 844e777..748235c 100644 --- a/ui.c +++ b/ui.c @@ -21,13 +21,16 @@ #include #include #include -#include #include #include #include #include #include +#ifndef A_ITALIC +#define A_ITALIC A_NORMAL +#endif + #include "chat.h" #undef uiFmt @@ -35,20 +38,6 @@ #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define CTRL(c) ((c) & 037) -#define UNCTRL(c) ((c) + '@') - -#ifndef A_ITALIC -#define A_ITALIC A_NORMAL -#endif - -static void focusEnable(void) { - printf("\33[?1004h"); - fflush(stdout); -} -static void focusDisable(void) { - printf("\33[?1004l"); - fflush(stdout); -} static void colorInit(void) { start_color(); @@ -81,52 +70,83 @@ static short pair8(short pair) { return (pair & 0x70) >> 1 | (pair & 0x07); } -static const int TOPIC_COLS = 512; -static const int INPUT_COLS = 512; -static const int LOG_LINES = 100; +static const int LOG_LINES = 256; +static const int TOPIC_COLS = 512; +static const int INPUT_COLS = 512; +static struct View { + struct Tag tag; + WINDOW *topic; + WINDOW *log; + int scroll; + bool mark; + struct View *prev; + struct View *next; +} *viewHead, *viewTail; + +static void viewAppend(struct View *view) { + if (viewTail) viewTail->next = view; + view->prev = viewTail; + view->next = NULL; + viewTail = view; + if (!viewHead) viewHead = view; +} + +static int logHeight(const struct View *view) { + return LINES - (view->topic ? 2 : 0) - 2; +} +static int lastLogLine(void) { + return LOG_LINES - 1; +} static int lastLine(void) { return LINES - 1; } static int lastCol(void) { return COLS - 1; } -static int logHeight(void) { - return LINES - 4; -} - -struct View { - WINDOW *topic; - WINDOW *log; - int scroll; - bool mark; -}; static struct { bool hide; WINDOW *input; - struct Tag tag; - struct View views[TAGS_LEN]; - size_t len; + struct View *view; + struct View *tags[TAGS_LEN]; } ui; +static struct View *viewTag(struct Tag tag) { + struct View *view = ui.tags[tag.id]; + if (view) return view; + + view = calloc(1, sizeof(*view)); + if (!view) err(EX_OSERR, "calloc"); + + view->tag = tag; + view->log = newpad(LOG_LINES, COLS); + wsetscrreg(view->log, 0, lastLogLine()); + scrollok(view->log, true); + wmove(view->log, lastLogLine() - logHeight(view), 0); + view->scroll = LOG_LINES; + + viewAppend(view); + ui.tags[tag.id] = view; + return view; +} + void uiInit(void) { setlocale(LC_CTYPE, ""); initscr(); cbreak(); noecho(); - focusEnable(); colorInit(); - - ui.tag = TAG_DEFAULT; + termMode(TERM_FOCUS, true); ui.input = newpad(2, INPUT_COLS); mvwhline(ui.input, 0, 0, ACS_HLINE, INPUT_COLS); wmove(ui.input, 1, 0); - keypad(ui.input, true); nodelay(ui.input, true); + + ui.view = viewTag(TAG_STATUS); } void uiHide(void) { @@ -136,54 +156,34 @@ void uiHide(void) { void uiExit(void) { uiHide(); - focusDisable(); + termMode(TERM_FOCUS, false); printf( "This program is AGPLv3 free software!\n" "The source is available at <" SOURCE_URL ">.\n" ); } -static struct View *uiView(struct Tag tag) { - struct View *view = &ui.views[tag.id]; - if (view->log) return view; - - view->topic = newpad(2, TOPIC_COLS); - mvwhline(view->topic, 1, 0, ACS_HLINE, TOPIC_COLS); - - view->log = newpad(LOG_LINES, COLS); - wsetscrreg(view->log, 0, LOG_LINES - 1); - scrollok(view->log, true); - wmove(view->log, LOG_LINES - logHeight() - 1, 0); - - view->scroll = LOG_LINES; - view->mark = false; - - if (tag.id >= ui.len) ui.len = tag.id + 1; - return view; -} - static void uiResize(void) { - for (size_t i = 0; i < ui.len; ++i) { - struct View *view = &ui.views[i]; - if (!view->log) continue; + for (struct View *view = viewHead; view; view = view->next) { wresize(view->log, LOG_LINES, COLS); - wmove(view->log, LOG_LINES - 1, COLS - 1); + wmove(view->log, lastLogLine(), lastCol()); } } void uiDraw(void) { if (ui.hide) return; - struct View *view = uiView(ui.tag); + if (ui.view->topic) { + pnoutrefresh( + ui.view->topic, + 0, 0, + 0, 0, + 1, lastCol() + ); + } pnoutrefresh( - view->topic, - 0, 0, - 0, 0, - 1, lastCol() - ); - pnoutrefresh( - view->log, - view->scroll - logHeight(), 0, - 2, 0, + ui.view->log, + ui.view->scroll - logHeight(ui.view), 0, + (ui.view->topic ? 2 : 0), 0, lastLine() - 2, lastCol() ); int _, x; @@ -201,37 +201,51 @@ static void uiRedraw(void) { clearok(curscr, true); } -void uiFocus(struct Tag tag) { - struct View *view = uiView(ui.tag); - view->mark = true; - view = uiView(tag); - view->mark = false; - touchwin(view->topic); +static void uiView(struct View *view) { + if (view->topic) touchwin(view->topic); touchwin(view->log); - ui.tag = tag; + view->mark = false; + ui.view->mark = true; + ui.view = view; } -void uiBeep(void) { - beep(); // always be beeping +void uiViewTag(struct Tag tag) { + uiView(viewTag(tag)); } -static const short IRC_COLORS[16] = { - 8 + COLOR_WHITE, // white - 0 + COLOR_BLACK, // black - 0 + COLOR_BLUE, // blue - 0 + COLOR_GREEN, // green - 8 + COLOR_RED, // red - 0 + COLOR_RED, // brown - 0 + COLOR_MAGENTA, // magenta - 0 + COLOR_YELLOW, // orange - 8 + COLOR_YELLOW, // yellow - 8 + COLOR_GREEN, // light green - 0 + COLOR_CYAN, // cyan - 8 + COLOR_CYAN, // light cyan - 8 + COLOR_BLUE, // light blue - 8 + COLOR_MAGENTA, // pink - 8 + COLOR_BLACK, // gray - 0 + COLOR_WHITE, // light gray +void uiViewNum(int num) { + if (num < 0) { + for (struct View *view = viewTail; view; view = view->prev) { + if (++num) continue; + uiView(view); + break; + } + } else { + for (struct View *view = viewHead; view; view = view->next) { + if (num--) continue; + uiView(view); + break; + } + } +} + +static const short IRC_COLORS[] = { + [IRC_WHITE] = 8 + COLOR_WHITE, + [IRC_BLACK] = 0 + COLOR_BLACK, + [IRC_BLUE] = 0 + COLOR_BLUE, + [IRC_GREEN] = 0 + COLOR_GREEN, + [IRC_RED] = 8 + COLOR_RED, + [IRC_BROWN] = 0 + COLOR_RED, + [IRC_MAGENTA] = 0 + COLOR_MAGENTA, + [IRC_ORANGE] = 0 + COLOR_YELLOW, + [IRC_YELLOW] = 8 + COLOR_YELLOW, + [IRC_LIGHT_GREEN] = 8 + COLOR_GREEN, + [IRC_CYAN] = 0 + COLOR_CYAN, + [IRC_LIGHT_CYAN] = 8 + COLOR_CYAN, + [IRC_LIGHT_BLUE] = 8 + COLOR_BLUE, + [IRC_PINK] = 8 + COLOR_MAGENTA, + [IRC_GRAY] = 8 + COLOR_BLACK, + [IRC_LIGHT_GRAY] = 0 + COLOR_WHITE, }; static const wchar_t *parseColor(short *pair, const wchar_t *str) { @@ -310,9 +324,13 @@ static void addIRC(WINDOW *win, const wchar_t *str) { } void uiTopic(struct Tag tag, const char *topic) { + struct View *view = viewTag(tag); + if (!view->topic) { + view->topic = newpad(2, TOPIC_COLS); + mvwhline(view->topic, 1, 0, ACS_HLINE, TOPIC_COLS); + } wchar_t *wcs = ambstowcs(topic); if (!wcs) err(EX_DATAERR, "ambstowcs"); - struct View *view = uiView(tag); wmove(view->topic, 0, 0); addIRC(view->topic, wcs); wclrtoeol(view->topic); @@ -320,7 +338,7 @@ void uiTopic(struct Tag tag, const char *topic) { } void uiLog(struct Tag tag, const wchar_t *line) { - struct View *view = uiView(tag); + struct View *view = viewTag(tag); waddch(view->log, '\n'); if (view->mark) { waddch(view->log, '\n'); @@ -330,93 +348,89 @@ void uiLog(struct Tag tag, const wchar_t *line) { } void uiFmt(struct Tag tag, const wchar_t *format, ...) { - wchar_t *buf; + wchar_t *wcs; va_list ap; va_start(ap, format); - vaswprintf(&buf, format, ap); + vaswprintf(&wcs, format, ap); va_end(ap); - if (!buf) err(EX_OSERR, "vaswprintf"); - uiLog(tag, buf); - free(buf); + if (!wcs) err(EX_OSERR, "vaswprintf"); + uiLog(tag, wcs); + free(wcs); } -static void logUp(void) { - struct View *view = uiView(ui.tag); - if (view->scroll == logHeight()) return; - if (view->scroll == LOG_LINES) view->mark = true; - view->scroll = MAX(view->scroll - logHeight() / 2, logHeight()); +static void logPageUp(void) { + int height = logHeight(ui.view); + if (ui.view->scroll == height) return; + if (ui.view->scroll == LOG_LINES) ui.view->mark = true; + ui.view->scroll = MAX(ui.view->scroll - height / 2, height); } -static void logDown(void) { - struct View *view = uiView(ui.tag); - if (view->scroll == LOG_LINES) return; - view->scroll = MIN(view->scroll + logHeight() / 2, LOG_LINES); - if (view->scroll == LOG_LINES) view->mark = false; +static void logPageDown(void) { + if (ui.view->scroll == LOG_LINES) return; + ui.view->scroll = MIN(ui.view->scroll + logHeight(ui.view) / 2, LOG_LINES); + if (ui.view->scroll == LOG_LINES) ui.view->mark = false; } static bool keyChar(wchar_t ch) { - static bool esc, csi; - if (ch == L'\33') { - esc = true; - return false; + if (iswascii(ch)) { + enum TermEvent event = termEvent((char)ch); + switch (event) { + break; case TERM_FOCUS_IN: ui.view->mark = false; + break; case TERM_FOCUS_OUT: ui.view->mark = true; + break; default: {} + } + if (event) return false; } - if (esc && ch == L'[') { - esc = false; - csi = true; - return false; - } - if (csi) { - if (ch == L'O') uiView(ui.tag)->mark = true; - if (ch == L'I') uiView(ui.tag)->mark = false; - csi = false; - return false; - } - if (ch == L'\177') ch = L'\b'; - bool update = true; - if (esc) { + static bool meta; + if (ch == L'\33') { + meta = true; + return false; + } + + if (meta) { + bool update = true; switch (ch) { - break; case L'b': edit(ui.tag, EDIT_BACK_WORD, 0); - break; case L'f': edit(ui.tag, EDIT_FORE_WORD, 0); - break; case L'\b': edit(ui.tag, EDIT_KILL_BACK_WORD, 0); - break; case L'd': edit(ui.tag, EDIT_KILL_FORE_WORD, 0); + break; case L'b': edit(ui.view->tag, EDIT_BACK_WORD, 0); + break; case L'f': edit(ui.view->tag, EDIT_FORE_WORD, 0); + break; case L'\b': edit(ui.view->tag, EDIT_KILL_BACK_WORD, 0); + break; case L'd': edit(ui.view->tag, EDIT_KILL_FORE_WORD, 0); break; default: { update = false; - if (ch >= L'0' && ch <= L'9') { - struct Tag tag = tagNum(ch - L'0'); - if (tag.name) uiFocus(tag); - } + if (ch < L'0' || ch > L'9') break; + uiViewNum(ch - L'0'); } } - esc = false; + meta = false; return update; } + if (ch == L'\177') ch = L'\b'; switch (ch) { break; case CTRL(L'L'): uiRedraw(); return false; - break; case CTRL(L'A'): edit(ui.tag, EDIT_HOME, 0); - break; case CTRL(L'B'): edit(ui.tag, EDIT_LEFT, 0); - break; case CTRL(L'D'): edit(ui.tag, EDIT_DELETE, 0); - break; case CTRL(L'E'): edit(ui.tag, EDIT_END, 0); - break; case CTRL(L'F'): edit(ui.tag, EDIT_RIGHT, 0); - break; case CTRL(L'K'): edit(ui.tag, EDIT_KILL_LINE, 0); - break; case CTRL(L'W'): edit(ui.tag, EDIT_KILL_BACK_WORD, 0); + break; case CTRL(L'A'): edit(ui.view->tag, EDIT_HOME, 0); + break; case CTRL(L'B'): edit(ui.view->tag, EDIT_LEFT, 0); + break; case CTRL(L'D'): edit(ui.view->tag, EDIT_DELETE, 0); + break; case CTRL(L'E'): edit(ui.view->tag, EDIT_END, 0); + break; case CTRL(L'F'): edit(ui.view->tag, EDIT_RIGHT, 0); + break; case CTRL(L'K'): edit(ui.view->tag, EDIT_KILL_LINE, 0); + break; case CTRL(L'W'): edit(ui.view->tag, EDIT_KILL_BACK_WORD, 0); - break; case CTRL(L'C'): edit(ui.tag, EDIT_INSERT, IRC_COLOR); - break; case CTRL(L'N'): edit(ui.tag, EDIT_INSERT, IRC_RESET); - break; case CTRL(L'O'): edit(ui.tag, EDIT_INSERT, IRC_BOLD); - break; case CTRL(L'R'): edit(ui.tag, EDIT_INSERT, IRC_COLOR); - break; case CTRL(L'T'): edit(ui.tag, EDIT_INSERT, IRC_ITALIC); - break; case CTRL(L'U'): edit(ui.tag, EDIT_INSERT, IRC_UNDERLINE); - break; case CTRL(L'V'): edit(ui.tag, EDIT_INSERT, IRC_REVERSE); + break; case CTRL(L'C'): edit(ui.view->tag, EDIT_INSERT, IRC_COLOR); + break; case CTRL(L'N'): edit(ui.view->tag, EDIT_INSERT, IRC_RESET); + break; case CTRL(L'O'): edit(ui.view->tag, EDIT_INSERT, IRC_BOLD); + break; case CTRL(L'R'): edit(ui.view->tag, EDIT_INSERT, IRC_COLOR); + break; case CTRL(L'T'): edit(ui.view->tag, EDIT_INSERT, IRC_ITALIC); + break; case CTRL(L'U'): edit(ui.view->tag, EDIT_INSERT, IRC_UNDERLINE); + break; case CTRL(L'V'): edit(ui.view->tag, EDIT_INSERT, IRC_REVERSE); - break; case L'\b': edit(ui.tag, EDIT_BACKSPACE, 0); - break; case L'\t': edit(ui.tag, EDIT_COMPLETE, 0); - break; case L'\n': edit(ui.tag, EDIT_ENTER, 0); + break; case L'\b': edit(ui.view->tag, EDIT_BACKSPACE, 0); + break; case L'\t': edit(ui.view->tag, EDIT_COMPLETE, 0); + break; case L'\n': edit(ui.view->tag, EDIT_ENTER, 0); break; default: { if (!iswprint(ch)) return false; - edit(ui.tag, EDIT_INSERT, ch); + edit(ui.view->tag, EDIT_INSERT, ch); } } return true; @@ -425,15 +439,15 @@ static bool keyChar(wchar_t ch) { static bool keyCode(wchar_t ch) { switch (ch) { break; case KEY_RESIZE: uiResize(); return false; - break; case KEY_PPAGE: logUp(); return false; - break; case KEY_NPAGE: logDown(); return false; - break; case KEY_LEFT: edit(ui.tag, EDIT_LEFT, ch); - break; case KEY_RIGHT: edit(ui.tag, EDIT_RIGHT, ch); - break; case KEY_HOME: edit(ui.tag, EDIT_HOME, ch); - break; case KEY_END: edit(ui.tag, EDIT_END, ch); - break; case KEY_DC: edit(ui.tag, EDIT_DELETE, ch); - break; case KEY_BACKSPACE: edit(ui.tag, EDIT_BACKSPACE, ch); - break; case KEY_ENTER: edit(ui.tag, EDIT_ENTER, ch); + break; case KEY_PPAGE: logPageUp(); return false; + break; case KEY_NPAGE: logPageDown(); return false; + break; case KEY_LEFT: edit(ui.view->tag, EDIT_LEFT, 0); + break; case KEY_RIGHT: edit(ui.view->tag, EDIT_RIGHT, 0); + break; case KEY_HOME: edit(ui.view->tag, EDIT_HOME, 0); + break; case KEY_END: edit(ui.view->tag, EDIT_END, 0); + break; case KEY_DC: edit(ui.view->tag, EDIT_DELETE, 0); + break; case KEY_BACKSPACE: edit(ui.view->tag, EDIT_BACKSPACE, 0); + break; case KEY_ENTER: edit(ui.view->tag, EDIT_ENTER, 0); } return true; } @@ -453,14 +467,11 @@ void uiRead(void) { } if (!update) return; + int y, x; wmove(ui.input, 1, 0); addIRC(ui.input, editHead()); - - int y, x; getyx(ui.input, y, x); - addIRC(ui.input, editTail()); - wclrtoeol(ui.input); wmove(ui.input, y, x); }