diff options
| -rw-r--r-- | chat.c | 12 | ||||
| -rw-r--r-- | chat.h | 32 | ||||
| -rw-r--r-- | handle.c | 115 | ||||
| -rw-r--r-- | input.c | 24 | ||||
| -rw-r--r-- | irc.c | 10 | ||||
| -rw-r--r-- | tab.c | 55 | ||||
| -rw-r--r-- | tag.c | 45 | ||||
| -rw-r--r-- | ui.c | 351 | 
8 files changed, 334 insertions, 310 deletions
| @@ -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); @@ -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); @@ -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 }, @@ -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);  	}  } @@ -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];  	} @@ -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;  } @@ -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);  } @@ -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);  } | 
