summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chat.c12
-rw-r--r--chat.h32
-rw-r--r--handle.c115
-rw-r--r--input.c24
-rw-r--r--irc.c10
-rw-r--r--tab.c55
-rw-r--r--tag.c45
-rw-r--r--ui.c351
8 files changed, 334 insertions, 310 deletions
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,13 +42,30 @@ 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,
IRC_REVERSE = 026,
@@ -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 <https://github.com/cbreeden/fxhash/blob/master/lib.rs>.
+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 <name> 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 <name> 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(&params, " ");
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 <locale.h>
#include <stdarg.h>
#include <stdbool.h>
-#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <wchar.h>
#include <wctype.h>
+#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);
- pnoutrefresh(
- view->topic,
- 0, 0,
- 0, 0,
- 1, lastCol()
- );
+ if (ui.view->topic) {
+ pnoutrefresh(
+ ui.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 (esc && ch == L'[') {
- esc = false;
- csi = 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 (csi) {
- if (ch == L'O') uiView(ui.tag)->mark = true;
- if (ch == L'I') uiView(ui.tag)->mark = false;
- csi = false;
+
+ static bool meta;
+ if (ch == L'\33') {
+ meta = true;
return false;
}
- if (ch == L'\177') ch = L'\b';
- bool update = true;
- if (esc) {
+ 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'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 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 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.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.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);
}