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
 | 
			
		||||
LDFLAGS += -L/usr/local/lib -L/usr/local/opt/libressl/lib
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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.
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	edit.c      Line editing
 | 
			
		||||
	irc.c       TLS client connection
 | 
			
		||||
	input.c     Input command handling
 | 
			
		||||
	handle.c    Incoming command handling
 | 
			
		||||
	tab.c       Tab-complete
 | 
			
		||||
	url.c       URL detection
 | 
			
		||||
	pls.c       Functions which should not have to be written
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										43
									
								
								chat.c
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								chat.c
									
									
									
									
									
								
							@ -29,6 +29,22 @@
 | 
			
		||||
 | 
			
		||||
#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 {
 | 
			
		||||
	struct {
 | 
			
		||||
		struct pollfd ui;
 | 
			
		||||
@ -44,7 +60,7 @@ static union {
 | 
			
		||||
 | 
			
		||||
void spawn(char *const argv[]) {
 | 
			
		||||
	if (fds.pipe.events) {
 | 
			
		||||
		uiLog(L"spawn: existing pipe");
 | 
			
		||||
		uiLog(TAG_DEFAULT, L"spawn: existing pipe");
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -77,7 +93,7 @@ static void pipeRead(void) {
 | 
			
		||||
	if (len) {
 | 
			
		||||
		buf[len] = '\0';
 | 
			
		||||
		len = strcspn(buf, "\n");
 | 
			
		||||
		uiFmt("%.*s", (int)len, buf);
 | 
			
		||||
		uiFmt(TAG_DEFAULT, "%.*s", (int)len, buf);
 | 
			
		||||
	} else {
 | 
			
		||||
		close(fds.pipe.fd);
 | 
			
		||||
		fds.pipe.events = 0;
 | 
			
		||||
@ -108,15 +124,15 @@ static void sigchld(int sig) {
 | 
			
		||||
	pid_t pid = wait(&status);
 | 
			
		||||
	if (pid < 0) err(EX_OSERR, "wait");
 | 
			
		||||
	if (WIFEXITED(status) && WEXITSTATUS(status)) {
 | 
			
		||||
		uiFmt("spawn: exit %d", WEXITSTATUS(status));
 | 
			
		||||
		uiFmt(TAG_DEFAULT, "spawn: exit %d", WEXITSTATUS(status));
 | 
			
		||||
	} else if (WIFSIGNALED(status)) {
 | 
			
		||||
		uiFmt("spawn: signal %d", WTERMSIG(status));
 | 
			
		||||
		uiFmt(TAG_DEFAULT, "spawn: signal %d", WTERMSIG(status));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void sigint(int sig) {
 | 
			
		||||
	(void)sig;
 | 
			
		||||
	input("/quit");
 | 
			
		||||
	input(TAG_DEFAULT, "/quit");
 | 
			
		||||
	uiExit();
 | 
			
		||||
	exit(EX_OK);
 | 
			
		||||
}
 | 
			
		||||
@ -129,7 +145,7 @@ static char *prompt(const char *prompt) {
 | 
			
		||||
		fflush(stdout);
 | 
			
		||||
 | 
			
		||||
		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 (len < 2) continue;
 | 
			
		||||
 | 
			
		||||
@ -149,25 +165,24 @@ int main(int argc, char *argv[]) {
 | 
			
		||||
		switch (opt) {
 | 
			
		||||
			break; case 'W': webirc = optarg;
 | 
			
		||||
			break; case 'h': host = strdup(optarg);
 | 
			
		||||
			break; case 'j': chat.join = strdup(optarg);
 | 
			
		||||
			break; case 'n': chat.nick = strdup(optarg);
 | 
			
		||||
			break; case 'j': selfJoin(optarg);
 | 
			
		||||
			break; case 'n': selfNick(optarg);
 | 
			
		||||
			break; case 'p': port = optarg;
 | 
			
		||||
			break; case 'u': chat.user = strdup(optarg);
 | 
			
		||||
			break; case 'v': chat.verbose = true;
 | 
			
		||||
			break; case 'u': selfUser(optarg);
 | 
			
		||||
			break; case 'v': self.verbose = true;
 | 
			
		||||
			break; case 'w': pass = optarg;
 | 
			
		||||
			break; default:  return EX_USAGE;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!host) host = prompt("Host: ");
 | 
			
		||||
	if (!chat.join) chat.join = prompt("Join: ");
 | 
			
		||||
	if (!chat.nick) chat.nick = prompt("Name: ");
 | 
			
		||||
	if (!chat.user) chat.user = strdup(chat.nick);
 | 
			
		||||
	if (!self.nick) self.nick = prompt("Name: ");
 | 
			
		||||
	if (!self.user) selfUser(self.nick);
 | 
			
		||||
 | 
			
		||||
	inputTab();
 | 
			
		||||
 | 
			
		||||
	uiInit();
 | 
			
		||||
	uiLog(L"Traveling...");
 | 
			
		||||
	uiLog(TAG_DEFAULT, L"Traveling...");
 | 
			
		||||
	uiDraw();
 | 
			
		||||
 | 
			
		||||
	fds.irc.fd = ircConnect(host, port, pass, webirc);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										102
									
								
								chat.h
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								chat.h
									
									
									
									
									
								
							@ -30,18 +30,23 @@ struct {
 | 
			
		||||
	char *nick;
 | 
			
		||||
	char *user;
 | 
			
		||||
	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(
 | 
			
		||||
	const char *host, const char *port, const char *pass, const char *webPass
 | 
			
		||||
);
 | 
			
		||||
void ircRead(void);
 | 
			
		||||
void ircWrite(const char *ptr, size_t len);
 | 
			
		||||
struct Tag {
 | 
			
		||||
	size_t id;
 | 
			
		||||
	const char *name;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
__attribute__((format(printf, 1, 2)))
 | 
			
		||||
void ircFmt(const char *format, ...);
 | 
			
		||||
enum { TAGS_LEN = 256 };
 | 
			
		||||
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 {
 | 
			
		||||
	IRC_BOLD      = 002,
 | 
			
		||||
@ -52,47 +57,72 @@ enum {
 | 
			
		||||
	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 uiHide(void);
 | 
			
		||||
void uiExit(void);
 | 
			
		||||
void uiDraw(void);
 | 
			
		||||
void uiBeep(void);
 | 
			
		||||
void uiRead(void);
 | 
			
		||||
void uiTopic(const wchar_t *topic);
 | 
			
		||||
void uiTopicStr(const char *topic);
 | 
			
		||||
void uiLog(const wchar_t *line);
 | 
			
		||||
void uiFmt(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
 | 
			
		||||
void uiFocus(struct Tag tag);
 | 
			
		||||
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, ...);
 | 
			
		||||
 | 
			
		||||
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 *editTail(void);
 | 
			
		||||
bool edit(bool meta, bool ctrl, wchar_t ch);
 | 
			
		||||
 | 
			
		||||
void handle(char *line);
 | 
			
		||||
 | 
			
		||||
void inputTab(void);
 | 
			
		||||
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 tabTouch(struct Tag tag, const char *word);
 | 
			
		||||
void tabRemove(struct Tag tag, const char *word);
 | 
			
		||||
void tabClear(struct Tag tag);
 | 
			
		||||
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 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);
 | 
			
		||||
char *awcstombs(const wchar_t *src);
 | 
			
		||||
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,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// XXX: editTail must always be called after editHead.
 | 
			
		||||
static wchar_t tail;
 | 
			
		||||
const wchar_t *editHead(void) {
 | 
			
		||||
	tail = *line.ptr;
 | 
			
		||||
@ -41,8 +42,9 @@ const wchar_t *editHead(void) {
 | 
			
		||||
	return line.buf;
 | 
			
		||||
}
 | 
			
		||||
const wchar_t *editTail(void) {
 | 
			
		||||
	*line.ptr = tail;
 | 
			
		||||
	if (tail) *line.ptr = tail;
 | 
			
		||||
	*line.end = L'\0';
 | 
			
		||||
	tail = L'\0';
 | 
			
		||||
	return line.ptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -52,13 +54,29 @@ static void left(void) {
 | 
			
		||||
static void right(void) {
 | 
			
		||||
	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) {
 | 
			
		||||
	line.ptr = line.end;
 | 
			
		||||
static void foreWord(void) {
 | 
			
		||||
	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) {
 | 
			
		||||
	if (line.ptr == line.buf) return;
 | 
			
		||||
	if (line.ptr != line.end) {
 | 
			
		||||
@ -73,41 +91,6 @@ static void delete(void) {
 | 
			
		||||
	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) {
 | 
			
		||||
	wchar_t *from = line.ptr;
 | 
			
		||||
	backWord();
 | 
			
		||||
@ -121,12 +104,9 @@ static void killForeWord(void) {
 | 
			
		||||
	line.end -= line.ptr - from;
 | 
			
		||||
	line.ptr = from;
 | 
			
		||||
}
 | 
			
		||||
static void killLine(void) {
 | 
			
		||||
	line.end = line.ptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *prefix;
 | 
			
		||||
static void complete(void) {
 | 
			
		||||
static void complete(struct Tag tag) {
 | 
			
		||||
	if (!line.tab) {
 | 
			
		||||
		editHead();
 | 
			
		||||
		line.tab = wcsrchr(line.buf, L' ');
 | 
			
		||||
@ -136,7 +116,7 @@ static void complete(void) {
 | 
			
		||||
		editTail();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const char *next = tabNext(prefix);
 | 
			
		||||
	const char *next = tabNext(tag, prefix);
 | 
			
		||||
	if (!next) return;
 | 
			
		||||
 | 
			
		||||
	wchar_t *wcs = ambstowcs(next);
 | 
			
		||||
@ -179,52 +159,37 @@ static void reject(void) {
 | 
			
		||||
	tabReject();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool editMeta(wchar_t ch) {
 | 
			
		||||
	switch (ch) {
 | 
			
		||||
		break; case L'b':  reject(); backWord();
 | 
			
		||||
		break; case L'f':  reject(); foreWord();
 | 
			
		||||
		break; case L'\b': reject(); killBackWord();
 | 
			
		||||
		break; case L'd':  reject(); killForeWord();
 | 
			
		||||
 | 
			
		||||
		break; default: return false;
 | 
			
		||||
	}
 | 
			
		||||
	return true;
 | 
			
		||||
static void enter(struct Tag tag) {
 | 
			
		||||
	if (line.end == line.buf) return;
 | 
			
		||||
	editTail();
 | 
			
		||||
	char *str = awcstombs(line.buf);
 | 
			
		||||
	if (!str) err(EX_DATAERR, "awcstombs");
 | 
			
		||||
	input(tag, str);
 | 
			
		||||
	free(str);
 | 
			
		||||
	line.ptr = line.buf;
 | 
			
		||||
	line.end = line.buf;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool editCtrl(wchar_t ch) {
 | 
			
		||||
	switch (ch) {
 | 
			
		||||
		break; case L'B': reject(); left();
 | 
			
		||||
		break; case L'F': reject(); right();
 | 
			
		||||
		break; case L'A': reject(); home();
 | 
			
		||||
		break; case L'E': reject(); end();
 | 
			
		||||
		break; case L'D': reject(); delete();
 | 
			
		||||
		break; case L'W': reject(); killBackWord();
 | 
			
		||||
		break; case L'K': reject(); killLine();
 | 
			
		||||
void edit(struct Tag tag, enum Edit op, wchar_t ch) {
 | 
			
		||||
	switch (op) {
 | 
			
		||||
		break; case EDIT_LEFT:  reject(); left();
 | 
			
		||||
		break; case EDIT_RIGHT: reject(); right();
 | 
			
		||||
		break; case EDIT_HOME:  reject(); line.ptr = line.buf;
 | 
			
		||||
		break; case EDIT_END:   reject(); line.ptr = line.end;
 | 
			
		||||
 | 
			
		||||
		break; case L'C': accept(); insert(IRC_COLOR);
 | 
			
		||||
		break; case L'N': accept(); insert(IRC_RESET);
 | 
			
		||||
		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; case EDIT_BACK_WORD: reject(); backWord();
 | 
			
		||||
		break; case EDIT_FORE_WORD: reject(); foreWord();
 | 
			
		||||
 | 
			
		||||
		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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
	char *mesg;
 | 
			
		||||
	shift(prefix, NULL, NULL, NULL, params, 3, 0, NULL, NULL, &mesg);
 | 
			
		||||
	uiLog(L"You can't use that name here");
 | 
			
		||||
	uiFmt("Sheriff says, \"%s\"", mesg);
 | 
			
		||||
	uiLog(L"Type /nick <name> to choose a new one");
 | 
			
		||||
	uiLog(TAG_DEFAULT, L"You can't use that name here");
 | 
			
		||||
	uiFmt(TAG_DEFAULT, "Sheriff says, \"%s\"", mesg);
 | 
			
		||||
	uiLog(TAG_DEFAULT, L"Type /nick <name> to choose a new one");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void handle001(char *prefix, char *params) {
 | 
			
		||||
	char *nick;
 | 
			
		||||
	shift(prefix, NULL, NULL, NULL, params, 1, 0, &nick);
 | 
			
		||||
	if (strcmp(nick, chat.nick)) {
 | 
			
		||||
		free(chat.nick);
 | 
			
		||||
		chat.nick = strdup(nick);
 | 
			
		||||
	}
 | 
			
		||||
	ircFmt("JOIN %s\r\n", chat.join);
 | 
			
		||||
	if (strcmp(nick, self.nick)) selfNick(nick);
 | 
			
		||||
	tabTouch(TAG_DEFAULT, self.nick);
 | 
			
		||||
	if (self.join) ircFmt("JOIN %s\r\n", self.join);
 | 
			
		||||
	uiLog(TAG_DEFAULT, L"You have arrived");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
	char *nick, *user, *chan;
 | 
			
		||||
	shift(prefix, &nick, &user, NULL, params, 1, 0, &chan);
 | 
			
		||||
	struct Tag tag = tagFor(chan);
 | 
			
		||||
	if (isSelf(nick, user)) {
 | 
			
		||||
		tabTouch(TAG_DEFAULT, chan);
 | 
			
		||||
		uiFocus(tag);
 | 
			
		||||
	} else {
 | 
			
		||||
		tabTouch(tag, nick);
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
	);
 | 
			
		||||
	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) {
 | 
			
		||||
	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 (mesg) {
 | 
			
		||||
		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
 | 
			
		||||
		);
 | 
			
		||||
	} else {
 | 
			
		||||
		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
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
	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) {
 | 
			
		||||
	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));
 | 
			
		||||
	if (mesg) {
 | 
			
		||||
		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
 | 
			
		||||
		);
 | 
			
		||||
	} else {
 | 
			
		||||
		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
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
	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) {
 | 
			
		||||
	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(
 | 
			
		||||
		"The sign in \3%d%s\3 reads, \"%s\"",
 | 
			
		||||
		tag, "The sign in \3%d%s\3 reads, \"%s\"",
 | 
			
		||||
		color(chan), chan, topic
 | 
			
		||||
	);
 | 
			
		||||
	urlScan(topic);
 | 
			
		||||
	uiTopicStr(topic);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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(
 | 
			
		||||
		"\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
 | 
			
		||||
	);
 | 
			
		||||
	urlScan(topic);
 | 
			
		||||
	uiTopicStr(topic);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void handle366(char *prefix, char *params) {
 | 
			
		||||
@ -190,17 +214,20 @@ static void handle366(char *prefix, char *params) {
 | 
			
		||||
	ircFmt("WHO %s\r\n", chan);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FIXME: Track tag?
 | 
			
		||||
static struct {
 | 
			
		||||
	char buf[4096];
 | 
			
		||||
	size_t len;
 | 
			
		||||
} who;
 | 
			
		||||
 | 
			
		||||
static void handle352(char *prefix, char *params) {
 | 
			
		||||
	char *user, *nick;
 | 
			
		||||
	char *chan, *user, *nick;
 | 
			
		||||
	shift(
 | 
			
		||||
		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;
 | 
			
		||||
	int len = snprintf(
 | 
			
		||||
		&who.buf[who.len], cap,
 | 
			
		||||
@ -208,14 +235,14 @@ static void handle352(char *prefix, char *params) {
 | 
			
		||||
		(who.len ? ", " : ""), color(user), nick
 | 
			
		||||
	);
 | 
			
		||||
	if ((size_t)len < cap) who.len += len;
 | 
			
		||||
	tabTouch(nick);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void handle315(char *prefix, char *params) {
 | 
			
		||||
	char *chan;
 | 
			
		||||
	shift(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &chan);
 | 
			
		||||
	struct Tag tag = tagFor(chan);
 | 
			
		||||
	uiFmt(
 | 
			
		||||
		"In \3%d%s\3 are %s",
 | 
			
		||||
		tag, "In \3%d%s\3 are %s",
 | 
			
		||||
		color(chan), chan, who.buf
 | 
			
		||||
	);
 | 
			
		||||
	who.len = 0;
 | 
			
		||||
@ -224,58 +251,58 @@ static void handle315(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);
 | 
			
		||||
	// TODO: Send to tags where prev is in tab.
 | 
			
		||||
	tabReplace(prev, next);
 | 
			
		||||
	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
 | 
			
		||||
	);
 | 
			
		||||
	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];
 | 
			
		||||
	char *ctcp = strsep(&mesg, " ");
 | 
			
		||||
	char *params = strsep(&mesg, "\1");
 | 
			
		||||
	if (strcmp(ctcp, "ACTION")) return;
 | 
			
		||||
	if (!isSelf(nick, user)) tabTouch(tag, nick);
 | 
			
		||||
	urlScan(tag, params);
 | 
			
		||||
	uiFmt(
 | 
			
		||||
		"\3%d* %s\3 %s",
 | 
			
		||||
		tag, "\3%d* %s\3 %s",
 | 
			
		||||
		color(user), nick, params
 | 
			
		||||
	);
 | 
			
		||||
	if (strcmp(user, chat.user)) tabTouch(nick);
 | 
			
		||||
	urlScan(params);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void handlePrivmsg(char *prefix, char *params) {
 | 
			
		||||
	char *nick, *user, *mesg;
 | 
			
		||||
	shift(prefix, &nick, &user, NULL, params, 2, 0, NULL, &mesg);
 | 
			
		||||
	char *nick, *user, *chan, *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') {
 | 
			
		||||
		handleCTCP(nick, user, mesg);
 | 
			
		||||
		handleCTCP(tag, nick, user, mesg);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	bool self = !strcmp(user, chat.user);
 | 
			
		||||
	bool ping = !strncasecmp(mesg, chat.nick, strlen(chat.nick));
 | 
			
		||||
	if (!isSelf(nick, user)) tabTouch(tag, nick);
 | 
			
		||||
	urlScan(tag, mesg);
 | 
			
		||||
	bool ping = !strncasecmp(mesg, self.nick, strlen(self.nick));
 | 
			
		||||
	bool self = isSelf(nick, user);
 | 
			
		||||
	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
 | 
			
		||||
	);
 | 
			
		||||
	if (!self) tabTouch(nick);
 | 
			
		||||
	if (ping) uiBeep();
 | 
			
		||||
	urlScan(mesg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void handleNotice(char *prefix, char *params) {
 | 
			
		||||
	char *nick, *user, *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(
 | 
			
		||||
		"\3%d-%s-\3 %s",
 | 
			
		||||
		tag, "\3%d-%s-\3 %s",
 | 
			
		||||
		color(user), nick, mesg
 | 
			
		||||
	);
 | 
			
		||||
	tabTouch(nick);
 | 
			
		||||
	urlScan(mesg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const struct {
 | 
			
		||||
@ -287,6 +314,8 @@ static const struct {
 | 
			
		||||
	{ "332", handle332 },
 | 
			
		||||
	{ "352", handle352 },
 | 
			
		||||
	{ "366", handle366 },
 | 
			
		||||
	{ "372", handle372 },
 | 
			
		||||
	{ "375", handle372 },
 | 
			
		||||
	{ "432", handle432 },
 | 
			
		||||
	{ "433", handle432 },
 | 
			
		||||
	{ "JOIN", handleJoin },
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										79
									
								
								input.c
									
									
									
									
									
								
							
							
						
						
									
										79
									
								
								input.c
									
									
									
									
									
								
							@ -23,12 +23,13 @@
 | 
			
		||||
 | 
			
		||||
#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;
 | 
			
		||||
	int send;
 | 
			
		||||
	asprintf(
 | 
			
		||||
		&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" : "")
 | 
			
		||||
	);
 | 
			
		||||
	if (!line) err(EX_OSERR, "asprintf");
 | 
			
		||||
@ -37,35 +38,47 @@ static void privmsg(bool action, const char *mesg) {
 | 
			
		||||
	free(line);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
typedef void (*Handler)(char *params);
 | 
			
		||||
typedef void (*Handler)(struct Tag tag, char *params);
 | 
			
		||||
 | 
			
		||||
static void inputMe(char *params) {
 | 
			
		||||
	privmsg(true, params ? params : "");
 | 
			
		||||
static void inputMe(struct Tag tag, char *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, " ");
 | 
			
		||||
	if (nick) {
 | 
			
		||||
		ircFmt("NICK %s\r\n", nick);
 | 
			
		||||
	} else {
 | 
			
		||||
		uiLog(L"/nick requires a name");
 | 
			
		||||
		uiLog(TAG_DEFAULT, L"/nick requires a name");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void inputWho(char *params) {
 | 
			
		||||
	(void)params;
 | 
			
		||||
	ircFmt("WHO %s\r\n", chat.join);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void inputTopic(char *params) {
 | 
			
		||||
	if (params) {
 | 
			
		||||
		ircFmt("TOPIC %s :%s\r\n", chat.join, params);
 | 
			
		||||
static void inputJoin(struct Tag tag, char *params) {
 | 
			
		||||
	(void)tag;
 | 
			
		||||
	char *chan = strsep(¶ms, " ");
 | 
			
		||||
	if (chan) {
 | 
			
		||||
		ircFmt("JOIN %s\r\n", chan);
 | 
			
		||||
	} 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) {
 | 
			
		||||
		ircFmt("QUIT :%s\r\n", params);
 | 
			
		||||
	} 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;
 | 
			
		||||
	urlList();
 | 
			
		||||
	urlList(tag);
 | 
			
		||||
}
 | 
			
		||||
static void inputOpen(char *params) {
 | 
			
		||||
	if (!params) { urlOpen(1); return; }
 | 
			
		||||
static void inputOpen(struct Tag tag, char *params) {
 | 
			
		||||
	if (!params) { urlOpen(tag, 1); return; }
 | 
			
		||||
	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);
 | 
			
		||||
	if (to < from) to = from;
 | 
			
		||||
	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 {
 | 
			
		||||
	const char *command;
 | 
			
		||||
	Handler handler;
 | 
			
		||||
} COMMANDS[] = {
 | 
			
		||||
	{ "/join", inputJoin },
 | 
			
		||||
	{ "/me", inputMe },
 | 
			
		||||
	{ "/names", inputWho },
 | 
			
		||||
	{ "/nick", inputNick },
 | 
			
		||||
@ -99,27 +121,28 @@ static const struct {
 | 
			
		||||
	{ "/quit", inputQuit },
 | 
			
		||||
	{ "/topic", inputTopic },
 | 
			
		||||
	{ "/url", inputUrl },
 | 
			
		||||
	{ "/view", inputView },
 | 
			
		||||
	{ "/who", inputWho },
 | 
			
		||||
};
 | 
			
		||||
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] != '/') {
 | 
			
		||||
		privmsg(false, input);
 | 
			
		||||
		privmsg(tag, false, input);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	char *command = strsep(&input, " ");
 | 
			
		||||
	if (input && !input[0]) input = NULL;
 | 
			
		||||
	for (size_t i = 0; i < COMMANDS_LEN; ++i) {
 | 
			
		||||
		if (strcasecmp(command, COMMANDS[i].command)) continue;
 | 
			
		||||
		COMMANDS[i].handler(input);
 | 
			
		||||
		COMMANDS[i].handler(tag, input);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	uiFmt("%s isn't a recognized command", command);
 | 
			
		||||
	uiFmt(TAG_DEFAULT, "%s isn't a recognized command", command);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void inputTab(void) {
 | 
			
		||||
	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;
 | 
			
		||||
	ircFmt(
 | 
			
		||||
		"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 (pass) ircFmt("PASS :%s\r\n", pass);
 | 
			
		||||
	ircFmt(
 | 
			
		||||
		"NICK %s\r\n"
 | 
			
		||||
		"USER %s 0 * :%s\r\n",
 | 
			
		||||
		chat.nick, chat.user, chat.nick
 | 
			
		||||
	);
 | 
			
		||||
	ircFmt("NICK %s\r\n", self.nick);
 | 
			
		||||
	ircFmt("USER %s 0 * :%s\r\n", self.user, self.nick);
 | 
			
		||||
 | 
			
		||||
	return sock;
 | 
			
		||||
}
 | 
			
		||||
@ -109,7 +106,7 @@ void ircFmt(const char *format, ...) {
 | 
			
		||||
	int len = vasprintf(&buf, format, ap);
 | 
			
		||||
	va_end(ap);
 | 
			
		||||
	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);
 | 
			
		||||
	free(buf);
 | 
			
		||||
}
 | 
			
		||||
@ -129,7 +126,7 @@ void ircRead(void) {
 | 
			
		||||
	char *crlf, *line = buf;
 | 
			
		||||
	while ((crlf = strnstr(line, "\r\n", &buf[len] - line))) {
 | 
			
		||||
		crlf[0] = '\0';
 | 
			
		||||
		if (chat.verbose) uiFmt(">>> %s", line);
 | 
			
		||||
		if (self.verbose) uiFmt(tagFor("(irc)"), "\00314>>>\3 %s", line);
 | 
			
		||||
		handle(line);
 | 
			
		||||
		line = &crlf[2];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										35
									
								
								tab.c
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								tab.c
									
									
									
									
									
								
							@ -22,6 +22,7 @@
 | 
			
		||||
#include "chat.h"
 | 
			
		||||
 | 
			
		||||
static struct Entry {
 | 
			
		||||
	size_t tag;
 | 
			
		||||
	char *word;
 | 
			
		||||
	struct Entry *prev;
 | 
			
		||||
	struct Entry *next;
 | 
			
		||||
@ -46,8 +47,9 @@ static void touch(struct Entry *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) {
 | 
			
		||||
		if (entry->tag != tag.id) continue;
 | 
			
		||||
		if (strcmp(entry->word, word)) continue;
 | 
			
		||||
		touch(entry);
 | 
			
		||||
		return;
 | 
			
		||||
@ -55,20 +57,28 @@ void tabTouch(const char *word) {
 | 
			
		||||
 | 
			
		||||
	struct Entry *entry = malloc(sizeof(*entry));
 | 
			
		||||
	if (!entry) err(EX_OSERR, "malloc");
 | 
			
		||||
 | 
			
		||||
	entry->tag = tag.id;
 | 
			
		||||
	entry->word = strdup(word);
 | 
			
		||||
	if (!entry->word) err(EX_OSERR, "strdup");
 | 
			
		||||
 | 
			
		||||
	prepend(entry);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void tabReplace(const char *prev, const char *next) {
 | 
			
		||||
	tabTouch(prev);
 | 
			
		||||
	free(head->word);
 | 
			
		||||
	head->word = strdup(next);
 | 
			
		||||
	for (struct Entry *entry = head; entry; entry = entry->next) {
 | 
			
		||||
		if (strcmp(entry->word, prev)) continue;
 | 
			
		||||
		free(entry->word);
 | 
			
		||||
		entry->word = strdup(next);
 | 
			
		||||
		if (!entry->word) err(EX_OSERR, "strdup");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
		if (tag.id != TAG_ALL.id && entry->tag != tag.id) continue;
 | 
			
		||||
		if (strcmp(entry->word, word)) continue;
 | 
			
		||||
		unlink(entry);
 | 
			
		||||
		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);
 | 
			
		||||
	struct Entry *start = (match ? match->next : head);
 | 
			
		||||
	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;
 | 
			
		||||
		match = entry;
 | 
			
		||||
		return entry->word;
 | 
			
		||||
	}
 | 
			
		||||
	if (!match) return NULL;
 | 
			
		||||
	match = NULL;
 | 
			
		||||
	return tabNext(prefix);
 | 
			
		||||
	return tabNext(tag, prefix);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct {
 | 
			
		||||
struct View {
 | 
			
		||||
	WINDOW *topic;
 | 
			
		||||
	WINDOW *log;
 | 
			
		||||
	WINDOW *input;
 | 
			
		||||
	bool hide;
 | 
			
		||||
	bool mark;
 | 
			
		||||
	int scroll;
 | 
			
		||||
	bool mark;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct {
 | 
			
		||||
	bool hide;
 | 
			
		||||
	WINDOW *input;
 | 
			
		||||
	struct Tag tag;
 | 
			
		||||
	struct View views[TAGS_LEN];
 | 
			
		||||
	size_t len;
 | 
			
		||||
} ui;
 | 
			
		||||
 | 
			
		||||
void uiInit(void) {
 | 
			
		||||
@ -113,14 +119,7 @@ void uiInit(void) {
 | 
			
		||||
	focusEnable();
 | 
			
		||||
	colorInit();
 | 
			
		||||
 | 
			
		||||
	ui.topic = newpad(2, TOPIC_COLS);
 | 
			
		||||
	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.tag = TAG_DEFAULT;
 | 
			
		||||
 | 
			
		||||
	ui.input = newpad(2, INPUT_COLS);
 | 
			
		||||
	mvwhline(ui.input, 0, 0, ACS_HLINE, INPUT_COLS);
 | 
			
		||||
@ -130,11 +129,6 @@ void uiInit(void) {
 | 
			
		||||
	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) {
 | 
			
		||||
	ui.hide = true;
 | 
			
		||||
	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) {
 | 
			
		||||
	if (ui.hide) return;
 | 
			
		||||
	struct View *view = uiView(ui.tag);
 | 
			
		||||
	pnoutrefresh(
 | 
			
		||||
		ui.topic,
 | 
			
		||||
		view->topic,
 | 
			
		||||
		0, 0,
 | 
			
		||||
		0, 0,
 | 
			
		||||
		1, lastCol()
 | 
			
		||||
	);
 | 
			
		||||
	pnoutrefresh(
 | 
			
		||||
		ui.log,
 | 
			
		||||
		ui.scroll - logHeight(), 0,
 | 
			
		||||
		view->log,
 | 
			
		||||
		view->scroll - logHeight(), 0,
 | 
			
		||||
		2, 0,
 | 
			
		||||
		lastLine() - 2, lastCol()
 | 
			
		||||
	);
 | 
			
		||||
@ -178,6 +201,16 @@ static void uiRedraw(void) {
 | 
			
		||||
	clearok(curscr, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void uiFocus(struct Tag tag) {
 | 
			
		||||
	struct View *view = uiView(ui.tag);
 | 
			
		||||
	view->mark = true;
 | 
			
		||||
	view = uiView(tag);
 | 
			
		||||
	view->mark = false;
 | 
			
		||||
	touchwin(view->topic);
 | 
			
		||||
	touchwin(view->log);
 | 
			
		||||
	ui.tag = tag;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void uiBeep(void) {
 | 
			
		||||
	beep(); // always be beeping
 | 
			
		||||
}
 | 
			
		||||
@ -276,93 +309,133 @@ static void addIRC(WINDOW *win, const wchar_t *str) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void uiTopic(const wchar_t *topic) {
 | 
			
		||||
	wmove(ui.topic, 0, 0);
 | 
			
		||||
	addIRC(ui.topic, topic);
 | 
			
		||||
	wclrtoeol(ui.topic);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void uiTopicStr(const char *topic) {
 | 
			
		||||
void uiTopic(struct Tag tag, const char *topic) {
 | 
			
		||||
	wchar_t *wcs = ambstowcs(topic);
 | 
			
		||||
	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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void uiLog(const wchar_t *line) {
 | 
			
		||||
	waddch(ui.log, '\n');
 | 
			
		||||
	if (ui.mark) {
 | 
			
		||||
		waddch(ui.log, '\n');
 | 
			
		||||
		ui.mark = false;
 | 
			
		||||
void uiLog(struct Tag tag, const wchar_t *line) {
 | 
			
		||||
	struct View *view = uiView(tag);
 | 
			
		||||
	waddch(view->log, '\n');
 | 
			
		||||
	if (view->mark) {
 | 
			
		||||
		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;
 | 
			
		||||
	va_list ap;
 | 
			
		||||
	va_start(ap, format);
 | 
			
		||||
	vaswprintf(&buf, format, ap);
 | 
			
		||||
	va_end(ap);
 | 
			
		||||
	if (!buf) err(EX_OSERR, "vaswprintf");
 | 
			
		||||
	uiLog(buf);
 | 
			
		||||
	uiLog(tag, buf);
 | 
			
		||||
	free(buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void logUp(void) {
 | 
			
		||||
	if (ui.scroll == logHeight()) return;
 | 
			
		||||
	if (ui.scroll == LOG_LINES) ui.mark = true;
 | 
			
		||||
	ui.scroll = MAX(ui.scroll - logHeight() / 2, logHeight());
 | 
			
		||||
	struct View *view = uiView(ui.tag);
 | 
			
		||||
	if (view->scroll == logHeight()) return;
 | 
			
		||||
	if (view->scroll == LOG_LINES) view->mark = true;
 | 
			
		||||
	view->scroll = MAX(view->scroll - logHeight() / 2, logHeight());
 | 
			
		||||
}
 | 
			
		||||
static void logDown(void) {
 | 
			
		||||
	if (ui.scroll == LOG_LINES) return;
 | 
			
		||||
	ui.scroll = MIN(ui.scroll + logHeight() / 2, LOG_LINES);
 | 
			
		||||
	if (ui.scroll == LOG_LINES) ui.mark = false;
 | 
			
		||||
	struct View *view = uiView(ui.tag);
 | 
			
		||||
	if (view->scroll == LOG_LINES) return;
 | 
			
		||||
	view->scroll = MIN(view->scroll + logHeight() / 2, LOG_LINES);
 | 
			
		||||
	if (view->scroll == LOG_LINES) view->mark = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool keyChar(wint_t ch) {
 | 
			
		||||
static bool keyChar(wchar_t ch) {
 | 
			
		||||
	static bool esc, csi;
 | 
			
		||||
	bool update = false;
 | 
			
		||||
	switch (ch) {
 | 
			
		||||
		break; case CTRL('L'): uiRedraw();
 | 
			
		||||
		break; case CTRL('['): esc = true; return false;
 | 
			
		||||
		break; case L'\b':     update = edit(esc, false, L'\b');
 | 
			
		||||
		break; case L'\177':   update = edit(esc, false, L'\b');
 | 
			
		||||
		break; case L'\t':     update = edit(esc, false, L'\t');
 | 
			
		||||
		break; case L'\n':     update = edit(esc, false, L'\n');
 | 
			
		||||
		break; default: {
 | 
			
		||||
			if (esc && ch == L'[') {
 | 
			
		||||
				csi = true;
 | 
			
		||||
				return false;
 | 
			
		||||
			} else if (csi) {
 | 
			
		||||
				if (ch == L'O') ui.mark = true;
 | 
			
		||||
				if (ch == L'I') ui.mark = false;
 | 
			
		||||
			} else if (iswcntrl(ch)) {
 | 
			
		||||
				update = edit(esc, true, UNCTRL(ch));
 | 
			
		||||
			} else {
 | 
			
		||||
				update = edit(esc, false, ch);
 | 
			
		||||
	if (ch == L'\33') {
 | 
			
		||||
		esc = true;
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	if (esc && ch == L'[') {
 | 
			
		||||
		esc = false;
 | 
			
		||||
		csi = true;
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	if (csi) {
 | 
			
		||||
		if (ch == L'O') uiView(ui.tag)->mark = true;
 | 
			
		||||
		if (ch == L'I') uiView(ui.tag)->mark = false;
 | 
			
		||||
		csi = false;
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	if (ch == L'\177') ch = L'\b';
 | 
			
		||||
 | 
			
		||||
	bool update = true;
 | 
			
		||||
	if (esc) {
 | 
			
		||||
		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;
 | 
			
		||||
	return update;
 | 
			
		||||
 | 
			
		||||
	switch (ch) {
 | 
			
		||||
		break; case CTRL(L'L'): uiRedraw(); return false;
 | 
			
		||||
 | 
			
		||||
		break; case CTRL(L'A'): edit(ui.tag, EDIT_HOME, 0);
 | 
			
		||||
		break; case CTRL(L'B'): edit(ui.tag, EDIT_LEFT, 0);
 | 
			
		||||
		break; case CTRL(L'D'): edit(ui.tag, EDIT_DELETE, 0);
 | 
			
		||||
		break; case CTRL(L'E'): edit(ui.tag, EDIT_END, 0);
 | 
			
		||||
		break; case CTRL(L'F'): edit(ui.tag, EDIT_RIGHT, 0);
 | 
			
		||||
		break; case CTRL(L'K'): edit(ui.tag, EDIT_KILL_LINE, 0);
 | 
			
		||||
		break; case CTRL(L'W'): edit(ui.tag, EDIT_KILL_BACK_WORD, 0);
 | 
			
		||||
 | 
			
		||||
		break; case CTRL(L'C'): edit(ui.tag, EDIT_INSERT, IRC_COLOR);
 | 
			
		||||
		break; case CTRL(L'N'): edit(ui.tag, EDIT_INSERT, IRC_RESET);
 | 
			
		||||
		break; case CTRL(L'O'): edit(ui.tag, EDIT_INSERT, IRC_BOLD);
 | 
			
		||||
		break; case CTRL(L'R'): edit(ui.tag, EDIT_INSERT, IRC_COLOR);
 | 
			
		||||
		break; case CTRL(L'T'): edit(ui.tag, EDIT_INSERT, IRC_ITALIC);
 | 
			
		||||
		break; case CTRL(L'U'): edit(ui.tag, EDIT_INSERT, IRC_UNDERLINE);
 | 
			
		||||
		break; case CTRL(L'V'): edit(ui.tag, EDIT_INSERT, IRC_REVERSE);
 | 
			
		||||
 | 
			
		||||
		break; case L'\b': edit(ui.tag, EDIT_BACKSPACE, 0);
 | 
			
		||||
		break; case L'\t': edit(ui.tag, EDIT_COMPLETE, 0);
 | 
			
		||||
		break; case L'\n': edit(ui.tag, EDIT_ENTER, 0);
 | 
			
		||||
 | 
			
		||||
		break; 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) {
 | 
			
		||||
		break; case KEY_RESIZE:    uiResize();
 | 
			
		||||
		break; case KEY_PPAGE:     logUp();
 | 
			
		||||
		break; case KEY_NPAGE:     logDown();
 | 
			
		||||
		break; case KEY_LEFT:      return edit(false, true, 'B');
 | 
			
		||||
		break; case KEY_RIGHT:     return edit(false, true, 'F');
 | 
			
		||||
		break; case KEY_HOME:      return edit(false, true, 'A');
 | 
			
		||||
		break; case KEY_END:       return edit(false, true, 'E');
 | 
			
		||||
		break; case KEY_DC:        return edit(false, true, 'D');
 | 
			
		||||
		break; case KEY_BACKSPACE: return edit(false, false, '\b');
 | 
			
		||||
		break; case KEY_ENTER:     return edit(false, false, '\n');
 | 
			
		||||
		break; case KEY_RESIZE:    uiResize(); return false;
 | 
			
		||||
		break; case KEY_PPAGE:     logUp(); return false;
 | 
			
		||||
		break; case KEY_NPAGE:     logDown(); return false;
 | 
			
		||||
		break; case KEY_LEFT:      edit(ui.tag, EDIT_LEFT, ch);
 | 
			
		||||
		break; case KEY_RIGHT:     edit(ui.tag, EDIT_RIGHT, ch);
 | 
			
		||||
		break; case KEY_HOME:      edit(ui.tag, EDIT_HOME, ch);
 | 
			
		||||
		break; case KEY_END:       edit(ui.tag, EDIT_END, ch);
 | 
			
		||||
		break; case KEY_DC:        edit(ui.tag, EDIT_DELETE, ch);
 | 
			
		||||
		break; case KEY_BACKSPACE: edit(ui.tag, EDIT_BACKSPACE, ch);
 | 
			
		||||
		break; case KEY_ENTER:     edit(ui.tag, EDIT_ENTER, ch);
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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]);
 | 
			
		||||
 | 
			
		||||
enum { RING_LEN = 16 };
 | 
			
		||||
static char *ring[RING_LEN];
 | 
			
		||||
static size_t last;
 | 
			
		||||
struct Entry {
 | 
			
		||||
	size_t tag;
 | 
			
		||||
	char *url;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum { RING_LEN = 32 };
 | 
			
		||||
static_assert(!(RING_LEN & (RING_LEN - 1)), "power of two RING_LEN");
 | 
			
		||||
 | 
			
		||||
static void push(const char *url, size_t len) {
 | 
			
		||||
	free(ring[last]);
 | 
			
		||||
	ring[last++] = strndup(url, len);
 | 
			
		||||
	last &= RING_LEN - 1;
 | 
			
		||||
static struct {
 | 
			
		||||
	struct Entry buf[RING_LEN];
 | 
			
		||||
	size_t end;
 | 
			
		||||
} 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]) {
 | 
			
		||||
		size_t len = 1;
 | 
			
		||||
		for (size_t i = 0; i < SCHEMES_LEN; ++i) {
 | 
			
		||||
			if (strncmp(str, SCHEMES[i], strlen(SCHEMES[i]))) continue;
 | 
			
		||||
			len = strcspn(str, " >\"");
 | 
			
		||||
			push(str, len);
 | 
			
		||||
			push(tag, str, len);
 | 
			
		||||
		}
 | 
			
		||||
		str = &str[len];
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void urlList(void) {
 | 
			
		||||
void urlList(struct Tag tag) {
 | 
			
		||||
	uiHide();
 | 
			
		||||
	for (size_t i = 0; i < RING_LEN; ++i) {
 | 
			
		||||
		char *url = ring[(i + last) & (RING_LEN - 1)];
 | 
			
		||||
		if (url) printf("%s\n", url);
 | 
			
		||||
		struct Entry entry = ring.buf[(ring.end + i) & (RING_LEN - 1)];
 | 
			
		||||
		if (!entry.url || entry.tag != tag.id) continue;
 | 
			
		||||
		printf("%s\n", entry.url);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void urlOpen(size_t i) {
 | 
			
		||||
	char *url = ring[(last - i) & (RING_LEN - 1)];
 | 
			
		||||
	if (!url) return;
 | 
			
		||||
	char *argv[] = { "open", url, NULL };
 | 
			
		||||
	spawn(argv);
 | 
			
		||||
void urlOpen(struct Tag tag, size_t fromEnd) {
 | 
			
		||||
	size_t count = 0;
 | 
			
		||||
	for (size_t i = 0; i < RING_LEN; ++i) {
 | 
			
		||||
		struct Entry entry = ring.buf[(ring.end - i) & (RING_LEN - 1)];
 | 
			
		||||
		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