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