diff options
| -rw-r--r-- | chat.c | 10 | ||||
| -rw-r--r-- | chat.h | 17 | ||||
| -rw-r--r-- | handle.c | 127 | ||||
| -rw-r--r-- | input.c | 6 | ||||
| -rw-r--r-- | irc.c | 14 | ||||
| -rw-r--r-- | ui.c | 38 | 
6 files changed, 151 insertions, 61 deletions
| @@ -60,7 +60,7 @@ static union {  void spawn(char *const argv[]) {  	if (fds.pipe.events) { -		uiLog(TAG_STATUS, L"spawn: existing pipe"); +		uiLog(TAG_STATUS, UI_WARM, L"spawn: existing pipe");  		return;  	} @@ -93,7 +93,7 @@ static void pipeRead(void) {  	if (len) {  		buf[len] = '\0';  		len = strcspn(buf, "\n"); -		uiFmt(TAG_STATUS, "spawn: %.*s", (int)len, buf); +		uiFmt(TAG_STATUS, UI_WARM, "spawn: %.*s", (int)len, buf);  	} else {  		close(fds.pipe.fd);  		fds.pipe.events = 0; @@ -124,9 +124,9 @@ static void sigchld(int sig) {  	pid_t pid = wait(&status);  	if (pid < 0) err(EX_OSERR, "wait");  	if (WIFEXITED(status) && WEXITSTATUS(status)) { -		uiFmt(TAG_STATUS, "spawn: exit %d", WEXITSTATUS(status)); +		uiFmt(TAG_STATUS, UI_WARM, "spawn: exit %d", WEXITSTATUS(status));  	} else if (WIFSIGNALED(status)) { -		uiFmt(TAG_STATUS, "spawn: signal %d", WTERMSIG(status)); +		uiFmt(TAG_STATUS, UI_WARM, "spawn: signal %d", WTERMSIG(status));  	}  } @@ -182,7 +182,7 @@ int main(int argc, char *argv[]) {  	inputTab();  	uiInit(); -	uiLog(TAG_STATUS, L"Traveling..."); +	uiLog(TAG_STATUS, UI_WARM, L"Traveling...");  	uiDraw();  	fds.irc.fd = ircConnect(host, port, pass, webirc); @@ -91,12 +91,19 @@ void uiHide(void);  void uiExit(void);  void uiDraw(void);  void uiRead(void); +  void uiViewTag(struct Tag tag);  void uiViewNum(int num);  void uiCloseTag(struct Tag tag); + +enum UIHeat { +	UI_COLD, +	UI_WARM, +	UI_HOT, +};  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, ...); +void uiLog(struct Tag tag, enum UIHeat heat, const wchar_t *line); +void uiFmt(struct Tag tag, enum UIHeat heat, const wchar_t *format, ...);  enum TermMode {  	TERM_FOCUS, @@ -155,10 +162,10 @@ int vaswprintf(wchar_t **ret, const wchar_t *format, va_list ap);  // HACK: clang won't check wchar_t *format strings.  #ifdef NDEBUG -#define uiFmt(tag, format, ...) uiFmt(tag, L##format, __VA_ARGS__) +#define uiFmt(tag, heat, format, ...) uiFmt(tag, heat, L##format, __VA_ARGS__)  #else -#define uiFmt(tag, format, ...) do { \ +#define uiFmt(tag, heat, format, ...) do { \  	snprintf(NULL, 0, format, __VA_ARGS__); \ -	uiFmt(tag, L##format, __VA_ARGS__); \ +	uiFmt(tag, heat, L##format, __VA_ARGS__); \  } while(0)  #endif @@ -93,8 +93,7 @@ static bool isSelf(const char *nick, const char *user) {  	return false;  } -static bool isPing(const char *nick, const char *user, const char *mesg) { -	if (isSelf(nick, user)) return false; +static bool isPing(const char *mesg) {  	size_t len = strlen(self.nick);  	const char *match = mesg;  	while (NULL != (match = strcasestr(match, self.nick))) { @@ -108,6 +107,13 @@ static bool isPing(const char *nick, const char *user, const char *mesg) {  	return false;  } +static char *dequote(char *mesg) { +	if (mesg[0] == '"') mesg = &mesg[1]; +	size_t len = strlen(mesg); +	if (mesg[len - 1] == '"') mesg[len - 1] = '\0'; +	return mesg; +} +  typedef void (*Handler)(char *prefix, char *params);  static void handlePing(char *prefix, char *params) { @@ -118,39 +124,46 @@ static void handlePing(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_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"); +	// FIXME: Better formatting. +	uiLog(TAG_STATUS, UI_HOT, L"You can't use that name here"); +	uiFmt(TAG_STATUS, UI_HOT, "Sheriff says, \"%s\"", mesg); +	uiLog(TAG_STATUS, UI_HOT, L"Type /nick <name> to choose a new one");  }  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_STATUS, self.nick);  	if (self.join) ircFmt("JOIN %s\r\n", self.join); -	uiLog(TAG_STATUS, L"You have arrived"); +	tabTouch(TAG_STATUS, self.nick); + +	uiLog(TAG_STATUS, UI_WARM, L"You have arrived");  }  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]; +  	urlScan(TAG_STATUS, mesg); -	uiFmt(TAG_STATUS, "%s", mesg); +	uiFmt(TAG_STATUS, UI_COLD, "%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_NONE, chan);  		uiViewTag(tag);  	}  	tabTouch(tag, nick); +  	uiFmt( -		tag, "\3%d%s\3 arrives in \3%d%s\3", +		tag, UI_COLD, +		"\3%d%s\3 arrives in \3%d%s\3",  		color(user), nick, color(chan), chan  	);  } @@ -159,16 +172,24 @@ static void handlePart(char *prefix, char *params) {  	char *nick, *user, *chan, *mesg;  	shift(prefix, &nick, &user, NULL, params, 1, 1, &chan, &mesg);  	struct Tag tag = tagFor(chan); -	(void)(isSelf(nick, user) ? tabClear(tag) : tabRemove(tag, nick)); + +	if (isSelf(nick, user)) { +		tabClear(tag); +	} else { +		tabRemove(tag, nick); +	} +  	if (mesg) {  		urlScan(tag, mesg);  		uiFmt( -			tag, "\3%d%s\3 leaves \3%d%s\3, \"%s\"", +			tag, UI_COLD, +			"\3%d%s\3 leaves \3%d%s\3, \"%s\"",  			color(user), nick, color(chan), chan, mesg  		);  	} else {  		uiFmt( -			tag, "\3%d%s\3 leaves \3%d%s\3", +			tag, UI_COLD, +			"\3%d%s\3 leaves \3%d%s\3",  			color(user), nick, color(chan), chan  		);  	} @@ -178,16 +199,26 @@ static void handleKick(char *prefix, char *params) {  	char *nick, *user, *chan, *kick, *mesg;  	shift(prefix, &nick, &user, NULL, params, 2, 1, &chan, &kick, &mesg);  	struct Tag tag = tagFor(chan); -	(void)(isSelf(nick, user) ? tabClear(tag) : tabRemove(tag, nick)); +	bool kicked = !strcmp(kick, self.nick); + +	if (kicked) { +		tabClear(tag); +	} else { +		tabRemove(tag, kick); +	} +  	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 +			tag, (kicked ? UI_HOT : UI_COLD), +			"\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, +			dequote(mesg)  		);  	} else {  		uiFmt( -			tag, "\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3", +			tag, (kicked ? UI_HOT : UI_COLD), +			"\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3",  			color(user), nick, color(kick), kick, color(chan), chan  		);  	} @@ -196,18 +227,20 @@ 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); -	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 +				tag, UI_COLD, +				"\3%d%s\3 leaves, \"%s\"", +				color(user), nick, dequote(mesg)  			);  		} else { -			uiFmt(tag, "\3%d%s\3 leaves", color(user), nick); +			uiFmt(tag, UI_COLD, "\3%d%s\3 leaves", color(user), nick);  		}  	}  } @@ -216,10 +249,12 @@ 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); +  	urlScan(tag, topic);  	uiTopic(tag, topic);  	uiFmt( -		tag, "The sign in \3%d%s\3 reads, \"%s\"", +		tag, UI_COLD, +		"The sign in \3%d%s\3 reads, \"%s\"",  		color(chan), chan, topic  	);  } @@ -228,11 +263,14 @@ static void handleTopic(char *prefix, char *params) {  	char *nick, *user, *chan, *topic;  	shift(prefix, &nick, &user, NULL, params, 2, 0, &chan, &topic);  	struct Tag tag = tagFor(chan); +  	if (!isSelf(nick, user)) tabTouch(tag, nick); +  	urlScan(tag, topic);  	uiTopic(tag, topic);  	uiFmt( -		tag, "\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"", +		tag, UI_COLD, +		"\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"",  		color(user), nick, color(chan), chan, topic  	);  } @@ -256,7 +294,10 @@ static void handleReplyWho(char *prefix, char *params) {  		params, 6, 0, NULL, &chan, &user, NULL, NULL, &nick  	);  	struct Tag tag = tagFor(chan); + +	// FIXME: Don't clobber order if they already exist.  	tabTouch(tag, nick); +  	size_t cap = sizeof(who.buf) - who.len;  	int len = snprintf(  		&who.buf[who.len], cap, @@ -270,8 +311,10 @@ static void handleReplyEndOfWho(char *prefix, char *params) {  	char *chan;  	shift(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &chan);  	struct Tag tag = tagFor(chan); +  	uiFmt( -		tag, "In \3%d%s\3 are %s", +		tag, UI_COLD, +		"In \3%d%s\3 are %s",  		color(chan), chan, who.buf  	);  	who.len = 0; @@ -280,12 +323,16 @@ static void handleReplyEndOfWho(char *prefix, char *params) {  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); +  	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", +			tag, UI_COLD, +			"\3%d%s\3 is now known as \3%d%s\3",  			color(user), prev, color(user), next  		);  	} @@ -296,11 +343,15 @@ static void handleCTCP(struct Tag tag, char *nick, char *user, char *mesg) {  	char *ctcp = strsep(&mesg, " ");  	char *params = strsep(&mesg, "\1");  	if (strcmp(ctcp, "ACTION")) return; -	if (!isSelf(nick, user)) tabTouch(tag, nick); + +	bool self = isSelf(nick, user); +	if (!self) tabTouch(tag, nick); +  	urlScan(tag, params); -	bool ping = isPing(nick, user, params); +	bool ping = !self && isPing(params);  	uiFmt( -		tag, "%c\3%d* %s\17 %s", +		tag, (ping ? UI_HOT : UI_WARM), +		"%c\3%d* %s\17 %s",  		ping["\17\26"], color(user), nick, params  	);  } @@ -313,12 +364,15 @@ static void handlePrivmsg(char *prefix, char *params) {  		handleCTCP(tag, nick, user, mesg);  		return;  	} -	if (!isSelf(nick, user)) tabTouch(tag, nick); -	urlScan(tag, mesg); +  	bool self = isSelf(nick, user); -	bool ping = isPing(nick, user, mesg); +	if (!self) tabTouch(tag, nick); + +	urlScan(tag, mesg); +	bool ping = !self && isPing(mesg);  	uiFmt( -		tag, "%c\3%d%c%s%c\17 %s", +		tag, (ping ? UI_HOT : UI_WARM), +		"%c\3%d%c%s%c\17 %s",  		ping["\17\26"], color(user), self["<("], nick, self[">)"], mesg  	);  } @@ -328,11 +382,16 @@ static void handleNotice(char *prefix, char *params) {  	shift(prefix, &nick, &user, NULL, params, 2, 0, &chan, &mesg);  	struct Tag tag = TAG_STATUS;  	if (user) tag = (strcmp(chan, self.nick) ? tagFor(chan) : tagFor(nick)); -	if (!isSelf(nick, user)) tabTouch(tag, nick); + +	bool self = isSelf(nick, user); +	if (!self) tabTouch(tag, nick); +  	urlScan(tag, mesg); +	bool ping = !self && isPing(mesg);  	uiFmt( -		tag, "\3%d-%s-\3 %s", -		color(user), nick, mesg +		tag, (ping ? UI_HOT : UI_WARM), +		"%c\3%d-%s-\17 %s", +		ping["\17\26"], color(user), nick, mesg  	);  } @@ -40,7 +40,7 @@ static void privmsg(struct Tag tag, bool action, const char *mesg) {  static char *param(const char *command, char **params, const char *name) {  	char *param = strsep(params, " ");  	if (param) return param; -	uiFmt(TAG_STATUS, "%s requires a %s", command, name); +	uiFmt(TAG_STATUS, UI_WARM, "%s requires a %s", command, name);  	return NULL;  } @@ -123,7 +123,7 @@ static void inputView(struct Tag tag, char *params) {  		if (tag.id != TAG_NONE.id) {  			uiViewTag(tag);  		} else { -			uiFmt(TAG_STATUS, "No view for %s", view); +			uiFmt(TAG_STATUS, UI_WARM, "No view for %s", view);  		}  	}  } @@ -178,7 +178,7 @@ void input(struct Tag tag, char *input) {  		COMMANDS[i].handler(tag, input);  		return;  	} -	uiFmt(TAG_STATUS, "%s isn't a recognized command", command); +	uiFmt(TAG_STATUS, UI_WARM, "%s isn't a recognized command", command);  }  void inputTab(void) { @@ -106,7 +106,12 @@ void ircFmt(const char *format, ...) {  	int len = vasprintf(&buf, format, ap);  	va_end(ap);  	if (!buf) err(EX_OSERR, "vasprintf"); -	if (self.verbose) uiFmt(TAG_VERBOSE, "\3%d<<<\3 %.*s", IRC_WHITE, len - 2, buf); +	if (self.verbose) { +		uiFmt( +			TAG_VERBOSE, UI_COLD, +			"\3%d<<<\3 %.*s", IRC_WHITE, len - 2, buf +		); +	}  	ircWrite(buf, len);  	free(buf);  } @@ -126,7 +131,12 @@ void ircRead(void) {  	char *crlf, *line = buf;  	while ((crlf = strnstr(line, "\r\n", &buf[len] - line))) {  		crlf[0] = '\0'; -		if (self.verbose) uiFmt(TAG_VERBOSE, "\3%d>>>\3 %s", IRC_GRAY, line); +		if (self.verbose) { +			uiFmt( +				TAG_VERBOSE, UI_COLD, +				"\3%d>>>\3 %s", IRC_GRAY, line +			); +		}  		handle(line);  		line = &crlf[2];  	} @@ -75,6 +75,8 @@ struct View {  	WINDOW *topic;  	WINDOW *log;  	int scroll; +	int unread; +	bool hot;  	bool mark;  	struct View *prev;  	struct View *next; @@ -145,6 +147,15 @@ static void viewClose(struct View *view) {  	free(view);  } +static void viewMark(struct View *view) { +	view->mark = true; +} +static void viewUnmark(struct View *view) { +	view->unread = 0; +	view->hot = false; +	view->mark = false; +} +  static struct {  	bool hide;  	struct View *view; @@ -233,8 +244,8 @@ static void uiView(struct View *view) {  	termTitle(view->tag.name);  	if (view->topic) touchwin(view->topic);  	touchwin(view->log); -	ui.view->mark = true; -	view->mark = false; +	viewMark(ui.view); +	viewUnmark(view);  	ui.view = view;  } @@ -383,37 +394,40 @@ void uiTopic(struct Tag tag, const char *topic) {  	free(wcs);  } -void uiLog(struct Tag tag, const wchar_t *line) { +void uiLog(struct Tag tag, enum UIHeat heat, const wchar_t *line) {  	struct View *view = viewTag(tag);  	waddch(view->log, '\n'); -	if (view->mark) { -		waddch(view->log, '\n'); -		view->mark = false; +	if (view->mark && heat > UI_COLD) { +		if (!view->unread++) waddch(view->log, '\n'); +		if (heat > UI_WARM) { +			view->hot = true; +			beep(); // TODO: Notification. +		}  	}  	addIRC(view->log, line);  } -void uiFmt(struct Tag tag, const wchar_t *format, ...) { +void uiFmt(struct Tag tag, enum UIHeat heat, const wchar_t *format, ...) {  	wchar_t *wcs;  	va_list ap;  	va_start(ap, format);  	vaswprintf(&wcs, format, ap);  	va_end(ap);  	if (!wcs) err(EX_OSERR, "vaswprintf"); -	uiLog(tag, wcs); +	uiLog(tag, heat, wcs);  	free(wcs);  }  static void logScrollUp(int lines) {  	int height = logHeight(ui.view);  	if (ui.view->scroll == height) return; -	if (ui.view->scroll == LOG_LINES) ui.view->mark = true; +	if (ui.view->scroll == LOG_LINES) viewMark(ui.view);  	ui.view->scroll = MAX(ui.view->scroll - lines, height);  }  static void logScrollDown(int lines) {  	if (ui.view->scroll == LOG_LINES) return;  	ui.view->scroll = MIN(ui.view->scroll + lines, LOG_LINES); -	if (ui.view->scroll == LOG_LINES) ui.view->mark = false; +	if (ui.view->scroll == LOG_LINES) viewUnmark(ui.view);  }  static void logPageUp(void) {  	logScrollUp(logHeight(ui.view) / 2); @@ -426,8 +440,8 @@ static bool keyChar(wchar_t ch) {  	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; case TERM_FOCUS_IN:  viewUnmark(ui.view); +			break; case TERM_FOCUS_OUT: viewMark(ui.view);  			break; default: {}  		}  		if (event) return false; | 
