Become multi-channel
There's a lot of UI missing for it, but it technically works.
This commit is contained in:
		
							parent
							
								
									e9793b4bce
								
							
						
					
					
						commit
						07c750d25c
					
				
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							@ -3,7 +3,7 @@ CFLAGS += -Wall -Wextra -Wpedantic
 | 
				
			|||||||
CFLAGS += -I/usr/local/include -I/usr/local/opt/libressl/include
 | 
					CFLAGS += -I/usr/local/include -I/usr/local/opt/libressl/include
 | 
				
			||||||
LDFLAGS += -L/usr/local/lib -L/usr/local/opt/libressl/lib
 | 
					LDFLAGS += -L/usr/local/lib -L/usr/local/opt/libressl/lib
 | 
				
			||||||
LDLIBS = -lcursesw -ltls
 | 
					LDLIBS = -lcursesw -ltls
 | 
				
			||||||
OBJS = chat.o edit.o handle.o input.o irc.o pls.o tab.o ui.o url.o
 | 
					OBJS = chat.o edit.o handle.o input.o irc.o pls.o tab.o tag.o ui.o url.o
 | 
				
			||||||
 | 
					
 | 
				
			||||||
all: tags chat
 | 
					all: tags chat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										9
									
								
								README
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								README
									
									
									
									
									
								
							@ -3,12 +3,13 @@ Simple IRC client for use over anonymous SSH.
 | 
				
			|||||||
This software requires LibreSSL and targets FreeBSD and Darwin.
 | 
					This software requires LibreSSL and targets FreeBSD and Darwin.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	chat.h      Shared state and function prototypes
 | 
						chat.h      Shared state and function prototypes
 | 
				
			||||||
	chat.c      Command line parsing and poll loop
 | 
						chat.c      Command line parsing and event loop
 | 
				
			||||||
 | 
						tag.c       Tag (channel, query) management
 | 
				
			||||||
 | 
						handle.c    Incoming command handling
 | 
				
			||||||
 | 
						input.c     Input command handling
 | 
				
			||||||
 | 
						irc.c       TLS client connection
 | 
				
			||||||
	ui.c        Curses UI and mIRC formatting
 | 
						ui.c        Curses UI and mIRC formatting
 | 
				
			||||||
	edit.c      Line editing
 | 
						edit.c      Line editing
 | 
				
			||||||
	irc.c       TLS client connection
 | 
					 | 
				
			||||||
	input.c     Input command handling
 | 
					 | 
				
			||||||
	handle.c    Incoming command handling
 | 
					 | 
				
			||||||
	tab.c       Tab-complete
 | 
						tab.c       Tab-complete
 | 
				
			||||||
	url.c       URL detection
 | 
						url.c       URL detection
 | 
				
			||||||
	pls.c       Functions which should not have to be written
 | 
						pls.c       Functions which should not have to be written
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										43
									
								
								chat.c
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								chat.c
									
									
									
									
									
								
							@ -29,6 +29,22 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "chat.h"
 | 
					#include "chat.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void selfNick(const char *nick) {
 | 
				
			||||||
 | 
						free(self.nick);
 | 
				
			||||||
 | 
						self.nick = strdup(nick);
 | 
				
			||||||
 | 
						if (!self.nick) err(EX_OSERR, "strdup");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void selfUser(const char *user) {
 | 
				
			||||||
 | 
						free(self.user);
 | 
				
			||||||
 | 
						self.user = strdup(user);
 | 
				
			||||||
 | 
						if (!self.user) err(EX_OSERR, "strdup");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void selfJoin(const char *join) {
 | 
				
			||||||
 | 
						free(self.join);
 | 
				
			||||||
 | 
						self.join = strdup(join);
 | 
				
			||||||
 | 
						if (!self.join) err(EX_OSERR, "strdup");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static union {
 | 
					static union {
 | 
				
			||||||
	struct {
 | 
						struct {
 | 
				
			||||||
		struct pollfd ui;
 | 
							struct pollfd ui;
 | 
				
			||||||
@ -44,7 +60,7 @@ static union {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void spawn(char *const argv[]) {
 | 
					void spawn(char *const argv[]) {
 | 
				
			||||||
	if (fds.pipe.events) {
 | 
						if (fds.pipe.events) {
 | 
				
			||||||
		uiLog(L"spawn: existing pipe");
 | 
							uiLog(TAG_DEFAULT, L"spawn: existing pipe");
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -77,7 +93,7 @@ static void pipeRead(void) {
 | 
				
			|||||||
	if (len) {
 | 
						if (len) {
 | 
				
			||||||
		buf[len] = '\0';
 | 
							buf[len] = '\0';
 | 
				
			||||||
		len = strcspn(buf, "\n");
 | 
							len = strcspn(buf, "\n");
 | 
				
			||||||
		uiFmt("%.*s", (int)len, buf);
 | 
							uiFmt(TAG_DEFAULT, "%.*s", (int)len, buf);
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		close(fds.pipe.fd);
 | 
							close(fds.pipe.fd);
 | 
				
			||||||
		fds.pipe.events = 0;
 | 
							fds.pipe.events = 0;
 | 
				
			||||||
@ -108,15 +124,15 @@ static void sigchld(int sig) {
 | 
				
			|||||||
	pid_t pid = wait(&status);
 | 
						pid_t pid = wait(&status);
 | 
				
			||||||
	if (pid < 0) err(EX_OSERR, "wait");
 | 
						if (pid < 0) err(EX_OSERR, "wait");
 | 
				
			||||||
	if (WIFEXITED(status) && WEXITSTATUS(status)) {
 | 
						if (WIFEXITED(status) && WEXITSTATUS(status)) {
 | 
				
			||||||
		uiFmt("spawn: exit %d", WEXITSTATUS(status));
 | 
							uiFmt(TAG_DEFAULT, "spawn: exit %d", WEXITSTATUS(status));
 | 
				
			||||||
	} else if (WIFSIGNALED(status)) {
 | 
						} else if (WIFSIGNALED(status)) {
 | 
				
			||||||
		uiFmt("spawn: signal %d", WTERMSIG(status));
 | 
							uiFmt(TAG_DEFAULT, "spawn: signal %d", WTERMSIG(status));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void sigint(int sig) {
 | 
					static void sigint(int sig) {
 | 
				
			||||||
	(void)sig;
 | 
						(void)sig;
 | 
				
			||||||
	input("/quit");
 | 
						input(TAG_DEFAULT, "/quit");
 | 
				
			||||||
	uiExit();
 | 
						uiExit();
 | 
				
			||||||
	exit(EX_OK);
 | 
						exit(EX_OK);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -129,7 +145,7 @@ static char *prompt(const char *prompt) {
 | 
				
			|||||||
		fflush(stdout);
 | 
							fflush(stdout);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ssize_t len = getline(&line, &cap, stdin);
 | 
							ssize_t len = getline(&line, &cap, stdin);
 | 
				
			||||||
		if (ferror(stdin)) err(EX_IOERR, "getline");
 | 
							//if (ferror(stdin)) err(EX_IOERR, "getline");
 | 
				
			||||||
		if (feof(stdin)) exit(EX_OK);
 | 
							if (feof(stdin)) exit(EX_OK);
 | 
				
			||||||
		if (len < 2) continue;
 | 
							if (len < 2) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -149,25 +165,24 @@ int main(int argc, char *argv[]) {
 | 
				
			|||||||
		switch (opt) {
 | 
							switch (opt) {
 | 
				
			||||||
			break; case 'W': webirc = optarg;
 | 
								break; case 'W': webirc = optarg;
 | 
				
			||||||
			break; case 'h': host = strdup(optarg);
 | 
								break; case 'h': host = strdup(optarg);
 | 
				
			||||||
			break; case 'j': chat.join = strdup(optarg);
 | 
								break; case 'j': selfJoin(optarg);
 | 
				
			||||||
			break; case 'n': chat.nick = strdup(optarg);
 | 
								break; case 'n': selfNick(optarg);
 | 
				
			||||||
			break; case 'p': port = optarg;
 | 
								break; case 'p': port = optarg;
 | 
				
			||||||
			break; case 'u': chat.user = strdup(optarg);
 | 
								break; case 'u': selfUser(optarg);
 | 
				
			||||||
			break; case 'v': chat.verbose = true;
 | 
								break; case 'v': self.verbose = true;
 | 
				
			||||||
			break; case 'w': pass = optarg;
 | 
								break; case 'w': pass = optarg;
 | 
				
			||||||
			break; default:  return EX_USAGE;
 | 
								break; default:  return EX_USAGE;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!host) host = prompt("Host: ");
 | 
						if (!host) host = prompt("Host: ");
 | 
				
			||||||
	if (!chat.join) chat.join = prompt("Join: ");
 | 
						if (!self.nick) self.nick = prompt("Name: ");
 | 
				
			||||||
	if (!chat.nick) chat.nick = prompt("Name: ");
 | 
						if (!self.user) selfUser(self.nick);
 | 
				
			||||||
	if (!chat.user) chat.user = strdup(chat.nick);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	inputTab();
 | 
						inputTab();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	uiInit();
 | 
						uiInit();
 | 
				
			||||||
	uiLog(L"Traveling...");
 | 
						uiLog(TAG_DEFAULT, L"Traveling...");
 | 
				
			||||||
	uiDraw();
 | 
						uiDraw();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fds.irc.fd = ircConnect(host, port, pass, webirc);
 | 
						fds.irc.fd = ircConnect(host, port, pass, webirc);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										102
									
								
								chat.h
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								chat.h
									
									
									
									
									
								
							@ -30,18 +30,23 @@ struct {
 | 
				
			|||||||
	char *nick;
 | 
						char *nick;
 | 
				
			||||||
	char *user;
 | 
						char *user;
 | 
				
			||||||
	char *join;
 | 
						char *join;
 | 
				
			||||||
} chat;
 | 
					} self;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void spawn(char *const argv[]);
 | 
					void selfNick(const char *nick);
 | 
				
			||||||
 | 
					void selfUser(const char *user);
 | 
				
			||||||
 | 
					void selfJoin(const char *join);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int ircConnect(
 | 
					struct Tag {
 | 
				
			||||||
	const char *host, const char *port, const char *pass, const char *webPass
 | 
						size_t id;
 | 
				
			||||||
);
 | 
						const char *name;
 | 
				
			||||||
void ircRead(void);
 | 
					};
 | 
				
			||||||
void ircWrite(const char *ptr, size_t len);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
__attribute__((format(printf, 1, 2)))
 | 
					enum { TAGS_LEN = 256 };
 | 
				
			||||||
void ircFmt(const char *format, ...);
 | 
					const struct Tag TAG_ALL;
 | 
				
			||||||
 | 
					const struct Tag TAG_DEFAULT;
 | 
				
			||||||
 | 
					struct Tag tagFor(const char *name);
 | 
				
			||||||
 | 
					struct Tag tagName(const char *name);
 | 
				
			||||||
 | 
					struct Tag tagNum(size_t num);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum {
 | 
					enum {
 | 
				
			||||||
	IRC_BOLD      = 002,
 | 
						IRC_BOLD      = 002,
 | 
				
			||||||
@ -52,47 +57,72 @@ enum {
 | 
				
			|||||||
	IRC_UNDERLINE = 037,
 | 
						IRC_UNDERLINE = 037,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void handle(char *line);
 | 
				
			||||||
 | 
					void input(struct Tag tag, char *line);
 | 
				
			||||||
 | 
					void inputTab(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int ircConnect(
 | 
				
			||||||
 | 
						const char *host, const char *port, const char *pass, const char *webPass
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					void ircRead(void);
 | 
				
			||||||
 | 
					void ircWrite(const char *ptr, size_t len);
 | 
				
			||||||
 | 
					void ircFmt(const char *format, ...) __attribute__((format(printf, 1, 2)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void uiInit(void);
 | 
					void uiInit(void);
 | 
				
			||||||
void uiHide(void);
 | 
					void uiHide(void);
 | 
				
			||||||
void uiExit(void);
 | 
					void uiExit(void);
 | 
				
			||||||
void uiDraw(void);
 | 
					void uiDraw(void);
 | 
				
			||||||
void uiBeep(void);
 | 
					void uiBeep(void);
 | 
				
			||||||
void uiRead(void);
 | 
					void uiRead(void);
 | 
				
			||||||
void uiTopic(const wchar_t *topic);
 | 
					void uiFocus(struct Tag tag);
 | 
				
			||||||
void uiTopicStr(const char *topic);
 | 
					void uiTopic(struct Tag tag, const char *topic);
 | 
				
			||||||
void uiLog(const wchar_t *line);
 | 
					void uiLog(struct Tag tag, const wchar_t *line);
 | 
				
			||||||
void uiFmt(const wchar_t *format, ...);
 | 
					void uiFmt(struct Tag tag, const wchar_t *format, ...);
 | 
				
			||||||
 | 
					 | 
				
			||||||
// HACK: clang won't check wchar_t *format strings.
 | 
					 | 
				
			||||||
#ifdef NDEBUG
 | 
					 | 
				
			||||||
#define uiFmt(format, ...) uiFmt(L##format, __VA_ARGS__)
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
#define uiFmt(format, ...) do { \
 | 
					 | 
				
			||||||
	snprintf(NULL, 0, format, __VA_ARGS__); \
 | 
					 | 
				
			||||||
	uiFmt(L##format, __VA_ARGS__); \
 | 
					 | 
				
			||||||
} while(0)
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum Edit {
 | 
				
			||||||
 | 
						EDIT_LEFT,
 | 
				
			||||||
 | 
						EDIT_RIGHT,
 | 
				
			||||||
 | 
						EDIT_HOME,
 | 
				
			||||||
 | 
						EDIT_END,
 | 
				
			||||||
 | 
						EDIT_BACK_WORD,
 | 
				
			||||||
 | 
						EDIT_FORE_WORD,
 | 
				
			||||||
 | 
						EDIT_INSERT,
 | 
				
			||||||
 | 
						EDIT_BACKSPACE,
 | 
				
			||||||
 | 
						EDIT_DELETE,
 | 
				
			||||||
 | 
						EDIT_KILL_BACK_WORD,
 | 
				
			||||||
 | 
						EDIT_KILL_FORE_WORD,
 | 
				
			||||||
 | 
						EDIT_KILL_LINE,
 | 
				
			||||||
 | 
						EDIT_COMPLETE,
 | 
				
			||||||
 | 
						EDIT_ENTER,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					void edit(struct Tag tag, enum Edit op, wchar_t ch);
 | 
				
			||||||
const wchar_t *editHead(void);
 | 
					const wchar_t *editHead(void);
 | 
				
			||||||
const wchar_t *editTail(void);
 | 
					const wchar_t *editTail(void);
 | 
				
			||||||
bool edit(bool meta, bool ctrl, wchar_t ch);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void handle(char *line);
 | 
					void tabTouch(struct Tag tag, const char *word);
 | 
				
			||||||
 | 
					void tabRemove(struct Tag tag, const char *word);
 | 
				
			||||||
void inputTab(void);
 | 
					void tabClear(struct Tag tag);
 | 
				
			||||||
void input(char *line);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void urlScan(const char *str);
 | 
					 | 
				
			||||||
void urlList(void);
 | 
					 | 
				
			||||||
void urlOpen(size_t i);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void tabTouch(const char *word);
 | 
					 | 
				
			||||||
void tabRemove(const char *word);
 | 
					 | 
				
			||||||
void tabReplace(const char *prev, const char *next);
 | 
					void tabReplace(const char *prev, const char *next);
 | 
				
			||||||
const char *tabNext(const char *prefix);
 | 
					const char *tabNext(struct Tag tag, const char *prefix);
 | 
				
			||||||
void tabAccept(void);
 | 
					void tabAccept(void);
 | 
				
			||||||
void tabReject(void);
 | 
					void tabReject(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void urlScan(struct Tag tag, const char *str);
 | 
				
			||||||
 | 
					void urlList(struct Tag tag);
 | 
				
			||||||
 | 
					void urlOpen(struct Tag tag, size_t fromEnd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void spawn(char *const argv[]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
wchar_t *ambstowcs(const char *src);
 | 
					wchar_t *ambstowcs(const char *src);
 | 
				
			||||||
char *awcstombs(const wchar_t *src);
 | 
					char *awcstombs(const wchar_t *src);
 | 
				
			||||||
int vaswprintf(wchar_t **ret, const wchar_t *format, va_list ap);
 | 
					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__)
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					#define uiFmt(tag, format, ...) do { \
 | 
				
			||||||
 | 
						snprintf(NULL, 0, format, __VA_ARGS__); \
 | 
				
			||||||
 | 
						uiFmt(tag, L##format, __VA_ARGS__); \
 | 
				
			||||||
 | 
					} while(0)
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										141
									
								
								edit.c
									
									
									
									
									
								
							
							
						
						
									
										141
									
								
								edit.c
									
									
									
									
									
								
							@ -34,6 +34,7 @@ static struct {
 | 
				
			|||||||
	.end = line.buf,
 | 
						.end = line.buf,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// XXX: editTail must always be called after editHead.
 | 
				
			||||||
static wchar_t tail;
 | 
					static wchar_t tail;
 | 
				
			||||||
const wchar_t *editHead(void) {
 | 
					const wchar_t *editHead(void) {
 | 
				
			||||||
	tail = *line.ptr;
 | 
						tail = *line.ptr;
 | 
				
			||||||
@ -41,8 +42,9 @@ const wchar_t *editHead(void) {
 | 
				
			|||||||
	return line.buf;
 | 
						return line.buf;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
const wchar_t *editTail(void) {
 | 
					const wchar_t *editTail(void) {
 | 
				
			||||||
	*line.ptr = tail;
 | 
						if (tail) *line.ptr = tail;
 | 
				
			||||||
	*line.end = L'\0';
 | 
						*line.end = L'\0';
 | 
				
			||||||
 | 
						tail = L'\0';
 | 
				
			||||||
	return line.ptr;
 | 
						return line.ptr;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -52,13 +54,29 @@ static void left(void) {
 | 
				
			|||||||
static void right(void) {
 | 
					static void right(void) {
 | 
				
			||||||
	if (line.ptr < line.end) line.ptr++;
 | 
						if (line.ptr < line.end) line.ptr++;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
static void home(void) {
 | 
					
 | 
				
			||||||
	line.ptr = line.buf;
 | 
					static void backWord(void) {
 | 
				
			||||||
 | 
						left();
 | 
				
			||||||
 | 
						editHead();
 | 
				
			||||||
 | 
						wchar_t *word = wcsrchr(line.buf, ' ');
 | 
				
			||||||
 | 
						editTail();
 | 
				
			||||||
 | 
						line.ptr = (word ? &word[1] : line.buf);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
static void end(void) {
 | 
					static void foreWord(void) {
 | 
				
			||||||
	line.ptr = line.end;
 | 
						right();
 | 
				
			||||||
 | 
						editTail();
 | 
				
			||||||
 | 
						wchar_t *word = wcschr(line.ptr, ' ');
 | 
				
			||||||
 | 
						line.ptr = (word ? word : line.end);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void insert(wchar_t ch) {
 | 
				
			||||||
 | 
						if (line.end == &line.buf[BUF_LEN - 1]) return;
 | 
				
			||||||
 | 
						if (line.ptr != line.end) {
 | 
				
			||||||
 | 
							wmemmove(line.ptr + 1, line.ptr, line.end - line.ptr);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						*line.ptr++ = ch;
 | 
				
			||||||
 | 
						line.end++;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
static void backspace(void) {
 | 
					static void backspace(void) {
 | 
				
			||||||
	if (line.ptr == line.buf) return;
 | 
						if (line.ptr == line.buf) return;
 | 
				
			||||||
	if (line.ptr != line.end) {
 | 
						if (line.ptr != line.end) {
 | 
				
			||||||
@ -73,41 +91,6 @@ static void delete(void) {
 | 
				
			|||||||
	backspace();
 | 
						backspace();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void insert(wchar_t ch) {
 | 
					 | 
				
			||||||
	if (line.end == &line.buf[BUF_LEN - 1]) return;
 | 
					 | 
				
			||||||
	if (line.ptr != line.end) {
 | 
					 | 
				
			||||||
		wmemmove(line.ptr + 1, line.ptr, line.end - line.ptr);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	*line.ptr++ = ch;
 | 
					 | 
				
			||||||
	line.end++;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void enter(void) {
 | 
					 | 
				
			||||||
	if (line.end == line.buf) return;
 | 
					 | 
				
			||||||
	*line.end = L'\0';
 | 
					 | 
				
			||||||
	char *str = awcstombs(line.buf);
 | 
					 | 
				
			||||||
	if (!str) err(EX_DATAERR, "awcstombs");
 | 
					 | 
				
			||||||
	input(str);
 | 
					 | 
				
			||||||
	free(str);
 | 
					 | 
				
			||||||
	line.ptr = line.buf;
 | 
					 | 
				
			||||||
	line.end = line.buf;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void backWord(void) {
 | 
					 | 
				
			||||||
	left();
 | 
					 | 
				
			||||||
	editHead();
 | 
					 | 
				
			||||||
	wchar_t *word = wcsrchr(line.buf, ' ');
 | 
					 | 
				
			||||||
	editTail();
 | 
					 | 
				
			||||||
	line.ptr = (word ? &word[1] : line.buf);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
static void foreWord(void) {
 | 
					 | 
				
			||||||
	right();
 | 
					 | 
				
			||||||
	editHead();
 | 
					 | 
				
			||||||
	editTail();
 | 
					 | 
				
			||||||
	wchar_t *word = wcschr(line.ptr, ' ');
 | 
					 | 
				
			||||||
	line.ptr = (word ? word : line.end);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void killBackWord(void) {
 | 
					static void killBackWord(void) {
 | 
				
			||||||
	wchar_t *from = line.ptr;
 | 
						wchar_t *from = line.ptr;
 | 
				
			||||||
	backWord();
 | 
						backWord();
 | 
				
			||||||
@ -121,12 +104,9 @@ static void killForeWord(void) {
 | 
				
			|||||||
	line.end -= line.ptr - from;
 | 
						line.end -= line.ptr - from;
 | 
				
			||||||
	line.ptr = from;
 | 
						line.ptr = from;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
static void killLine(void) {
 | 
					 | 
				
			||||||
	line.end = line.ptr;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
static char *prefix;
 | 
					static char *prefix;
 | 
				
			||||||
static void complete(void) {
 | 
					static void complete(struct Tag tag) {
 | 
				
			||||||
	if (!line.tab) {
 | 
						if (!line.tab) {
 | 
				
			||||||
		editHead();
 | 
							editHead();
 | 
				
			||||||
		line.tab = wcsrchr(line.buf, L' ');
 | 
							line.tab = wcsrchr(line.buf, L' ');
 | 
				
			||||||
@ -136,7 +116,7 @@ static void complete(void) {
 | 
				
			|||||||
		editTail();
 | 
							editTail();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const char *next = tabNext(prefix);
 | 
						const char *next = tabNext(tag, prefix);
 | 
				
			||||||
	if (!next) return;
 | 
						if (!next) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	wchar_t *wcs = ambstowcs(next);
 | 
						wchar_t *wcs = ambstowcs(next);
 | 
				
			||||||
@ -179,52 +159,37 @@ static void reject(void) {
 | 
				
			|||||||
	tabReject();
 | 
						tabReject();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool editMeta(wchar_t ch) {
 | 
					static void enter(struct Tag tag) {
 | 
				
			||||||
	switch (ch) {
 | 
						if (line.end == line.buf) return;
 | 
				
			||||||
		break; case L'b':  reject(); backWord();
 | 
						editTail();
 | 
				
			||||||
		break; case L'f':  reject(); foreWord();
 | 
						char *str = awcstombs(line.buf);
 | 
				
			||||||
		break; case L'\b': reject(); killBackWord();
 | 
						if (!str) err(EX_DATAERR, "awcstombs");
 | 
				
			||||||
		break; case L'd':  reject(); killForeWord();
 | 
						input(tag, str);
 | 
				
			||||||
 | 
						free(str);
 | 
				
			||||||
		break; default: return false;
 | 
						line.ptr = line.buf;
 | 
				
			||||||
	}
 | 
						line.end = line.buf;
 | 
				
			||||||
	return true;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool editCtrl(wchar_t ch) {
 | 
					void edit(struct Tag tag, enum Edit op, wchar_t ch) {
 | 
				
			||||||
	switch (ch) {
 | 
						switch (op) {
 | 
				
			||||||
		break; case L'B': reject(); left();
 | 
							break; case EDIT_LEFT:  reject(); left();
 | 
				
			||||||
		break; case L'F': reject(); right();
 | 
							break; case EDIT_RIGHT: reject(); right();
 | 
				
			||||||
		break; case L'A': reject(); home();
 | 
							break; case EDIT_HOME:  reject(); line.ptr = line.buf;
 | 
				
			||||||
		break; case L'E': reject(); end();
 | 
							break; case EDIT_END:   reject(); line.ptr = line.end;
 | 
				
			||||||
		break; case L'D': reject(); delete();
 | 
					 | 
				
			||||||
		break; case L'W': reject(); killBackWord();
 | 
					 | 
				
			||||||
		break; case L'K': reject(); killLine();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		break; case L'C': accept(); insert(IRC_COLOR);
 | 
							break; case EDIT_BACK_WORD: reject(); backWord();
 | 
				
			||||||
		break; case L'N': accept(); insert(IRC_RESET);
 | 
							break; case EDIT_FORE_WORD: reject(); foreWord();
 | 
				
			||||||
		break; case L'O': accept(); insert(IRC_BOLD);
 | 
					 | 
				
			||||||
		break; case L'R': accept(); insert(IRC_COLOR);
 | 
					 | 
				
			||||||
		break; case L'T': accept(); insert(IRC_ITALIC);
 | 
					 | 
				
			||||||
		break; case L'V': accept(); insert(IRC_REVERSE);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		break; default: return false;
 | 
							break; case EDIT_INSERT:    accept(); insert(ch);
 | 
				
			||||||
 | 
							break; case EDIT_BACKSPACE: reject(); backspace();
 | 
				
			||||||
 | 
							break; case EDIT_DELETE:    reject(); delete();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							break; case EDIT_KILL_BACK_WORD: reject(); killBackWord();
 | 
				
			||||||
 | 
							break; case EDIT_KILL_FORE_WORD: reject(); killForeWord();
 | 
				
			||||||
 | 
							break; case EDIT_KILL_LINE:      reject(); line.end = line.ptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							break; case EDIT_COMPLETE: complete(tag);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							break; case EDIT_ENTER: accept(); enter(tag);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool edit(bool meta, bool ctrl, wchar_t ch) {
 | 
					 | 
				
			||||||
	if (meta) return editMeta(ch);
 | 
					 | 
				
			||||||
	if (ctrl) return editCtrl(ch);
 | 
					 | 
				
			||||||
	switch (ch) {
 | 
					 | 
				
			||||||
		break; case L'\t': complete();
 | 
					 | 
				
			||||||
		break; case L'\b': reject(); backspace();
 | 
					 | 
				
			||||||
		break; case L'\n': accept(); enter();
 | 
					 | 
				
			||||||
		break; default: {
 | 
					 | 
				
			||||||
			if (!iswprint(ch)) return false;
 | 
					 | 
				
			||||||
			accept();
 | 
					 | 
				
			||||||
			insert(ch);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return true;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										163
									
								
								handle.c
									
									
									
									
									
								
							
							
						
						
									
										163
									
								
								handle.c
									
									
									
									
									
								
							@ -74,6 +74,16 @@ static void shift(
 | 
				
			|||||||
	va_end(ap);
 | 
						va_end(ap);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool isSelf(const char *nick, const char *user) {
 | 
				
			||||||
 | 
						if (!user) return false;
 | 
				
			||||||
 | 
						if (!strcmp(user, self.user)) return true;
 | 
				
			||||||
 | 
						if (!strcmp(nick, self.nick)) {
 | 
				
			||||||
 | 
							if (strcmp(user, self.user)) selfUser(user);
 | 
				
			||||||
 | 
							return true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef void (*Handler)(char *prefix, char *params);
 | 
					typedef void (*Handler)(char *prefix, char *params);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void handlePing(char *prefix, char *params) {
 | 
					static void handlePing(char *prefix, char *params) {
 | 
				
			||||||
@ -84,104 +94,118 @@ static void handlePing(char *prefix, char *params) {
 | 
				
			|||||||
static void handle432(char *prefix, char *params) {
 | 
					static void handle432(char *prefix, char *params) {
 | 
				
			||||||
	char *mesg;
 | 
						char *mesg;
 | 
				
			||||||
	shift(prefix, NULL, NULL, NULL, params, 3, 0, NULL, NULL, &mesg);
 | 
						shift(prefix, NULL, NULL, NULL, params, 3, 0, NULL, NULL, &mesg);
 | 
				
			||||||
	uiLog(L"You can't use that name here");
 | 
						uiLog(TAG_DEFAULT, L"You can't use that name here");
 | 
				
			||||||
	uiFmt("Sheriff says, \"%s\"", mesg);
 | 
						uiFmt(TAG_DEFAULT, "Sheriff says, \"%s\"", mesg);
 | 
				
			||||||
	uiLog(L"Type /nick <name> to choose a new one");
 | 
						uiLog(TAG_DEFAULT, L"Type /nick <name> to choose a new one");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void handle001(char *prefix, char *params) {
 | 
					static void handle001(char *prefix, char *params) {
 | 
				
			||||||
	char *nick;
 | 
						char *nick;
 | 
				
			||||||
	shift(prefix, NULL, NULL, NULL, params, 1, 0, &nick);
 | 
						shift(prefix, NULL, NULL, NULL, params, 1, 0, &nick);
 | 
				
			||||||
	if (strcmp(nick, chat.nick)) {
 | 
						if (strcmp(nick, self.nick)) selfNick(nick);
 | 
				
			||||||
		free(chat.nick);
 | 
						tabTouch(TAG_DEFAULT, self.nick);
 | 
				
			||||||
		chat.nick = strdup(nick);
 | 
						if (self.join) ircFmt("JOIN %s\r\n", self.join);
 | 
				
			||||||
	}
 | 
						uiLog(TAG_DEFAULT, L"You have arrived");
 | 
				
			||||||
	ircFmt("JOIN %s\r\n", chat.join);
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void handle372(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);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void handleJoin(char *prefix, char *params) {
 | 
					static void handleJoin(char *prefix, char *params) {
 | 
				
			||||||
	char *nick, *user, *chan;
 | 
						char *nick, *user, *chan;
 | 
				
			||||||
	shift(prefix, &nick, &user, NULL, params, 1, 0, &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);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	uiFmt(
 | 
						uiFmt(
 | 
				
			||||||
		"\3%d%s\3 arrives in \3%d%s\3",
 | 
							tag, "\3%d%s\3 arrives in \3%d%s\3",
 | 
				
			||||||
		color(user), nick, color(chan), chan
 | 
							color(user), nick, color(chan), chan
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
	if (!strcmp(nick, chat.nick) && strcmp(user, chat.user)) {
 | 
					 | 
				
			||||||
		free(chat.user);
 | 
					 | 
				
			||||||
		chat.user = strdup(user);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	tabTouch(nick);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void handlePart(char *prefix, char *params) {
 | 
					static void handlePart(char *prefix, char *params) {
 | 
				
			||||||
	char *nick, *user, *chan, *mesg;
 | 
						char *nick, *user, *chan, *mesg;
 | 
				
			||||||
	shift(prefix, &nick, &user, NULL, params, 1, 1, &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 (mesg) {
 | 
						if (mesg) {
 | 
				
			||||||
		uiFmt(
 | 
							uiFmt(
 | 
				
			||||||
			"\3%d%s\3 leaves \3%d%s\3, \"%s\"",
 | 
								tag, "\3%d%s\3 leaves \3%d%s\3, \"%s\"",
 | 
				
			||||||
			color(user), nick, color(chan), chan, mesg
 | 
								color(user), nick, color(chan), chan, mesg
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		uiFmt(
 | 
							uiFmt(
 | 
				
			||||||
			"\3%d%s\3 leaves \3%d%s\3",
 | 
								tag, "\3%d%s\3 leaves \3%d%s\3",
 | 
				
			||||||
			color(user), nick, color(chan), chan
 | 
								color(user), nick, color(chan), chan
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	tabRemove(nick);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void handleQuit(char *prefix, char *params) {
 | 
					 | 
				
			||||||
	char *nick, *user, *mesg;
 | 
					 | 
				
			||||||
	shift(prefix, &nick, &user, NULL, params, 0, 1, &mesg);
 | 
					 | 
				
			||||||
	if (mesg) {
 | 
					 | 
				
			||||||
		char *quot = (mesg[0] == '"') ? "" : "\"";
 | 
					 | 
				
			||||||
		uiFmt(
 | 
					 | 
				
			||||||
			"\3%d%s\3 leaves, %s%s%s",
 | 
					 | 
				
			||||||
			color(user), nick, quot, mesg, quot
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		uiFmt("\3%d%s\3 leaves", color(user), nick);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	tabRemove(nick);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void handleKick(char *prefix, char *params) {
 | 
					static void handleKick(char *prefix, char *params) {
 | 
				
			||||||
	char *nick, *user, *chan, *kick, *mesg;
 | 
						char *nick, *user, *chan, *kick, *mesg;
 | 
				
			||||||
	shift(prefix, &nick, &user, NULL, params, 2, 1, &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));
 | 
				
			||||||
	if (mesg) {
 | 
						if (mesg) {
 | 
				
			||||||
		uiFmt(
 | 
							uiFmt(
 | 
				
			||||||
			"\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3, \"%s\"",
 | 
								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
 | 
								color(user), nick, color(kick), kick, color(chan), chan, mesg
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		uiFmt(
 | 
							uiFmt(
 | 
				
			||||||
			"\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3",
 | 
								tag, "\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3",
 | 
				
			||||||
			color(user), nick, color(kick), kick, color(chan), chan
 | 
								color(user), nick, color(kick), kick, color(chan), chan
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	tabRemove(nick);
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void handle332(char *prefix, char *params) {
 | 
					static void handle332(char *prefix, char *params) {
 | 
				
			||||||
	char *chan, *topic;
 | 
						char *chan, *topic;
 | 
				
			||||||
	shift(prefix, NULL, NULL, NULL, params, 3, 0, NULL, &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(
 | 
						uiFmt(
 | 
				
			||||||
		"The sign in \3%d%s\3 reads, \"%s\"",
 | 
							tag, "The sign in \3%d%s\3 reads, \"%s\"",
 | 
				
			||||||
		color(chan), chan, topic
 | 
							color(chan), chan, topic
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
	urlScan(topic);
 | 
					 | 
				
			||||||
	uiTopicStr(topic);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void handleTopic(char *prefix, char *params) {
 | 
					static void handleTopic(char *prefix, char *params) {
 | 
				
			||||||
	char *nick, *user, *chan, *topic;
 | 
						char *nick, *user, *chan, *topic;
 | 
				
			||||||
	shift(prefix, &nick, &user, NULL, params, 2, 0, &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(
 | 
						uiFmt(
 | 
				
			||||||
		"\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"",
 | 
							tag, "\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"",
 | 
				
			||||||
		color(user), nick, color(chan), chan, topic
 | 
							color(user), nick, color(chan), chan, topic
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
	urlScan(topic);
 | 
					 | 
				
			||||||
	uiTopicStr(topic);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void handle366(char *prefix, char *params) {
 | 
					static void handle366(char *prefix, char *params) {
 | 
				
			||||||
@ -190,17 +214,20 @@ static void handle366(char *prefix, char *params) {
 | 
				
			|||||||
	ircFmt("WHO %s\r\n", chan);
 | 
						ircFmt("WHO %s\r\n", chan);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FIXME: Track tag?
 | 
				
			||||||
static struct {
 | 
					static struct {
 | 
				
			||||||
	char buf[4096];
 | 
						char buf[4096];
 | 
				
			||||||
	size_t len;
 | 
						size_t len;
 | 
				
			||||||
} who;
 | 
					} who;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void handle352(char *prefix, char *params) {
 | 
					static void handle352(char *prefix, char *params) {
 | 
				
			||||||
	char *user, *nick;
 | 
						char *chan, *user, *nick;
 | 
				
			||||||
	shift(
 | 
						shift(
 | 
				
			||||||
		prefix, NULL, NULL, NULL,
 | 
							prefix, NULL, NULL, NULL,
 | 
				
			||||||
		params, 6, 0, NULL, NULL, &user, NULL, NULL, &nick
 | 
							params, 6, 0, NULL, &chan, &user, NULL, NULL, &nick
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
 | 
						struct Tag tag = tagFor(chan);
 | 
				
			||||||
 | 
						if (!isSelf(nick, user)) tabTouch(tag, nick);
 | 
				
			||||||
	size_t cap = sizeof(who.buf) - who.len;
 | 
						size_t cap = sizeof(who.buf) - who.len;
 | 
				
			||||||
	int len = snprintf(
 | 
						int len = snprintf(
 | 
				
			||||||
		&who.buf[who.len], cap,
 | 
							&who.buf[who.len], cap,
 | 
				
			||||||
@ -208,14 +235,14 @@ static void handle352(char *prefix, char *params) {
 | 
				
			|||||||
		(who.len ? ", " : ""), color(user), nick
 | 
							(who.len ? ", " : ""), color(user), nick
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
	if ((size_t)len < cap) who.len += len;
 | 
						if ((size_t)len < cap) who.len += len;
 | 
				
			||||||
	tabTouch(nick);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void handle315(char *prefix, char *params) {
 | 
					static void handle315(char *prefix, char *params) {
 | 
				
			||||||
	char *chan;
 | 
						char *chan;
 | 
				
			||||||
	shift(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &chan);
 | 
						shift(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &chan);
 | 
				
			||||||
 | 
						struct Tag tag = tagFor(chan);
 | 
				
			||||||
	uiFmt(
 | 
						uiFmt(
 | 
				
			||||||
		"In \3%d%s\3 are %s",
 | 
							tag, "In \3%d%s\3 are %s",
 | 
				
			||||||
		color(chan), chan, who.buf
 | 
							color(chan), chan, who.buf
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
	who.len = 0;
 | 
						who.len = 0;
 | 
				
			||||||
@ -224,58 +251,58 @@ static void handle315(char *prefix, char *params) {
 | 
				
			|||||||
static void handleNick(char *prefix, char *params) {
 | 
					static void handleNick(char *prefix, char *params) {
 | 
				
			||||||
	char *prev, *user, *next;
 | 
						char *prev, *user, *next;
 | 
				
			||||||
	shift(prefix, &prev, &user, NULL, params, 1, 0, &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(
 | 
						uiFmt(
 | 
				
			||||||
		"\3%d%s\3 is now known as \3%d%s\3",
 | 
							TAG_DEFAULT, "\3%d%s\3 is now known as \3%d%s\3",
 | 
				
			||||||
		color(user), prev, color(user), next
 | 
							color(user), prev, color(user), next
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
	if (!strcmp(user, chat.user)) {
 | 
					 | 
				
			||||||
		free(chat.nick);
 | 
					 | 
				
			||||||
		chat.nick = strdup(next);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	tabReplace(prev, next);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void handleCTCP(char *nick, char *user, char *mesg) {
 | 
					static void handleCTCP(struct Tag tag, char *nick, char *user, char *mesg) {
 | 
				
			||||||
	mesg = &mesg[1];
 | 
						mesg = &mesg[1];
 | 
				
			||||||
	char *ctcp = strsep(&mesg, " ");
 | 
						char *ctcp = strsep(&mesg, " ");
 | 
				
			||||||
	char *params = strsep(&mesg, "\1");
 | 
						char *params = strsep(&mesg, "\1");
 | 
				
			||||||
	if (strcmp(ctcp, "ACTION")) return;
 | 
						if (strcmp(ctcp, "ACTION")) return;
 | 
				
			||||||
 | 
						if (!isSelf(nick, user)) tabTouch(tag, nick);
 | 
				
			||||||
 | 
						urlScan(tag, params);
 | 
				
			||||||
	uiFmt(
 | 
						uiFmt(
 | 
				
			||||||
		"\3%d* %s\3 %s",
 | 
							tag, "\3%d* %s\3 %s",
 | 
				
			||||||
		color(user), nick, params
 | 
							color(user), nick, params
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
	if (strcmp(user, chat.user)) tabTouch(nick);
 | 
					 | 
				
			||||||
	urlScan(params);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void handlePrivmsg(char *prefix, char *params) {
 | 
					static void handlePrivmsg(char *prefix, char *params) {
 | 
				
			||||||
	char *nick, *user, *mesg;
 | 
						char *nick, *user, *chan, *mesg;
 | 
				
			||||||
	shift(prefix, &nick, &user, NULL, params, 2, 0, NULL, &mesg);
 | 
						shift(prefix, &nick, &user, NULL, params, 2, 0, &chan, &mesg);
 | 
				
			||||||
 | 
						struct Tag tag = (strcmp(chan, self.nick) ? tagFor(chan) : tagFor(nick));
 | 
				
			||||||
	if (mesg[0] == '\1') {
 | 
						if (mesg[0] == '\1') {
 | 
				
			||||||
		handleCTCP(nick, user, mesg);
 | 
							handleCTCP(tag, nick, user, mesg);
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	bool self = !strcmp(user, chat.user);
 | 
						if (!isSelf(nick, user)) tabTouch(tag, nick);
 | 
				
			||||||
	bool ping = !strncasecmp(mesg, chat.nick, strlen(chat.nick));
 | 
						urlScan(tag, mesg);
 | 
				
			||||||
 | 
						bool ping = !strncasecmp(mesg, self.nick, strlen(self.nick));
 | 
				
			||||||
 | 
						bool self = isSelf(nick, user);
 | 
				
			||||||
	uiFmt(
 | 
						uiFmt(
 | 
				
			||||||
		"%c\3%d%c%s%c\17 %s",
 | 
							tag, "%c\3%d%c%s%c\17 %s",
 | 
				
			||||||
		ping["\17\26"], color(user), self["<("], nick, self[">)"], mesg
 | 
							ping["\17\26"], color(user), self["<("], nick, self[">)"], mesg
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
	if (!self) tabTouch(nick);
 | 
					 | 
				
			||||||
	if (ping) uiBeep();
 | 
						if (ping) uiBeep();
 | 
				
			||||||
	urlScan(mesg);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void handleNotice(char *prefix, char *params) {
 | 
					static void handleNotice(char *prefix, char *params) {
 | 
				
			||||||
	char *nick, *user, *chan, *mesg;
 | 
						char *nick, *user, *chan, *mesg;
 | 
				
			||||||
	shift(prefix, &nick, &user, NULL, params, 2, 0, &chan, &mesg);
 | 
						shift(prefix, &nick, &user, NULL, params, 2, 0, &chan, &mesg);
 | 
				
			||||||
	if (strcmp(chan, chat.join)) return;
 | 
						struct Tag tag = TAG_DEFAULT;
 | 
				
			||||||
 | 
						if (user) tag = (strcmp(chan, self.nick) ? tagFor(chan) : tagFor(nick));
 | 
				
			||||||
 | 
						if (!isSelf(nick, user)) tabTouch(tag, nick);
 | 
				
			||||||
 | 
						urlScan(tag, mesg);
 | 
				
			||||||
	uiFmt(
 | 
						uiFmt(
 | 
				
			||||||
		"\3%d-%s-\3 %s",
 | 
							tag, "\3%d-%s-\3 %s",
 | 
				
			||||||
		color(user), nick, mesg
 | 
							color(user), nick, mesg
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
	tabTouch(nick);
 | 
					 | 
				
			||||||
	urlScan(mesg);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const struct {
 | 
					static const struct {
 | 
				
			||||||
@ -287,6 +314,8 @@ static const struct {
 | 
				
			|||||||
	{ "332", handle332 },
 | 
						{ "332", handle332 },
 | 
				
			||||||
	{ "352", handle352 },
 | 
						{ "352", handle352 },
 | 
				
			||||||
	{ "366", handle366 },
 | 
						{ "366", handle366 },
 | 
				
			||||||
 | 
						{ "372", handle372 },
 | 
				
			||||||
 | 
						{ "375", handle372 },
 | 
				
			||||||
	{ "432", handle432 },
 | 
						{ "432", handle432 },
 | 
				
			||||||
	{ "433", handle432 },
 | 
						{ "433", handle432 },
 | 
				
			||||||
	{ "JOIN", handleJoin },
 | 
						{ "JOIN", handleJoin },
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										79
									
								
								input.c
									
									
									
									
									
								
							
							
						
						
									
										79
									
								
								input.c
									
									
									
									
									
								
							@ -23,12 +23,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "chat.h"
 | 
					#include "chat.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void privmsg(bool action, const char *mesg) {
 | 
					static void privmsg(struct Tag tag, bool action, const char *mesg) {
 | 
				
			||||||
 | 
						if (tag.id == TAG_DEFAULT.id) return;
 | 
				
			||||||
	char *line;
 | 
						char *line;
 | 
				
			||||||
	int send;
 | 
						int send;
 | 
				
			||||||
	asprintf(
 | 
						asprintf(
 | 
				
			||||||
		&line, ":%s!%s %nPRIVMSG %s :%s%s%s",
 | 
							&line, ":%s!%s %nPRIVMSG %s :%s%s%s",
 | 
				
			||||||
		chat.nick, chat.user, &send, chat.join,
 | 
							self.nick, self.user, &send, tag.name,
 | 
				
			||||||
		(action ? "\1ACTION " : ""), mesg, (action ? "\1" : "")
 | 
							(action ? "\1ACTION " : ""), mesg, (action ? "\1" : "")
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
	if (!line) err(EX_OSERR, "asprintf");
 | 
						if (!line) err(EX_OSERR, "asprintf");
 | 
				
			||||||
@ -37,35 +38,47 @@ static void privmsg(bool action, const char *mesg) {
 | 
				
			|||||||
	free(line);
 | 
						free(line);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef void (*Handler)(char *params);
 | 
					typedef void (*Handler)(struct Tag tag, char *params);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void inputMe(char *params) {
 | 
					static void inputMe(struct Tag tag, char *params) {
 | 
				
			||||||
	privmsg(true, params ? params : "");
 | 
						privmsg(tag, true, params ? params : "");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void inputNick(char *params) {
 | 
					static void inputNick(struct Tag tag, char *params) {
 | 
				
			||||||
 | 
						(void)tag;
 | 
				
			||||||
	char *nick = strsep(¶ms, " ");
 | 
						char *nick = strsep(¶ms, " ");
 | 
				
			||||||
	if (nick) {
 | 
						if (nick) {
 | 
				
			||||||
		ircFmt("NICK %s\r\n", nick);
 | 
							ircFmt("NICK %s\r\n", nick);
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		uiLog(L"/nick requires a name");
 | 
							uiLog(TAG_DEFAULT, L"/nick requires a name");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void inputWho(char *params) {
 | 
					static void inputJoin(struct Tag tag, char *params) {
 | 
				
			||||||
	(void)params;
 | 
						(void)tag;
 | 
				
			||||||
	ircFmt("WHO %s\r\n", chat.join);
 | 
						char *chan = strsep(¶ms, " ");
 | 
				
			||||||
}
 | 
						if (chan) {
 | 
				
			||||||
 | 
							ircFmt("JOIN %s\r\n", chan);
 | 
				
			||||||
static void inputTopic(char *params) {
 | 
					 | 
				
			||||||
	if (params) {
 | 
					 | 
				
			||||||
		ircFmt("TOPIC %s :%s\r\n", chat.join, params);
 | 
					 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		ircFmt("TOPIC %s\r\n", chat.join);
 | 
							uiLog(TAG_DEFAULT, L"/join requires a channel");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void inputQuit(char *params) {
 | 
					static void inputWho(struct Tag tag, char *params) {
 | 
				
			||||||
 | 
						(void)params; // TODO
 | 
				
			||||||
 | 
						ircFmt("WHO %s\r\n", tag.name);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void inputTopic(struct Tag tag, char *params) {
 | 
				
			||||||
 | 
						if (params) { // TODO
 | 
				
			||||||
 | 
							ircFmt("TOPIC %s :%s\r\n", tag.name, params);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							ircFmt("TOPIC %s\r\n", tag.name);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void inputQuit(struct Tag tag, char *params) {
 | 
				
			||||||
 | 
						(void)tag;
 | 
				
			||||||
	if (params) {
 | 
						if (params) {
 | 
				
			||||||
		ircFmt("QUIT :%s\r\n", params);
 | 
							ircFmt("QUIT :%s\r\n", params);
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
@ -73,25 +86,34 @@ static void inputQuit(char *params) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void inputUrl(char *params) {
 | 
					static void inputUrl(struct Tag tag, char *params) {
 | 
				
			||||||
	(void)params;
 | 
						(void)params;
 | 
				
			||||||
	urlList();
 | 
						urlList(tag);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
static void inputOpen(char *params) {
 | 
					static void inputOpen(struct Tag tag, char *params) {
 | 
				
			||||||
	if (!params) { urlOpen(1); return; }
 | 
						if (!params) { urlOpen(tag, 1); return; }
 | 
				
			||||||
	size_t from = strtoul(strsep(¶ms, "-,"), NULL, 0);
 | 
						size_t from = strtoul(strsep(¶ms, "-,"), NULL, 0);
 | 
				
			||||||
	if (!params) { urlOpen(from); return; }
 | 
						if (!params) { urlOpen(tag, from); return; }
 | 
				
			||||||
	size_t to = strtoul(strsep(¶ms, "-,"), NULL, 0);
 | 
						size_t to = strtoul(strsep(¶ms, "-,"), NULL, 0);
 | 
				
			||||||
	if (to < from) to = from;
 | 
						if (to < from) to = from;
 | 
				
			||||||
	for (size_t i = from; i <= to; ++i) {
 | 
						for (size_t i = from; i <= to; ++i) {
 | 
				
			||||||
		urlOpen(i);
 | 
							urlOpen(tag, i);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const struct {
 | 
					static const struct {
 | 
				
			||||||
	const char *command;
 | 
						const char *command;
 | 
				
			||||||
	Handler handler;
 | 
						Handler handler;
 | 
				
			||||||
} COMMANDS[] = {
 | 
					} COMMANDS[] = {
 | 
				
			||||||
 | 
						{ "/join", inputJoin },
 | 
				
			||||||
	{ "/me", inputMe },
 | 
						{ "/me", inputMe },
 | 
				
			||||||
	{ "/names", inputWho },
 | 
						{ "/names", inputWho },
 | 
				
			||||||
	{ "/nick", inputNick },
 | 
						{ "/nick", inputNick },
 | 
				
			||||||
@ -99,27 +121,28 @@ static const struct {
 | 
				
			|||||||
	{ "/quit", inputQuit },
 | 
						{ "/quit", inputQuit },
 | 
				
			||||||
	{ "/topic", inputTopic },
 | 
						{ "/topic", inputTopic },
 | 
				
			||||||
	{ "/url", inputUrl },
 | 
						{ "/url", inputUrl },
 | 
				
			||||||
 | 
						{ "/view", inputView },
 | 
				
			||||||
	{ "/who", inputWho },
 | 
						{ "/who", inputWho },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
static const size_t COMMANDS_LEN = sizeof(COMMANDS) / sizeof(COMMANDS[0]);
 | 
					static const size_t COMMANDS_LEN = sizeof(COMMANDS) / sizeof(COMMANDS[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void input(char *input) {
 | 
					void input(struct Tag tag, char *input) {
 | 
				
			||||||
	if (input[0] != '/') {
 | 
						if (input[0] != '/') {
 | 
				
			||||||
		privmsg(false, input);
 | 
							privmsg(tag, false, input);
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	char *command = strsep(&input, " ");
 | 
						char *command = strsep(&input, " ");
 | 
				
			||||||
	if (input && !input[0]) input = NULL;
 | 
						if (input && !input[0]) input = NULL;
 | 
				
			||||||
	for (size_t i = 0; i < COMMANDS_LEN; ++i) {
 | 
						for (size_t i = 0; i < COMMANDS_LEN; ++i) {
 | 
				
			||||||
		if (strcasecmp(command, COMMANDS[i].command)) continue;
 | 
							if (strcasecmp(command, COMMANDS[i].command)) continue;
 | 
				
			||||||
		COMMANDS[i].handler(input);
 | 
							COMMANDS[i].handler(tag, input);
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	uiFmt("%s isn't a recognized command", command);
 | 
						uiFmt(TAG_DEFAULT, "%s isn't a recognized command", command);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void inputTab(void) {
 | 
					void inputTab(void) {
 | 
				
			||||||
	for (size_t i = 0; i < COMMANDS_LEN; ++i) {
 | 
						for (size_t i = 0; i < COMMANDS_LEN; ++i) {
 | 
				
			||||||
		tabTouch(COMMANDS[i].command);
 | 
							tabTouch(TAG_DEFAULT, COMMANDS[i].command);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										13
									
								
								irc.c
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								irc.c
									
									
									
									
									
								
							@ -39,7 +39,7 @@ static void webirc(const char *pass) {
 | 
				
			|||||||
	if (sp) len = sp - ssh;
 | 
						if (sp) len = sp - ssh;
 | 
				
			||||||
	ircFmt(
 | 
						ircFmt(
 | 
				
			||||||
		"WEBIRC %s %s %.*s %.*s\r\n",
 | 
							"WEBIRC %s %s %.*s %.*s\r\n",
 | 
				
			||||||
		pass, chat.user, len, ssh, len, ssh
 | 
							pass, self.user, len, ssh, len, ssh
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -83,11 +83,8 @@ int ircConnect(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if (webPass) webirc(webPass);
 | 
						if (webPass) webirc(webPass);
 | 
				
			||||||
	if (pass) ircFmt("PASS :%s\r\n", pass);
 | 
						if (pass) ircFmt("PASS :%s\r\n", pass);
 | 
				
			||||||
	ircFmt(
 | 
						ircFmt("NICK %s\r\n", self.nick);
 | 
				
			||||||
		"NICK %s\r\n"
 | 
						ircFmt("USER %s 0 * :%s\r\n", self.user, self.nick);
 | 
				
			||||||
		"USER %s 0 * :%s\r\n",
 | 
					 | 
				
			||||||
		chat.nick, chat.user, chat.nick
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return sock;
 | 
						return sock;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -109,7 +106,7 @@ void ircFmt(const char *format, ...) {
 | 
				
			|||||||
	int len = vasprintf(&buf, format, ap);
 | 
						int len = vasprintf(&buf, format, ap);
 | 
				
			||||||
	va_end(ap);
 | 
						va_end(ap);
 | 
				
			||||||
	if (!buf) err(EX_OSERR, "vasprintf");
 | 
						if (!buf) err(EX_OSERR, "vasprintf");
 | 
				
			||||||
	if (chat.verbose) uiFmt("<<< %.*s", len - 2, buf);
 | 
						if (self.verbose) uiFmt(tagFor("(irc)"), "\00314<<<\3 %.*s", len - 2, buf);
 | 
				
			||||||
	ircWrite(buf, len);
 | 
						ircWrite(buf, len);
 | 
				
			||||||
	free(buf);
 | 
						free(buf);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -129,7 +126,7 @@ void ircRead(void) {
 | 
				
			|||||||
	char *crlf, *line = buf;
 | 
						char *crlf, *line = buf;
 | 
				
			||||||
	while ((crlf = strnstr(line, "\r\n", &buf[len] - line))) {
 | 
						while ((crlf = strnstr(line, "\r\n", &buf[len] - line))) {
 | 
				
			||||||
		crlf[0] = '\0';
 | 
							crlf[0] = '\0';
 | 
				
			||||||
		if (chat.verbose) uiFmt(">>> %s", line);
 | 
							if (self.verbose) uiFmt(tagFor("(irc)"), "\00314>>>\3 %s", line);
 | 
				
			||||||
		handle(line);
 | 
							handle(line);
 | 
				
			||||||
		line = &crlf[2];
 | 
							line = &crlf[2];
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										35
									
								
								tab.c
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								tab.c
									
									
									
									
									
								
							@ -22,6 +22,7 @@
 | 
				
			|||||||
#include "chat.h"
 | 
					#include "chat.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct Entry {
 | 
					static struct Entry {
 | 
				
			||||||
 | 
						size_t tag;
 | 
				
			||||||
	char *word;
 | 
						char *word;
 | 
				
			||||||
	struct Entry *prev;
 | 
						struct Entry *prev;
 | 
				
			||||||
	struct Entry *next;
 | 
						struct Entry *next;
 | 
				
			||||||
@ -46,8 +47,9 @@ static void touch(struct Entry *entry) {
 | 
				
			|||||||
	prepend(entry);
 | 
						prepend(entry);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void tabTouch(const char *word) {
 | 
					void tabTouch(struct Tag tag, const char *word) {
 | 
				
			||||||
	for (struct Entry *entry = head; entry; entry = entry->next) {
 | 
						for (struct Entry *entry = head; entry; entry = entry->next) {
 | 
				
			||||||
 | 
							if (entry->tag != tag.id) continue;
 | 
				
			||||||
		if (strcmp(entry->word, word)) continue;
 | 
							if (strcmp(entry->word, word)) continue;
 | 
				
			||||||
		touch(entry);
 | 
							touch(entry);
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
@ -55,20 +57,28 @@ void tabTouch(const char *word) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	struct Entry *entry = malloc(sizeof(*entry));
 | 
						struct Entry *entry = malloc(sizeof(*entry));
 | 
				
			||||||
	if (!entry) err(EX_OSERR, "malloc");
 | 
						if (!entry) err(EX_OSERR, "malloc");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						entry->tag = tag.id;
 | 
				
			||||||
	entry->word = strdup(word);
 | 
						entry->word = strdup(word);
 | 
				
			||||||
 | 
						if (!entry->word) err(EX_OSERR, "strdup");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	prepend(entry);
 | 
						prepend(entry);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void tabReplace(const char *prev, const char *next) {
 | 
					void tabReplace(const char *prev, const char *next) {
 | 
				
			||||||
	tabTouch(prev);
 | 
						for (struct Entry *entry = head; entry; entry = entry->next) {
 | 
				
			||||||
	free(head->word);
 | 
							if (strcmp(entry->word, prev)) continue;
 | 
				
			||||||
	head->word = strdup(next);
 | 
							free(entry->word);
 | 
				
			||||||
 | 
							entry->word = strdup(next);
 | 
				
			||||||
 | 
							if (!entry->word) err(EX_OSERR, "strdup");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct Entry *match;
 | 
					static struct Entry *match;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void tabRemove(const char *word) {
 | 
					void tabRemove(struct Tag tag, const char *word) {
 | 
				
			||||||
	for (struct Entry *entry = head; entry; entry = entry->next) {
 | 
						for (struct Entry *entry = head; entry; entry = entry->next) {
 | 
				
			||||||
 | 
							if (tag.id != TAG_ALL.id && entry->tag != tag.id) continue;
 | 
				
			||||||
		if (strcmp(entry->word, word)) continue;
 | 
							if (strcmp(entry->word, word)) continue;
 | 
				
			||||||
		unlink(entry);
 | 
							unlink(entry);
 | 
				
			||||||
		if (match == entry) match = entry->next;
 | 
							if (match == entry) match = entry->next;
 | 
				
			||||||
@ -78,17 +88,28 @@ void tabRemove(const char *word) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const char *tabNext(const char *prefix) {
 | 
					void tabClear(struct Tag tag) {
 | 
				
			||||||
 | 
						for (struct Entry *entry = head; entry; entry = entry->next) {
 | 
				
			||||||
 | 
							if (entry->tag != tag.id) continue;
 | 
				
			||||||
 | 
							unlink(entry);
 | 
				
			||||||
 | 
							if (match == entry) match = entry->next;
 | 
				
			||||||
 | 
							free(entry->word);
 | 
				
			||||||
 | 
							free(entry);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const char *tabNext(struct Tag tag, const char *prefix) {
 | 
				
			||||||
	size_t len = strlen(prefix);
 | 
						size_t len = strlen(prefix);
 | 
				
			||||||
	struct Entry *start = (match ? match->next : head);
 | 
						struct Entry *start = (match ? match->next : head);
 | 
				
			||||||
	for (struct Entry *entry = start; entry; entry = entry->next) {
 | 
						for (struct Entry *entry = start; entry; entry = entry->next) {
 | 
				
			||||||
 | 
							if (entry->tag != TAG_DEFAULT.id && entry->tag != tag.id) continue;
 | 
				
			||||||
		if (strncasecmp(entry->word, prefix, len)) continue;
 | 
							if (strncasecmp(entry->word, prefix, len)) continue;
 | 
				
			||||||
		match = entry;
 | 
							match = entry;
 | 
				
			||||||
		return entry->word;
 | 
							return entry->word;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (!match) return NULL;
 | 
						if (!match) return NULL;
 | 
				
			||||||
	match = NULL;
 | 
						match = NULL;
 | 
				
			||||||
	return tabNext(prefix);
 | 
						return tabNext(tag, prefix);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void tabAccept(void) {
 | 
					void tabAccept(void) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										77
									
								
								tag.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								tag.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					/* Copyright (C) 2018  Curtis McEnroe <june@causal.agency>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This program is free software: you can redistribute it and/or modify
 | 
				
			||||||
 | 
					 * it under the terms of the GNU Affero General Public License as published by
 | 
				
			||||||
 | 
					 * the Free Software Foundation, either version 3 of the License, or
 | 
				
			||||||
 | 
					 * (at your option) any later version.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This program is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
				
			||||||
 | 
					 * GNU Affero General Public License for more details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * You should have received a copy of the GNU Affero General Public License
 | 
				
			||||||
 | 
					 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <err.h>
 | 
				
			||||||
 | 
					#include <stdlib.h>
 | 
				
			||||||
 | 
					#include <string.h>
 | 
				
			||||||
 | 
					#include <sysexits.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "chat.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const struct Tag TAG_ALL = { (size_t)-1, NULL };
 | 
				
			||||||
 | 
					const struct Tag TAG_DEFAULT = { 0, "(status)" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct {
 | 
				
			||||||
 | 
						char *name[TAGS_LEN];
 | 
				
			||||||
 | 
						size_t len;
 | 
				
			||||||
 | 
						size_t gap;
 | 
				
			||||||
 | 
					} tags = {
 | 
				
			||||||
 | 
						.name = { "(status)" },
 | 
				
			||||||
 | 
						.len = 1,
 | 
				
			||||||
 | 
						.gap = 1,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct Tag Tag(size_t id) {
 | 
				
			||||||
 | 
						return (struct Tag) { id, tags.name[id] };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Tag tagName(const char *name) {
 | 
				
			||||||
 | 
						for (size_t id = 0; id < tags.len; ++id) {
 | 
				
			||||||
 | 
							if (!tags.name[id] || 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;
 | 
				
			||||||
 | 
						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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										227
									
								
								ui.c
									
									
									
									
									
								
							
							
						
						
									
										227
									
								
								ui.c
									
									
									
									
									
								
							@ -95,13 +95,19 @@ static int logHeight(void) {
 | 
				
			|||||||
	return LINES - 4;
 | 
						return LINES - 4;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct {
 | 
					struct View {
 | 
				
			||||||
	WINDOW *topic;
 | 
						WINDOW *topic;
 | 
				
			||||||
	WINDOW *log;
 | 
						WINDOW *log;
 | 
				
			||||||
	WINDOW *input;
 | 
					 | 
				
			||||||
	bool hide;
 | 
					 | 
				
			||||||
	bool mark;
 | 
					 | 
				
			||||||
	int scroll;
 | 
						int scroll;
 | 
				
			||||||
 | 
						bool mark;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct {
 | 
				
			||||||
 | 
						bool hide;
 | 
				
			||||||
 | 
						WINDOW *input;
 | 
				
			||||||
 | 
						struct Tag tag;
 | 
				
			||||||
 | 
						struct View views[TAGS_LEN];
 | 
				
			||||||
 | 
						size_t len;
 | 
				
			||||||
} ui;
 | 
					} ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void uiInit(void) {
 | 
					void uiInit(void) {
 | 
				
			||||||
@ -113,14 +119,7 @@ void uiInit(void) {
 | 
				
			|||||||
	focusEnable();
 | 
						focusEnable();
 | 
				
			||||||
	colorInit();
 | 
						colorInit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ui.topic = newpad(2, TOPIC_COLS);
 | 
						ui.tag = TAG_DEFAULT;
 | 
				
			||||||
	mvwhline(ui.topic, 1, 0, ACS_HLINE, TOPIC_COLS);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	ui.log = newpad(LOG_LINES, COLS);
 | 
					 | 
				
			||||||
	wsetscrreg(ui.log, 0, LOG_LINES - 1);
 | 
					 | 
				
			||||||
	scrollok(ui.log, true);
 | 
					 | 
				
			||||||
	wmove(ui.log, LOG_LINES - logHeight() - 1, 0);
 | 
					 | 
				
			||||||
	ui.scroll = LOG_LINES;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ui.input = newpad(2, INPUT_COLS);
 | 
						ui.input = newpad(2, INPUT_COLS);
 | 
				
			||||||
	mvwhline(ui.input, 0, 0, ACS_HLINE, INPUT_COLS);
 | 
						mvwhline(ui.input, 0, 0, ACS_HLINE, INPUT_COLS);
 | 
				
			||||||
@ -130,11 +129,6 @@ void uiInit(void) {
 | 
				
			|||||||
	nodelay(ui.input, true);
 | 
						nodelay(ui.input, true);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void uiResize(void) {
 | 
					 | 
				
			||||||
	wresize(ui.log, LOG_LINES, COLS);
 | 
					 | 
				
			||||||
	wmove(ui.log, LOG_LINES - 1, COLS - 1);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void uiHide(void) {
 | 
					void uiHide(void) {
 | 
				
			||||||
	ui.hide = true;
 | 
						ui.hide = true;
 | 
				
			||||||
	endwin();
 | 
						endwin();
 | 
				
			||||||
@ -149,17 +143,46 @@ void uiExit(void) {
 | 
				
			|||||||
	);
 | 
						);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
							wresize(view->log, LOG_LINES, COLS);
 | 
				
			||||||
 | 
							wmove(view->log, LOG_LINES - 1, COLS - 1);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void uiDraw(void) {
 | 
					void uiDraw(void) {
 | 
				
			||||||
	if (ui.hide) return;
 | 
						if (ui.hide) return;
 | 
				
			||||||
 | 
						struct View *view = uiView(ui.tag);
 | 
				
			||||||
	pnoutrefresh(
 | 
						pnoutrefresh(
 | 
				
			||||||
		ui.topic,
 | 
							view->topic,
 | 
				
			||||||
		0, 0,
 | 
							0, 0,
 | 
				
			||||||
		0, 0,
 | 
							0, 0,
 | 
				
			||||||
		1, lastCol()
 | 
							1, lastCol()
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
	pnoutrefresh(
 | 
						pnoutrefresh(
 | 
				
			||||||
		ui.log,
 | 
							view->log,
 | 
				
			||||||
		ui.scroll - logHeight(), 0,
 | 
							view->scroll - logHeight(), 0,
 | 
				
			||||||
		2, 0,
 | 
							2, 0,
 | 
				
			||||||
		lastLine() - 2, lastCol()
 | 
							lastLine() - 2, lastCol()
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
@ -178,6 +201,16 @@ static void uiRedraw(void) {
 | 
				
			|||||||
	clearok(curscr, true);
 | 
						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);
 | 
				
			||||||
 | 
						touchwin(view->log);
 | 
				
			||||||
 | 
						ui.tag = tag;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void uiBeep(void) {
 | 
					void uiBeep(void) {
 | 
				
			||||||
	beep(); // always be beeping
 | 
						beep(); // always be beeping
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -276,93 +309,133 @@ static void addIRC(WINDOW *win, const wchar_t *str) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void uiTopic(const wchar_t *topic) {
 | 
					void uiTopic(struct Tag tag, const char *topic) {
 | 
				
			||||||
	wmove(ui.topic, 0, 0);
 | 
					 | 
				
			||||||
	addIRC(ui.topic, topic);
 | 
					 | 
				
			||||||
	wclrtoeol(ui.topic);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void uiTopicStr(const char *topic) {
 | 
					 | 
				
			||||||
	wchar_t *wcs = ambstowcs(topic);
 | 
						wchar_t *wcs = ambstowcs(topic);
 | 
				
			||||||
	if (!wcs) err(EX_DATAERR, "ambstowcs");
 | 
						if (!wcs) err(EX_DATAERR, "ambstowcs");
 | 
				
			||||||
	uiTopic(wcs);
 | 
						struct View *view = uiView(tag);
 | 
				
			||||||
 | 
						wmove(view->topic, 0, 0);
 | 
				
			||||||
 | 
						addIRC(view->topic, wcs);
 | 
				
			||||||
 | 
						wclrtoeol(view->topic);
 | 
				
			||||||
	free(wcs);
 | 
						free(wcs);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void uiLog(const wchar_t *line) {
 | 
					void uiLog(struct Tag tag, const wchar_t *line) {
 | 
				
			||||||
	waddch(ui.log, '\n');
 | 
						struct View *view = uiView(tag);
 | 
				
			||||||
	if (ui.mark) {
 | 
						waddch(view->log, '\n');
 | 
				
			||||||
		waddch(ui.log, '\n');
 | 
						if (view->mark) {
 | 
				
			||||||
		ui.mark = false;
 | 
							waddch(view->log, '\n');
 | 
				
			||||||
 | 
							view->mark = false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	addIRC(ui.log, line);
 | 
						addIRC(view->log, line);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void uiFmt(const wchar_t *format, ...) {
 | 
					void uiFmt(struct Tag tag, const wchar_t *format, ...) {
 | 
				
			||||||
	wchar_t *buf;
 | 
						wchar_t *buf;
 | 
				
			||||||
	va_list ap;
 | 
						va_list ap;
 | 
				
			||||||
	va_start(ap, format);
 | 
						va_start(ap, format);
 | 
				
			||||||
	vaswprintf(&buf, format, ap);
 | 
						vaswprintf(&buf, format, ap);
 | 
				
			||||||
	va_end(ap);
 | 
						va_end(ap);
 | 
				
			||||||
	if (!buf) err(EX_OSERR, "vaswprintf");
 | 
						if (!buf) err(EX_OSERR, "vaswprintf");
 | 
				
			||||||
	uiLog(buf);
 | 
						uiLog(tag, buf);
 | 
				
			||||||
	free(buf);
 | 
						free(buf);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void logUp(void) {
 | 
					static void logUp(void) {
 | 
				
			||||||
	if (ui.scroll == logHeight()) return;
 | 
						struct View *view = uiView(ui.tag);
 | 
				
			||||||
	if (ui.scroll == LOG_LINES) ui.mark = true;
 | 
						if (view->scroll == logHeight()) return;
 | 
				
			||||||
	ui.scroll = MAX(ui.scroll - logHeight() / 2, logHeight());
 | 
						if (view->scroll == LOG_LINES) view->mark = true;
 | 
				
			||||||
 | 
						view->scroll = MAX(view->scroll - logHeight() / 2, logHeight());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
static void logDown(void) {
 | 
					static void logDown(void) {
 | 
				
			||||||
	if (ui.scroll == LOG_LINES) return;
 | 
						struct View *view = uiView(ui.tag);
 | 
				
			||||||
	ui.scroll = MIN(ui.scroll + logHeight() / 2, LOG_LINES);
 | 
						if (view->scroll == LOG_LINES) return;
 | 
				
			||||||
	if (ui.scroll == LOG_LINES) ui.mark = false;
 | 
						view->scroll = MIN(view->scroll + logHeight() / 2, LOG_LINES);
 | 
				
			||||||
 | 
						if (view->scroll == LOG_LINES) view->mark = false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool keyChar(wint_t ch) {
 | 
					static bool keyChar(wchar_t ch) {
 | 
				
			||||||
	static bool esc, csi;
 | 
						static bool esc, csi;
 | 
				
			||||||
	bool update = false;
 | 
						if (ch == L'\33') {
 | 
				
			||||||
	switch (ch) {
 | 
							esc = true;
 | 
				
			||||||
		break; case CTRL('L'): uiRedraw();
 | 
							return false;
 | 
				
			||||||
		break; case CTRL('['): esc = true; return false;
 | 
						}
 | 
				
			||||||
		break; case L'\b':     update = edit(esc, false, L'\b');
 | 
						if (esc && ch == L'[') {
 | 
				
			||||||
		break; case L'\177':   update = edit(esc, false, L'\b');
 | 
							esc = false;
 | 
				
			||||||
		break; case L'\t':     update = edit(esc, false, L'\t');
 | 
							csi = true;
 | 
				
			||||||
		break; case L'\n':     update = edit(esc, false, L'\n');
 | 
							return false;
 | 
				
			||||||
		break; default: {
 | 
						}
 | 
				
			||||||
			if (esc && ch == L'[') {
 | 
						if (csi) {
 | 
				
			||||||
				csi = true;
 | 
							if (ch == L'O') uiView(ui.tag)->mark = true;
 | 
				
			||||||
				return false;
 | 
							if (ch == L'I') uiView(ui.tag)->mark = false;
 | 
				
			||||||
			} else if (csi) {
 | 
							csi = false;
 | 
				
			||||||
				if (ch == L'O') ui.mark = true;
 | 
							return false;
 | 
				
			||||||
				if (ch == L'I') ui.mark = false;
 | 
						}
 | 
				
			||||||
			} else if (iswcntrl(ch)) {
 | 
						if (ch == L'\177') ch = L'\b';
 | 
				
			||||||
				update = edit(esc, true, UNCTRL(ch));
 | 
					
 | 
				
			||||||
			} else {
 | 
						bool update = true;
 | 
				
			||||||
				update = edit(esc, false, ch);
 | 
						if (esc) {
 | 
				
			||||||
 | 
							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; default: {
 | 
				
			||||||
 | 
									update = false;
 | 
				
			||||||
 | 
									if (ch >= L'0' && ch <= L'9') {
 | 
				
			||||||
 | 
										struct Tag tag = tagNum(ch - L'0');
 | 
				
			||||||
 | 
										if (tag.name) uiFocus(tag);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							esc = false;
 | 
				
			||||||
 | 
							return update;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	esc = false;
 | 
					
 | 
				
			||||||
	csi = false;
 | 
						switch (ch) {
 | 
				
			||||||
	return update;
 | 
							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; default: {
 | 
				
			||||||
 | 
								if (!iswprint(ch)) return false;
 | 
				
			||||||
 | 
								edit(ui.tag, EDIT_INSERT, ch);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool keyCode(wint_t ch) {
 | 
					static bool keyCode(wchar_t ch) {
 | 
				
			||||||
	switch (ch) {
 | 
						switch (ch) {
 | 
				
			||||||
		break; case KEY_RESIZE:    uiResize();
 | 
							break; case KEY_RESIZE:    uiResize(); return false;
 | 
				
			||||||
		break; case KEY_PPAGE:     logUp();
 | 
							break; case KEY_PPAGE:     logUp(); return false;
 | 
				
			||||||
		break; case KEY_NPAGE:     logDown();
 | 
							break; case KEY_NPAGE:     logDown(); return false;
 | 
				
			||||||
		break; case KEY_LEFT:      return edit(false, true, 'B');
 | 
							break; case KEY_LEFT:      edit(ui.tag, EDIT_LEFT, ch);
 | 
				
			||||||
		break; case KEY_RIGHT:     return edit(false, true, 'F');
 | 
							break; case KEY_RIGHT:     edit(ui.tag, EDIT_RIGHT, ch);
 | 
				
			||||||
		break; case KEY_HOME:      return edit(false, true, 'A');
 | 
							break; case KEY_HOME:      edit(ui.tag, EDIT_HOME, ch);
 | 
				
			||||||
		break; case KEY_END:       return edit(false, true, 'E');
 | 
							break; case KEY_END:       edit(ui.tag, EDIT_END, ch);
 | 
				
			||||||
		break; case KEY_DC:        return edit(false, true, 'D');
 | 
							break; case KEY_DC:        edit(ui.tag, EDIT_DELETE, ch);
 | 
				
			||||||
		break; case KEY_BACKSPACE: return edit(false, false, '\b');
 | 
							break; case KEY_BACKSPACE: edit(ui.tag, EDIT_BACKSPACE, ch);
 | 
				
			||||||
		break; case KEY_ENTER:     return edit(false, false, '\n');
 | 
							break; case KEY_ENTER:     edit(ui.tag, EDIT_ENTER, ch);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return false;
 | 
						return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void uiRead(void) {
 | 
					void uiRead(void) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										50
									
								
								url.c
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								url.c
									
									
									
									
									
								
							@ -30,40 +30,56 @@ static const char *SCHEMES[] = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
static const size_t SCHEMES_LEN = sizeof(SCHEMES) / sizeof(SCHEMES[0]);
 | 
					static const size_t SCHEMES_LEN = sizeof(SCHEMES) / sizeof(SCHEMES[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum { RING_LEN = 16 };
 | 
					struct Entry {
 | 
				
			||||||
static char *ring[RING_LEN];
 | 
						size_t tag;
 | 
				
			||||||
static size_t last;
 | 
						char *url;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum { RING_LEN = 32 };
 | 
				
			||||||
static_assert(!(RING_LEN & (RING_LEN - 1)), "power of two RING_LEN");
 | 
					static_assert(!(RING_LEN & (RING_LEN - 1)), "power of two RING_LEN");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void push(const char *url, size_t len) {
 | 
					static struct {
 | 
				
			||||||
	free(ring[last]);
 | 
						struct Entry buf[RING_LEN];
 | 
				
			||||||
	ring[last++] = strndup(url, len);
 | 
						size_t end;
 | 
				
			||||||
	last &= RING_LEN - 1;
 | 
					} ring;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void push(struct Tag tag, const char *url, size_t len) {
 | 
				
			||||||
 | 
						free(ring.buf[ring.end].url);
 | 
				
			||||||
 | 
						ring.buf[ring.end].tag = tag.id;
 | 
				
			||||||
 | 
						ring.buf[ring.end].url = strndup(url, len);
 | 
				
			||||||
 | 
						if (!ring.buf[ring.end].url) err(EX_OSERR, "strndup");
 | 
				
			||||||
 | 
						ring.end = (ring.end + 1) & (RING_LEN - 1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void urlScan(const char *str) {
 | 
					void urlScan(struct Tag tag, const char *str) {
 | 
				
			||||||
	while (str[0]) {
 | 
						while (str[0]) {
 | 
				
			||||||
		size_t len = 1;
 | 
							size_t len = 1;
 | 
				
			||||||
		for (size_t i = 0; i < SCHEMES_LEN; ++i) {
 | 
							for (size_t i = 0; i < SCHEMES_LEN; ++i) {
 | 
				
			||||||
			if (strncmp(str, SCHEMES[i], strlen(SCHEMES[i]))) continue;
 | 
								if (strncmp(str, SCHEMES[i], strlen(SCHEMES[i]))) continue;
 | 
				
			||||||
			len = strcspn(str, " >\"");
 | 
								len = strcspn(str, " >\"");
 | 
				
			||||||
			push(str, len);
 | 
								push(tag, str, len);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		str = &str[len];
 | 
							str = &str[len];
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void urlList(void) {
 | 
					void urlList(struct Tag tag) {
 | 
				
			||||||
	uiHide();
 | 
						uiHide();
 | 
				
			||||||
	for (size_t i = 0; i < RING_LEN; ++i) {
 | 
						for (size_t i = 0; i < RING_LEN; ++i) {
 | 
				
			||||||
		char *url = ring[(i + last) & (RING_LEN - 1)];
 | 
							struct Entry entry = ring.buf[(ring.end + i) & (RING_LEN - 1)];
 | 
				
			||||||
		if (url) printf("%s\n", url);
 | 
							if (!entry.url || entry.tag != tag.id) continue;
 | 
				
			||||||
 | 
							printf("%s\n", entry.url);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void urlOpen(size_t i) {
 | 
					void urlOpen(struct Tag tag, size_t fromEnd) {
 | 
				
			||||||
	char *url = ring[(last - i) & (RING_LEN - 1)];
 | 
						size_t count = 0;
 | 
				
			||||||
	if (!url) return;
 | 
						for (size_t i = 0; i < RING_LEN; ++i) {
 | 
				
			||||||
	char *argv[] = { "open", url, NULL };
 | 
							struct Entry entry = ring.buf[(ring.end - i) & (RING_LEN - 1)];
 | 
				
			||||||
	spawn(argv);
 | 
							if (!entry.url || entry.tag != tag.id) continue;
 | 
				
			||||||
 | 
							if (++count != fromEnd) continue;
 | 
				
			||||||
 | 
							char *argv[] = { "open", entry.url, NULL };
 | 
				
			||||||
 | 
							spawn(argv);
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user