circo

claudio's IRC oasis
git clone git://git.bitsmanent.org/circo
Log | Files | Refs | README | LICENSE

circo.c (37856B)


      1 /* See LICENSE file for copyright and license details.
      2  *
      3  * circo is an IRC client...
      4  *
      5  * The messages handlers are organized in an array which is accessed whenever a
      6  * new message has been fetched. This allows message dispatching in O(1) time.
      7  *
      8  * Keys are organized as arrays and defined in config.h.
      9  *
     10  * To understand everything else, start reading main().
     11 */
     12 
     13 #define _GNU_SOURCE
     14 #include <ctype.h>
     15 #include <errno.h>
     16 #include <locale.h>
     17 #include <netdb.h>
     18 #include <netinet/in.h>
     19 #include <signal.h>
     20 #include <stdarg.h>
     21 #include <stdio.h>
     22 #include <stdlib.h>
     23 #include <string.h>
     24 #include <sys/ioctl.h>
     25 #include <sys/select.h>
     26 #include <sys/socket.h>
     27 #include <sys/types.h>
     28 #include <sys/wait.h>
     29 #include <termios.h>
     30 #include <time.h>
     31 #include <unistd.h>
     32 #include <wchar.h>
     33 
     34 #include "arg.h"
     35 char *argv0;
     36 
     37 /* macros */
     38 #define LENGTH(X)       (sizeof X / sizeof X[0])
     39 #define ISCHANPFX(P)    ((P) == '#' || (P) == '&')
     40 #define ISCHAN(B)       ISCHANPFX((B)->name[0])
     41 
     42 /* UTF-8 utils */
     43 #define UTF8BYTES(X)    ( ((X) & 0xF0) == 0xF0 ? 4 \
     44 			: ((X) & 0xE0) == 0xE0 ? 3 \
     45 			: ((X) & 0xC0) == 0xC0 ? 2 \
     46 			: 1)
     47 #define UTF8CBYTE(X)    (((X) & 0xC0) == 0x80)
     48 
     49 /* drawing flags */
     50 #define REDRAW_BAR      1<<1
     51 #define REDRAW_BUFFER   1<<2
     52 #define REDRAW_CMDLN    1<<3
     53 #define REDRAW_ALL      (REDRAW_BAR|REDRAW_BUFFER|REDRAW_CMDLN)
     54 
     55 /* VT100 escape sequences */
     56 #define CLEAR           "\33[2J"
     57 #define CLEARLN         "\33[2K"
     58 #define CLEARRIGHT      "\33[0K"
     59 #define CURPOS          "\33[%d;%dH"
     60 #define CURSON          "\33[?25h"
     61 #define CURSOFF         "\33[?25l"
     62 /* colors */
     63 #define COLFG           "\33[38;5;%dm"
     64 #define COLBG           "\33[48;5;%dm"
     65 #define ATTR            "\33[%dm"
     66 #define COLRST          "\33[0m"
     67 
     68 /* UI colors and attributes */
     69 #define UI_BYTE         0x19
     70 #define UI_SET(X)       UI_BYTE, X
     71 #define UI_RST          UI_BYTE, -1
     72 #define UI_WRAP(A,B)    UI_SET(B), A, UI_RST
     73 #define UI_FMT          "%c%d"
     74 #define _C_             UI_FMT
     75 
     76 #if defined CTRL && defined _AIX
     77   #undef CTRL
     78 #endif
     79 #ifndef CTRL
     80   #define CTRL(k)   ((k) & 0x1F)
     81 #endif
     82 #define ALT(k)      ((k) + (161 - 'a'))
     83 #define CTRL_ALT(k) ((k) + (129 - 'a'))
     84 
     85 /* enums */
     86 enum { KeyFirst = -999, KeyUp, KeyDown, KeyRight, KeyLeft, KeyHome, KeyEnd, KeyDel, KeyPgUp, KeyPgDw, KeyBackspace, KeyLast };
     87 enum { LineToOffset, OffsetToLine, TotalLines }; /* bufinfo() flags */
     88 
     89 enum {
     90 	NickNormal,
     91 	NickMention,
     92 	IRCMessage,
     93 	ColorLast
     94 } Color;
     95 
     96 typedef union {
     97 	int i;
     98 	unsigned int ui;
     99 	float f;
    100 	const void *v;
    101 } Arg;
    102 
    103 typedef struct Nick Nick;
    104 struct Nick {
    105 	char name[16];
    106 	int len;
    107 	Nick *next;
    108 };
    109 
    110 typedef struct Buffer Buffer;
    111 struct Buffer {
    112 	char *data;
    113 	char name[64];
    114 	char *hist;
    115 	char cmdbuf[256];
    116 	int size, len, kicked;
    117 	int line, nlines, lnoff;
    118 	int cmdlen, cmdoff, cmdpos;
    119 	int histsz, histlnoff;
    120 	int need_redraw;
    121 	int notify;
    122 	int totnames;
    123 	int recvnames;
    124 	Nick *names;
    125 	Buffer *next;
    126 };
    127 
    128 typedef struct {
    129 	char *name;
    130 	void (*func)(char *, char *);
    131 } Command;
    132 
    133 typedef struct {
    134 	char *name;
    135 	void (*func)(char *, char *, char *);
    136 } Message;
    137 
    138 typedef struct {
    139 	const int key;
    140 	void (*func)(const Arg *);
    141 	const Arg arg;
    142 } Key;
    143 
    144 /* function declarations */
    145 void attach(Buffer *b);
    146 int bprintf(Buffer *b, char *fmt, ...);
    147 int bprintf_prefixed(Buffer *b, char *fmt, ...);
    148 int bufinfo(char *buf, int len, int val, int act);
    149 int bvprintf(Buffer *b, char *fmt, va_list ap);
    150 void cleanup(void);
    151 void cmd_close(char *cmd, char *s);
    152 void cmd_msg(char *cmd, char *s);
    153 void cmd_quit(char *cmd, char *s);
    154 void cmd_rejoinall(char *cmd, char *s);
    155 void cmd_server(char *cmd, char *s);
    156 void cmd_topic(char *cmd, char *s);
    157 void cmdln_chldel(const Arg *arg);
    158 void cmdln_chrdel(const Arg *arg);
    159 void cmdln_clear(const Arg *arg);
    160 void cmdln_complete(const Arg *arg);
    161 void cmdln_cursor(const Arg *arg);
    162 void cmdln_submit(const Arg *arg);
    163 void cmdln_wdel(const Arg *arg);
    164 void detach(Buffer *b);
    165 void destroy(Buffer *b);
    166 int dial(char *host, char *port, int flags);
    167 void die(const char *fmt, ...);
    168 void draw(void);
    169 void drawbar(void);
    170 void drawbuf(void);
    171 void drawcmdln(void);
    172 void *ecalloc(size_t nmemb, size_t size);
    173 Buffer *getbuf(char *name);
    174 void focus(Buffer *b);
    175 void focusnext(const Arg *arg);
    176 void focusnum(const Arg *arg);
    177 void focusprev(const Arg *arg);
    178 void freebuf(Buffer *b);
    179 void freenames(Nick **names);
    180 char *gcsfitcols(char *s, int maxw);
    181 int gcswidth(char *s, int len);
    182 int getkey(void);
    183 void hangsup(void);
    184 void history(const Arg *arg);
    185 void histpush(char *buf, int len);
    186 int logfmt(char *fmt, ...);
    187 int mvprintf(int x, int y, char *fmt, ...);
    188 Buffer *newbuf(char *name);
    189 Nick *nickadd(Buffer *b, char *name);
    190 void nickdel(Buffer *b, char *name);
    191 Nick *nickget(Buffer *b, char *name);
    192 void nicklist(Buffer *b, char *list);
    193 void nickmv(char *old, char *new);
    194 void parsecmd(char *cmd);
    195 void parsesrv(void);
    196 void privmsg(char *to, char *txt);
    197 void quit(char *msg);
    198 int readchar(void);
    199 void recv_busynick(char *u, char *u2, char *u3);
    200 void recv_join(char *who, char *chan, char *txt);
    201 void recv_kick(char *who, char *chan, char *txt);
    202 void recv_luserme(char *a, char *b, char *c);
    203 void recv_mode(char *u, char *val, char *u2);
    204 void recv_motd(char *u, char *u2, char *txt);
    205 void recv_names(char *usr, char *par, char *txt);
    206 void recv_namesend(char *host, char *par, char *names);
    207 void recv_nick(char *who, char *u, char *txt);
    208 void recv_notice(char *who, char *u, char *txt);
    209 void recv_part(char *who, char *chan, char *txt);
    210 void recv_ping(char *u, char *u2, char *txt);
    211 void recv_privmsg(char *from, char *to, char *txt);
    212 void recv_quit(char *who, char *u, char *txt);
    213 void recv_topic(char *who, char *chan, char *txt);
    214 void recv_topicrpl(char *usr, char *par, char *txt);
    215 void resize(int x, int y);
    216 void scroll(const Arg *arg);
    217 void sendident(void);
    218 void setup(void);
    219 void sigchld(int unused);
    220 void sigwinch(int unused);
    221 char *skip(char *s, char c);
    222 void sout(char *fmt, ...);
    223 void spawn(const char **cmd);
    224 void stripformats(char *s);
    225 void trim(char *s);
    226 int uiset(char *buf, int index);
    227 void usage(void);
    228 void usrin(void);
    229 char *wordleft(char *str, int offset, int *size);
    230 
    231 /* variables */
    232 FILE *srv, *logp;
    233 Buffer *buffers, *status, *sel;
    234 char bufin[4096];
    235 char bufout[4096];
    236 struct termios origti;
    237 time_t trespond;
    238 int running = 1;
    239 int online = 0;
    240 int rows, cols;
    241 
    242 Message messages[] = {
    243 	{ "JOIN",    recv_join },
    244 	{ "KICK",    recv_kick },
    245 	{ "MODE",    recv_mode },
    246 	{ "NICK",    recv_nick },
    247 	{ "NOTICE",  recv_notice },
    248 	{ "PART",    recv_part },
    249 	{ "PING",    recv_ping },
    250 	{ "PRIVMSG", recv_privmsg },
    251 	{ "QUIT",    recv_quit },
    252 	{ "TOPIC",   recv_topic },
    253 	{ "255",     recv_luserme },
    254 	{ "331",     recv_topicrpl }, /* no topic set */
    255 	{ "332",     recv_topicrpl },
    256 	{ "353",     recv_names },
    257 	{ "366",     recv_namesend },
    258 	{ "372",     recv_motd },
    259 	{ "433",     recv_busynick },
    260 	{ "437",     recv_busynick },
    261 
    262 	/* ignored */
    263 	{ "PONG",    NULL },
    264 	{ "470",     NULL }, /* channel forward */
    265 
    266 };
    267 
    268 /* configuration, allows nested code to access above variables */
    269 #include "config.h"
    270 
    271 /* function implementations */
    272 void
    273 attach(Buffer *b) {
    274 	b->next = buffers;
    275 	buffers = b;
    276 }
    277 
    278 int
    279 bprintf(Buffer *b, char *fmt, ...) {
    280 	va_list ap;
    281 	int len;
    282 
    283 	va_start(ap, fmt);
    284 	len = bvprintf(b, fmt, ap);
    285 	va_end(ap);
    286 	return len;
    287 }
    288 
    289 int
    290 bprintf_prefixed(Buffer *b, char *fmt, ...) {
    291 	va_list ap;
    292 	time_t t;
    293 	struct tm *tm;
    294 	char buf[64];
    295 	int len = 0;
    296 
    297 	if(*prefix_format) {
    298 		t = time(NULL);
    299 		tm = localtime(&t);
    300 		len = strftime(buf, sizeof buf, prefix_format, tm);
    301 		if(!len)
    302 			len = strftime(buf, sizeof buf, "%T | ", tm); /* fallback */
    303 		buf[len] = '\0';
    304 		len = bprintf(b, "%s", buf);
    305 	}
    306 	va_start(ap, fmt);
    307 
    308 #ifdef DEBUG
    309 	int olen = b->len;
    310 	len += bvprintf(b, fmt, ap);
    311 	logfmt("%ld DEBUG bvprintf() %s", time(NULL), &b->data[olen]); /* \n is already there from the caller */
    312 #else
    313 	len += bvprintf(b, fmt, ap);
    314 #endif
    315 	va_end(ap);
    316 	return len;
    317 }
    318 
    319 int
    320 bufinfo(char *buf, int len, int val, int act) {
    321 	int x, y, i;
    322 
    323 	for(i = 0, x = y = 1; i < len; ++i) {
    324 		switch(act) {
    325 		case LineToOffset:
    326 			if(val == y)
    327 				return i;
    328 			break;
    329 		case OffsetToLine:
    330 			if(val == i)
    331 				return y;
    332 			break;
    333 		}
    334 		if(x == cols || buf[i] == '\n') {
    335 			if(buf[i] != '\n' && i < len - 1 && buf[i + 1] == '\n')
    336 				++i;
    337 			x = 1;
    338 			++y;
    339 		}
    340 		else
    341 			++x;
    342 	}
    343 	return (act == TotalLines ? y : 0) - 1;
    344 }
    345 
    346 int
    347 bvprintf(Buffer *b, char *fmt, va_list ap) {
    348 	va_list ap2;
    349 	int len;
    350 
    351 	va_copy(ap2, ap);
    352 	len = vsnprintf(&b->data[b->len], b->size - b->len, fmt, ap);
    353 	if(len >= b->size - b->len) {
    354 		b->size += len + 1;
    355 		b->data = realloc(b->data, b->size);
    356 		if(!b->data)
    357 			die("realloc():");
    358 		len = vsnprintf(&b->data[b->len], b->size - b->len, fmt, ap2);
    359 	}
    360 	va_end(ap2);
    361 	if(len < 0)
    362 		return -1;
    363 	b->len += len;
    364 	b->nlines = bufinfo(b->data, b->len, 0, TotalLines);
    365 	b->need_redraw |= REDRAW_BUFFER;
    366 	return len;
    367 }
    368 
    369 void
    370 cleanup(void) {
    371 	Buffer *b;
    372 
    373 	while((b = buffers)) {
    374 		buffers = buffers->next;
    375 		freebuf(b);
    376 	}
    377 	tcsetattr(0, TCSANOW, &origti);
    378 }
    379 
    380 void
    381 cmd_close(char *cmd, char *s) {
    382 	Buffer *b;
    383 
    384 	b = *s ? getbuf(s) : sel;
    385 	if(!b) {
    386 		bprintf_prefixed(status, "/%s: %s: unknown buffer.\n", cmd, s);
    387 		return;
    388 	}
    389 	if(b == status) {
    390 		bprintf_prefixed(status, "/%s: cannot close the status.\n", cmd);
    391 		return;
    392 	}
    393 	if(srv && ISCHAN(b) && !b->kicked)
    394 		sout("PART :%s", b->name); /* Note: you may be not in that channel */
    395 	destroy(b);
    396 }
    397 
    398 void
    399 cmd_msg(char *cmd, char *s) {
    400 	char *to, *txt;
    401 
    402 	if(!srv) {
    403 		bprintf_prefixed(sel, "/%s: not connected.\n", cmd);
    404 		return;
    405 	}
    406 	trim(s);
    407 	to = s;
    408 	txt = skip(to, ' ');
    409 	if(!(*to && *txt)) {
    410 		bprintf_prefixed(sel, "Usage: /%s <channel or user> <text>\n", cmd);
    411 		return;
    412 	}
    413 	privmsg(to, txt);
    414 }
    415 
    416 void
    417 cmd_quit(char *cmd, char *msg) {
    418 	if(srv)
    419 		quit(*msg ? msg : QUIT_MESSAGE);
    420 	running = 0;
    421 }
    422 
    423 void
    424 cmd_rejoinall(char *cmd, char *s) {
    425 	Buffer *b;
    426 
    427 	if(!srv) {
    428 		bprintf_prefixed(sel, "/%s: not connected.\n", cmd);
    429 		return;
    430 	}
    431 	for(b = buffers; b; b = b->next)
    432 		if(ISCHAN(b))
    433 			sout("JOIN %s", b->name);
    434 }
    435 
    436 void
    437 cmd_server(char *cmd, char *s) {
    438 	char *t;
    439 	int fd;
    440 
    441 	t = skip(s, ' ');
    442 	if(!*t)
    443 		t = port;
    444 	else
    445 		strncpy(port, t, sizeof port);
    446 	t = s;
    447 	if(!*t)
    448 		t = host;
    449 	else
    450 		strncpy(host, t, sizeof host);
    451 	if(srv)
    452 		quit(QUIT_MESSAGE);
    453 	if((fd = dial(host, port, SOCK_NONBLOCK)) < 0) {
    454 		bprintf_prefixed(status, "Cannot connect to %s on port %s.\n", host, port);
    455 		return;
    456 	}
    457 	srv = fdopen(fd, "r+");
    458 	setbuf(srv, NULL);
    459 	sel->need_redraw |= REDRAW_BAR;
    460 }
    461 
    462 void
    463 cmd_topic(char *cmd, char *s) {
    464 	char *chan, *txt;
    465 
    466 	if(!srv) {
    467 		bprintf_prefixed(sel, "/%s: not connected.\n", cmd);
    468 		return;
    469 	}
    470 	if(!*s) {
    471 		if(ISCHAN(sel))
    472 			sout("TOPIC %s", sel->name);
    473 		else
    474 			bprintf_prefixed(sel, "/%s: %s in not a channel.\n", cmd, sel->name);
    475 		return;
    476 	}
    477 	if(ISCHANPFX(*s)) {
    478 		chan = s;
    479 		txt = skip(s, ' ');
    480 		if(!*txt) {
    481 			sout("TOPIC %s", chan);
    482 			return;
    483 		}
    484 	}
    485 	else {
    486 		if(sel == status) {
    487 			bprintf_prefixed(sel, "Usage: /%s [channel] [text]\n", cmd);
    488 			return;
    489 		}
    490 		chan = sel->name;
    491 		txt = s;
    492 	}
    493 	sout("TOPIC %s :%s", chan, txt);
    494 }
    495 
    496 void
    497 cmdln_chldel(const Arg *arg) {
    498 	int nb;
    499 
    500 	if(!sel->cmdoff)
    501 		return;
    502 	for(nb = 1; UTF8CBYTE(sel->cmdbuf[sel->cmdoff - nb]); ++nb);
    503 	if(sel->cmdoff < sel->cmdlen)
    504 		memmove(&sel->cmdbuf[sel->cmdoff - nb], &sel->cmdbuf[sel->cmdoff],
    505 			sel->cmdlen - sel->cmdoff);
    506 	sel->cmdlen -= nb;
    507 	sel->cmdoff -= nb;
    508 	sel->cmdbuf[sel->cmdlen] = '\0';
    509 	sel->need_redraw |= REDRAW_CMDLN;
    510 }
    511 
    512 void
    513 cmdln_chrdel(const Arg *arg) {
    514 	int nb;
    515 
    516 	if(!sel->cmdlen)
    517 		return;
    518 	if(sel->cmdoff == sel->cmdlen) {
    519 		for(nb = 1; UTF8CBYTE(sel->cmdbuf[sel->cmdoff - nb]); ++nb);
    520 		sel->cmdoff -= nb;
    521 		sel->need_redraw |= REDRAW_CMDLN;
    522 		return;
    523 	}
    524 	nb = UTF8BYTES(sel->cmdbuf[sel->cmdoff]);
    525 	memmove(&sel->cmdbuf[sel->cmdoff], &sel->cmdbuf[sel->cmdoff + nb],
    526 		sel->cmdlen - sel->cmdoff);
    527 
    528 	sel->cmdlen -= nb;
    529 	sel->cmdbuf[sel->cmdlen] = '\0';
    530 
    531 	if(sel->cmdoff && sel->cmdoff == sel->cmdlen) {
    532 		for(nb = 1; UTF8CBYTE(sel->cmdbuf[sel->cmdoff - nb]); ++nb);
    533 		sel->cmdoff -= nb;
    534 	}
    535 
    536 	sel->need_redraw |= REDRAW_CMDLN;
    537 }
    538 
    539 void
    540 cmdln_clear(const Arg *arg) {
    541 	if(!sel->cmdoff)
    542 		return;
    543 	/* preserve text on the right */
    544 	memmove(sel->cmdbuf, &sel->cmdbuf[sel->cmdoff], sel->cmdlen - sel->cmdoff);
    545 	sel->cmdlen -= sel->cmdoff;
    546 	sel->cmdbuf[sel->cmdlen] = '\0';
    547 	sel->cmdoff = 0;
    548 	sel->need_redraw |= REDRAW_CMDLN;
    549 }
    550 
    551 void
    552 cmdln_complete(const Arg *arg) {
    553 	char word[64];
    554 	char *match = NULL, *ws, *we, *epos;
    555 	int wlen, mlen, newlen, i;
    556 	Nick *n;
    557 
    558 	if(!sel->cmdlen)
    559 		return;
    560 	ws = wordleft(sel->cmdbuf, sel->cmdoff, &wlen);
    561 	if(!ws || wlen > sizeof word)
    562 		return;
    563 	memcpy(word, ws, wlen);
    564 	word[wlen] = '\0';
    565 
    566 	/* actual search */
    567 	if(word[0] == '/') {
    568 		/* search in commands */
    569 		for(i = 0; i < LENGTH(commands) && !match; ++i)
    570 			if(!strncasecmp(commands[i].name, &word[1], wlen - 1))
    571 				match = commands[i].name;
    572 		if(!match)
    573 			return;
    574 		mlen = strlen(match);
    575 		/* preserve the slash */
    576 		++ws;
    577 		--wlen;
    578 	}
    579 	else if(ISCHANPFX(word[0])) {
    580 		/* search buffer name */
    581 		if(strncasecmp(sel->name, word, wlen))
    582 			return;
    583 		match = sel->name;
    584 		mlen = strlen(match);
    585 	}
    586 	else {
    587 		/* match a nick in current buffer */
    588 		for(n = sel->names; n && !match; n = n->next)
    589 			if(!strncasecmp(n->name, word, wlen))
    590 				break;
    591 		if(!n)
    592 			return;
    593 		match = n->name;
    594 		mlen = n->len;
    595 	}
    596 
    597 	we = ws + wlen;
    598 	epos = &sel->cmdbuf[sel->cmdoff] > we ? &sel->cmdbuf[sel->cmdoff] : we;
    599 
    600 	/* check if match exceed buffer size */
    601 	newlen = sel->cmdlen - (epos - ws) + mlen;
    602 	if(newlen > sizeof sel->cmdbuf - 1)
    603 		return;
    604 
    605 	memmove(ws+mlen, epos, sel->cmdlen - (epos - sel->cmdbuf));
    606 	memcpy(ws, match, mlen);
    607 
    608 	sel->cmdlen = newlen;
    609 	sel->cmdbuf[sel->cmdlen] = '\0';
    610 	sel->cmdoff = ws - sel->cmdbuf + mlen;
    611 	sel->need_redraw |= REDRAW_CMDLN;
    612 }
    613 
    614 void
    615 cmdln_cursor(const Arg *arg) {
    616 	int i = arg->i, nb;
    617 
    618 	if(!i) {
    619 		sel->cmdoff = 0;
    620 		sel->need_redraw |= REDRAW_CMDLN;
    621 		return;
    622 	}
    623 	if(i < 0) {
    624 		nb = 1;
    625 		while(++i <= 0)
    626 			while(UTF8CBYTE(sel->cmdbuf[sel->cmdoff - nb]))
    627 				++nb;
    628 		nb = -nb;
    629 	}
    630 	else {
    631 		nb = 0;
    632 		while(--i >= 0)
    633 			nb += UTF8BYTES(sel->cmdbuf[sel->cmdoff + nb]);
    634 	}
    635 
    636 	sel->cmdoff += nb;
    637 	if(sel->cmdoff < 0)
    638 		sel->cmdoff = 0;
    639 	else if(sel->cmdoff > sel->cmdlen)
    640 		sel->cmdoff = sel->cmdlen;
    641 	sel->need_redraw |= REDRAW_CMDLN;
    642 }
    643 
    644 void
    645 cmdln_submit(const Arg *arg) {
    646 	char *buf;
    647 
    648 	if(sel->cmdbuf[0] == '\0')
    649 		return;
    650 
    651 #ifdef DEBUG
    652 	logfmt("%ld DEBUG cmdln_submit() %s\n", time(NULL), sel->cmdbuf);
    653 #endif
    654 	buf = ecalloc(1, sel->cmdlen);
    655 	memcpy(buf, sel->cmdbuf, sel->cmdlen);
    656 
    657 	histpush(sel->cmdbuf, sel->cmdlen);
    658 	if(buf[0] == '/') {
    659 		sel->cmdlen = sel->cmdoff = sel->histlnoff = 0;
    660 		sel->cmdbuf[sel->cmdlen] = '\0';
    661 		sel->need_redraw |= REDRAW_CMDLN;
    662 
    663 		/* Note: network latency may cause visual glitches. */
    664 		parsecmd(buf);
    665 	}
    666 	else {
    667 		if(sel == status)
    668 			bprintf_prefixed(sel, "Cannot send text here.\n");
    669 		else if(!srv)
    670 			bprintf_prefixed(sel, "Not connected.\n");
    671 		else
    672 			privmsg(sel->name, sel->cmdbuf);
    673 		sel->cmdlen = sel->cmdoff = sel->histlnoff = 0;
    674 		sel->cmdbuf[sel->cmdlen] = '\0';
    675 		sel->need_redraw |= REDRAW_CMDLN;
    676 	}
    677 	free(buf);
    678 }
    679 
    680 void
    681 cmdln_wdel(const Arg *arg) {
    682 	int i;
    683 
    684 	if(!sel->cmdoff)
    685 		return;
    686 	i = sel->cmdoff - 1;
    687 	while(i && sel->cmdbuf[i] == ' ')
    688 		--i;
    689 	if(i && isalnum(sel->cmdbuf[i])) {
    690 		while(--i && isalnum(sel->cmdbuf[i]));
    691 		if(i)
    692 			++i;
    693 	}
    694 	memmove(&sel->cmdbuf[i], &sel->cmdbuf[sel->cmdoff], sel->cmdlen - sel->cmdoff);
    695 	sel->cmdlen -= sel->cmdoff - i;
    696 	sel->cmdbuf[sel->cmdlen] = '\0';
    697 	sel->cmdoff = i;
    698 	sel->need_redraw |= REDRAW_CMDLN;
    699 }
    700 
    701 void
    702 destroy(Buffer *b) {
    703 	if(b == sel) {
    704 		sel = sel->next ? sel->next : buffers;
    705 		sel->need_redraw |= REDRAW_ALL;
    706 	}
    707 	detach(b);
    708 	freebuf(b);
    709 
    710 }
    711 
    712 void
    713 detach(Buffer *b) {
    714 	Buffer **tb;
    715 
    716 	for (tb = &buffers; *tb && *tb != b; tb = &(*tb)->next);
    717 	*tb = b->next;
    718 }
    719 
    720 int
    721 dial(char *host, char *port, int flags) {
    722 	static struct addrinfo hints;
    723 	struct addrinfo *res, *r;
    724 	int srvfd;
    725 
    726 	memset(&hints, 0, sizeof hints);
    727 	hints.ai_family = AF_UNSPEC;
    728 	hints.ai_socktype = SOCK_STREAM;
    729 	if(getaddrinfo(host, port, &hints, &res) != 0)
    730 		return -1;
    731 	for(r = res; r; r = r->ai_next) {
    732 		if((srvfd = socket(r->ai_family, r->ai_socktype | flags, r->ai_protocol)) == -1)
    733 			continue;
    734 		if(connect(srvfd, r->ai_addr, r->ai_addrlen) == 0 || errno == EINPROGRESS)
    735 			break;
    736 		close(srvfd);
    737 	}
    738 	freeaddrinfo(res);
    739 	if(!r)
    740 		return -1;
    741 	return srvfd;
    742 }
    743 
    744 void
    745 die(const char *fmt, ...) {
    746 	va_list ap;
    747 	va_start(ap, fmt);
    748 	vfprintf(stderr, fmt, ap);
    749 	va_end(ap);
    750 
    751 	if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
    752 		fputc(' ', stderr);
    753 		perror(NULL);
    754 	} else {
    755 		fputc('\n', stderr);
    756 	}
    757 
    758 	exit(0);
    759 }
    760 
    761 void
    762 draw(void) {
    763 	printf(CURSOFF);
    764 	if(sel->need_redraw & REDRAW_BAR)
    765 		drawbar();
    766 	if(sel->need_redraw & REDRAW_BUFFER)
    767 		drawbuf();
    768 	if(sel->need_redraw & REDRAW_CMDLN)
    769 		drawcmdln();
    770 	printf(CURPOS CURSON, rows, sel->cmdpos);
    771 }
    772 
    773 void
    774 drawbar(void) {
    775 	Buffer *b;
    776 	char buf[512];
    777 	int x = 1, len = 0;
    778 
    779 	if(!(cols && rows))
    780 		return;
    781 
    782 	if(ISCHAN(sel))
    783 		len += snprintf(buf, sizeof buf, "%d users in %s", sel->totnames, sel->name);
    784 	else
    785 		len += snprintf(buf, sizeof buf, "%s@%s:%s (%s)",
    786 			*nick ? nick : "[nick unset]", host, port,
    787 			srv ? (online ? "online" : "connecting...") : "offline");
    788 	if(sel->line)
    789 		len += snprintf(&buf[len], sizeof buf - len, " [scrolled]");
    790 
    791 #ifdef DEBUG
    792 	len += snprintf(&buf[len], sizeof buf - len, " | DEBUG");
    793 #endif
    794 
    795 	len = gcsfitcols(buf, cols - x + 1) - buf;
    796 	mvprintf(x, 1, "%.*s", len, buf);
    797 	x += gcswidth(buf, len);
    798 
    799 	for(b = buffers; b; b = b->next) {
    800 		if(!b->notify)
    801 			continue;
    802 		snprintf(buf, sizeof buf, " %s(%d)", b->name, b->notify);
    803 		len = gcsfitcols(buf, cols - x + 1) - buf;
    804 		uiset(NULL, NickMention);
    805 		mvprintf(x, 1, "%.*s", len, buf);
    806 		uiset(NULL, -1);
    807 		x += gcswidth(buf, len);
    808 	}
    809 	if(x < cols)
    810 		mvprintf(x, 1, CLEARRIGHT);
    811 }
    812 
    813 void
    814 drawbuf(void) {
    815 	int x, y, c, i, nb, nx;
    816 	char *endp;
    817 
    818 	if(!(cols && rows))
    819 		return;
    820 
    821 	x = rows - 2;
    822 	y = sel->nlines - x;
    823 	i = sel->line
    824 		? sel->lnoff
    825 		: sel->len ? bufinfo(sel->data, sel->len, 1 + (sel->nlines > x ? y : 0), LineToOffset) : 0;
    826 	x = 1;
    827 	y = 2;
    828 
    829 	for(; i < sel->len; ++i) {
    830                 if(sel->data[i] == UI_BYTE) {
    831 			c = strtol(&sel->data[++i], &endp, 10);
    832 			i += endp - &sel->data[i] - 1;
    833 			uiset(NULL, c);
    834 			continue;
    835 		}
    836 
    837 		c = sel->data[i];
    838 		if(x <= cols) {
    839 			if(c != '\n') {
    840 				nb = UTF8BYTES(c);
    841 				nx = x + gcswidth(&sel->data[i], 1);
    842 				if(nx - 1 <= cols) {
    843 					mvprintf(x, y, "%.*s", nb, &sel->data[i]);
    844 					x = nx;
    845 					i += nb - 1;
    846 					continue;
    847 				}
    848 			}
    849 			mvprintf(x, y, "%s", CLEARRIGHT);
    850 		}
    851 
    852 		x = 1;
    853 		if(++y == rows)
    854 			break;
    855 		if(c != '\n') {
    856 			nb = UTF8BYTES(c);
    857 			mvprintf(x, y, "%.*s", nb, &sel->data[i]);
    858 			x += gcswidth(&sel->data[i], 1);
    859 			i += nb - 1;
    860 		}
    861 		if(x > cols && i < sel->len - 1 && sel->data[i + 1] == '\n')
    862 			++i;
    863 	}
    864 	if(y < rows) {
    865 		mvprintf(x, y, "%s", CLEARRIGHT);
    866 		while(++y < rows)
    867 			mvprintf(1, y, "%s", CLEARLN);
    868 	}
    869 }
    870 
    871 void
    872 drawcmdln(void) {
    873 	char prompt[256], *buf, *p;
    874 	int s, w; /* size and width */
    875 	int x = 1, colw = cols;
    876 
    877 	sel->cmdpos = 1;
    878 
    879 	/* prompt */
    880 	s = snprintf(prompt, sizeof prompt, "[%s] ", sel->name);
    881 	w = gcswidth(prompt, colw - 1);
    882 	if(w > 0) {
    883 		s = gcsfitcols(prompt, colw - 1) - prompt;
    884 		mvprintf(x, rows, "%.*s", s, prompt);
    885 		x += w;
    886 		colw -= w;
    887 		sel->cmdpos += w;
    888 	}
    889 
    890 	/* buffer */
    891 	for(p = buf = sel->cmdbuf; p < &sel->cmdbuf[sel->cmdoff]; p = gcsfitcols(p, colw)) {
    892 		if(p != sel->cmdbuf && p == buf)
    893 			break;
    894 		buf = p;
    895 	}
    896 	w = gcswidth(buf, colw);
    897 
    898 	/* leave room for the cursor */
    899 	if(w >= colw && p == &sel->cmdbuf[sel->cmdoff]) {
    900 		buf = p;
    901 		w = gcswidth(buf, colw);
    902 	}
    903 
    904 	s = w ? gcsfitcols(buf, colw) - buf : 0;
    905 	mvprintf(x, rows, "%.*s%s", s, buf, w < colw ? CLEARRIGHT : "");
    906 
    907 	/* cursor position */
    908 	for(p = buf; p < &sel->cmdbuf[sel->cmdoff]; p += UTF8BYTES(*p))
    909 		sel->cmdpos += gcswidth(p, 1);
    910 }
    911 
    912 void *
    913 ecalloc(size_t nmemb, size_t size) {
    914 	void *p;
    915 
    916 	if(!(p = calloc(nmemb, size)))
    917 		die("Cannot allocate memory.");
    918 	return p;
    919 }
    920 
    921 void
    922 focus(Buffer *b) {
    923 	if(!b)
    924 		return;
    925 	if(b->notify)
    926 		b->notify = 0;
    927 	sel = b;
    928 	sel->need_redraw |= REDRAW_ALL;
    929 }
    930 
    931 void
    932 focusnext(const Arg *arg) {
    933 	Buffer *nb;
    934 
    935 	nb = sel->next ? sel->next : buffers;
    936 	if(nb == sel)
    937 		return;
    938 	focus(nb);
    939 }
    940 
    941 void
    942 focusnum(const Arg *arg) {
    943 	Buffer *b;
    944 	int i = arg->i;
    945 
    946 	for(b = buffers; b; b = b->next)
    947 		if(--i < 0)
    948 			break;
    949 	focus(b);
    950 }
    951 
    952 void 
    953 focusprev(const Arg *arg) {
    954 	Buffer *b, *nb;
    955 
    956 	nb = sel;
    957 	if(sel == buffers) {
    958 		for(b = buffers; b; b = b->next)
    959 			nb = b;
    960 	}
    961 	else {
    962 		for(b = buffers; b && b->next; b = b->next)
    963 			if(b->next == sel)
    964 				nb = b;
    965 	}
    966 	if(nb == sel)
    967 		return;
    968 	focus(nb);
    969 }
    970 
    971 void
    972 freebuf(Buffer *b) {
    973 	freenames(&b->names);
    974 	free(b->hist);
    975 	free(b->data);
    976 	free(b);
    977 }
    978 
    979 void
    980 freenames(Nick **names) {
    981 	Nick *n;
    982 
    983 	while((n = *names)) {
    984 		*names = (*names)->next;
    985 		free(n);
    986 	}
    987 }
    988 
    989 char *
    990 gcsfitcols(char *s, int maxw) {
    991 	int w = 0;
    992 
    993 	while(*s) {
    994 		w += gcswidth(s, 1);
    995 		if(w > maxw)
    996 			break;
    997 		s += UTF8BYTES(*s);
    998 	}
    999 	return s;
   1000 }
   1001 
   1002 int
   1003 gcswidth(char *s, int len) {
   1004 	wchar_t *wcs = malloc(len * sizeof(wchar_t) + 1);
   1005 	int n;
   1006 
   1007 	if(!wcs)
   1008 		return -1;
   1009 	n = mbstowcs(wcs, s, len);
   1010 	return n == -1 ? -1 : wcswidth(wcs, n);
   1011 }
   1012 
   1013 Buffer *
   1014 getbuf(char *name) {
   1015 	Buffer *b;
   1016 
   1017 	for(b = buffers; b; b = b->next)
   1018 		if(!strcmp(b->name, name))
   1019 			return b;
   1020 	return NULL;
   1021 }
   1022 
   1023 /* XXX quick'n dirty implementation */
   1024 int
   1025 getkey(void) {
   1026 	int key = readchar(), c;
   1027 
   1028 	if(key != '\x1b' || readchar() != '[') {
   1029 		switch(key) {
   1030 		case 127: key = KeyBackspace; break;
   1031 		}
   1032 		return key;
   1033 	}
   1034 	switch((c = readchar())) {
   1035 	case 'A': key = KeyUp; break;
   1036 	case 'B': key = KeyDown; break;
   1037 	case 'C': key = KeyRight; break;
   1038 	case 'D': key = KeyLeft; break;
   1039 	case 'H': key = KeyHome; break;
   1040 	case 'F': key = KeyEnd; break;
   1041 	case '1': key = KeyHome; break;
   1042 	case '3': key = KeyDel; break;
   1043 	case '4': key = KeyEnd; break;
   1044 	case '5': key = KeyPgUp; break;
   1045 	case '6': key = KeyPgDw; break;
   1046 	case '7': key = KeyHome; break;
   1047 	case '8': key = KeyEnd; break;
   1048 	}
   1049 	readchar();
   1050 	return key;
   1051 }
   1052 
   1053 void
   1054 hangsup(void) {
   1055 	if(!srv)
   1056 		return;
   1057 	fclose(srv);
   1058 	srv = NULL;
   1059 	online = 0;
   1060 	sel->need_redraw |= REDRAW_BAR;
   1061 }
   1062 
   1063 void
   1064 history(const Arg *arg) {
   1065 	int nl, n, i;
   1066 
   1067 	if(!sel->histsz)
   1068 		return;
   1069 	for(i = nl = 0; i < sel->histsz; i += strlen(&sel->hist[i]) + 2, ++nl);
   1070 	if(!sel->histlnoff) {
   1071 		if(sel->cmdlen)
   1072 			histpush(sel->cmdbuf, sel->cmdlen);
   1073 		sel->histlnoff = nl+1;
   1074 	}
   1075 	n = sel->histlnoff + arg->i;
   1076 	if(n < 1)
   1077 		n = 1;
   1078 	else if(n > nl)
   1079 		n = 0;
   1080 	sel->histlnoff = n;
   1081 	if(sel->histlnoff) {
   1082 		for(i = 0; i < sel->histsz && --n; i += strlen(&sel->hist[i]) + 2);
   1083 		sel->cmdlen = strlen(&sel->hist[i]);
   1084 		memcpy(sel->cmdbuf, &sel->hist[i], sel->cmdlen);
   1085 	}
   1086 	else {
   1087 		sel->cmdlen = 0;
   1088 	}
   1089 	sel->cmdoff = 0;
   1090 	sel->cmdbuf[sel->cmdlen] = '\0';
   1091 	sel->need_redraw |= REDRAW_CMDLN;
   1092 }
   1093 
   1094 void
   1095 histpush(char *buf, int len) {
   1096 	int nl, i;
   1097 
   1098 	/* do not clone unchanged lines */
   1099 	if(sel->histlnoff) {
   1100 		for(i = 0, nl = 1; i < sel->histsz && nl != sel->histlnoff; i += strlen(&sel->hist[i]) + 2, ++nl);
   1101 		if(!memcmp(&sel->hist[i], buf, len))
   1102 			return;
   1103 	}
   1104 	if(sel->histsz)
   1105 		++sel->histsz;
   1106 	i = sel->histsz;
   1107 	if(!(sel->hist = realloc(sel->hist, (sel->histsz += len + 1))))
   1108 		die("Cannot realloc");
   1109 	memcpy(&sel->hist[i], buf, len);
   1110 	sel->hist[i + len] = '\0';
   1111 }
   1112 
   1113 int
   1114 logfmt(char *fmt, ...) {
   1115 	va_list ap;
   1116 	int len;
   1117 
   1118 	if(!logp)
   1119 		return -1;
   1120 	va_start(ap, fmt);
   1121 	len = vfprintf(logp, fmt, ap);
   1122 	va_end(ap);
   1123 	fflush(logp);
   1124 	return len;
   1125 }
   1126 
   1127 int
   1128 mvprintf(int x, int y, char *fmt, ...) {
   1129 	va_list ap;
   1130 	int len;
   1131 
   1132 	printf(CURPOS, y, x);
   1133 	va_start(ap, fmt);
   1134 	len = vfprintf(stdout, fmt, ap);
   1135 	va_end(ap);
   1136 	return len;
   1137 }
   1138 
   1139 Buffer *
   1140 newbuf(char *name) {
   1141 	Buffer *b;
   1142 
   1143 	b = ecalloc(1, sizeof(Buffer));
   1144 	b->need_redraw = REDRAW_ALL;
   1145 	strncpy(b->name, name, sizeof b->name);
   1146 	attach(b);
   1147 	return b;
   1148 }
   1149 
   1150 Nick *
   1151 nickadd(Buffer *b, char *name) {
   1152 	Nick *n;
   1153 
   1154 	n = ecalloc(1, sizeof(Nick));
   1155 	strncpy(n->name, name, sizeof n->name - 1);
   1156 	n->len = strlen(n->name);
   1157 
   1158 	/* attach */
   1159 	n->next = b->names;
   1160 	b->names = n;
   1161 	++b->totnames;
   1162 	if(b == sel)
   1163 		sel->need_redraw |= REDRAW_BAR;
   1164 	return n;
   1165 }
   1166 
   1167 void
   1168 nickdel(Buffer *b, char *name) {
   1169 	Nick *n, **tn;
   1170 
   1171 	if(!b)
   1172 		return;
   1173 	if(!(n = nickget(b, name)))
   1174 		return;
   1175 	/* detach */
   1176 	for(tn = &b->names; *tn && *tn != n; tn = &(*tn)->next);
   1177 	*tn = n->next;
   1178 	free(n);
   1179 	--b->totnames;
   1180 	if(b == sel)
   1181 		sel->need_redraw |= REDRAW_BAR;
   1182 }
   1183 
   1184 Nick *
   1185 nickget(Buffer *b, char *name) {
   1186 	Nick *n;
   1187 
   1188 	for(n = b->names; n; n = n->next)
   1189 		if(!strcmp(n->name, name))
   1190 			return n;
   1191 	return NULL;
   1192 }
   1193 
   1194 void
   1195 nicklist(Buffer *b, char *list) {
   1196 	char *p, *np;
   1197 
   1198 	for(p = list, np = skip(list, ' '); *p; p = np, np = skip(np, ' ')) {
   1199 		/* skip nick flags */
   1200 		switch(*p) {
   1201 		case '+':
   1202 		case '@':
   1203 		case '~':
   1204 		case '%':
   1205 		case '&':
   1206 			++p;
   1207 			break;
   1208 		}
   1209 		nickadd(b, p);
   1210 	}
   1211 }
   1212 
   1213 void
   1214 nickmv(char *old, char *new) {
   1215 	Buffer *b;
   1216 	Nick *n;
   1217 
   1218 	for(b = buffers; b; b = b->next) {
   1219 		if(!(ISCHAN(b) && (n = nickget(b, old))))
   1220 			continue;
   1221 		strncpy(n->name, new, sizeof n->name - 1);
   1222 		n->len = strlen(n->name);
   1223 	}
   1224 }
   1225 
   1226 void
   1227 parsecmd(char *cmd) {
   1228 	char *p, *tp;
   1229 	int i, len;
   1230 
   1231 	p = &cmd[1]; /* skip the slash */
   1232 	if(!*p)
   1233 		return;
   1234 	tp = p + 1;
   1235 	tp = skip(p, ' ');
   1236 	len = strlen(p);
   1237 	for(i = 0; i < LENGTH(commands); ++i) {
   1238 		if(!strncmp(commands[i].name, p, len)) {
   1239 			commands[i].func(p, tp);
   1240 			return;
   1241 		}
   1242 	}
   1243 	if(srv)
   1244 		sout("%s %s", p, tp); /* raw */
   1245 	else
   1246 		bprintf_prefixed(sel, "/%s: not connected.\n", p);
   1247 }
   1248 
   1249 void
   1250 parsesrv(void) {
   1251 	char *cmd, *usr, *par, *txt;
   1252 
   1253 #ifdef DEBUG
   1254 	logfmt("%ld DEBUG parsesrv(): %s", time(NULL), bufin); /* \n is already there from fgets() */
   1255 #endif
   1256 	cmd = bufin;
   1257 	usr = host;
   1258 	if(!cmd || !*cmd)
   1259 		return;
   1260 	if(cmd[0] == ':') {
   1261 		usr = cmd + 1;
   1262 		cmd = skip(usr, ' ');
   1263 		if(cmd[0] == '\0')
   1264 			return;
   1265 		skip(usr, '!');
   1266 	}
   1267 	skip(cmd, '\r');
   1268 	par = skip(cmd, ' ');
   1269 	txt = skip(par, ':');
   1270 
   1271 	trim(txt);
   1272 	trim(par);
   1273 
   1274 	/* IRC formatting may be supported at some point in the future. For now
   1275 	 * just strip that out to keep things simple. */
   1276 	stripformats(txt);
   1277 
   1278 	for(int i = 0; i < LENGTH(messages); ++i) {
   1279 		if(!strcmp(messages[i].name, cmd)) {
   1280 			if(messages[i].func)
   1281 				messages[i].func(usr, par, txt);
   1282 			return;
   1283 		}
   1284 	}
   1285 	par = skip(par, ' ');
   1286 	bprintf_prefixed(sel, "%s %s\n", par, txt);
   1287 }
   1288 
   1289 void
   1290 privmsg(char *to, char *txt) {
   1291 	Buffer *b = getbuf(to);
   1292 	
   1293 	if(!b)
   1294 		b = isalpha(*to) ? newbuf(to) : sel;
   1295 	bprintf_prefixed(b, "%s: %s\n", nick, txt);
   1296 	sout("PRIVMSG %s :%s", to, txt);
   1297 	logfmt("%ld PRIVMSG to %s: %s\n", time(NULL), to, txt);
   1298 }
   1299 
   1300 void
   1301 quit(char *msg) {
   1302 	if(!srv)
   1303 		return;
   1304 	sout("QUIT :%s", msg);
   1305 	hangsup();
   1306 }
   1307 
   1308 int
   1309 readchar(void) {
   1310 	char buf[1] = {0};
   1311 	return (read(0, buf, 1) < 1 ? EOF : buf[0]);
   1312 }
   1313 
   1314 void
   1315 recv_busynick(char *u, char *u2, char *u3) {
   1316 	char *n = skip(u2, ' ');
   1317 
   1318 	bprintf_prefixed(status, "%s is busy, choose a different /nick\n", n);
   1319 	sel->need_redraw |= REDRAW_BAR;
   1320 }
   1321 
   1322 void
   1323 recv_join(char *who, char *chan, char *txt) {
   1324 	Buffer *b;
   1325 
   1326 	if(!*chan)
   1327 		chan = txt;
   1328 	b = getbuf(chan);
   1329 
   1330 	/* don't call nickadd() for ourselves since nicks list gets updated when join */
   1331 	if(!strcmp(who, nick)) {
   1332 		if(!b)
   1333 			b = newbuf(chan);
   1334 		else
   1335 			b->kicked = 0; /* b may only be non-NULL due to this */
   1336 		sel = b;
   1337 		sel->need_redraw = REDRAW_ALL;
   1338 	}
   1339 	else
   1340 		nickadd(b, who);
   1341 	bprintf_prefixed(b, _C_"%s"_C_" %s\n", UI_WRAP("JOIN", IRCMessage), who);
   1342 	logfmt("%ld JOIN %s on %s\n", time(NULL), who, chan);
   1343 }
   1344 
   1345 void
   1346 recv_kick(char *oper, char *chan, char *who) {
   1347 	Buffer *b;
   1348 
   1349 	who = skip(chan, ' ');
   1350 	b = getbuf(chan);
   1351 	if(!b)
   1352 		return;
   1353 	if(!strcmp(who, nick)) {
   1354 		b->kicked = 1;
   1355 		freenames(&b->names); /* we don't need this anymore */
   1356 		bprintf_prefixed(b, "You got kicked from %s\n", chan);
   1357 	}
   1358 	else {
   1359 		bprintf_prefixed(b, _C_"%s"_C_" %s (%s)\n", UI_WRAP("KICK", IRCMessage), who, oper);
   1360 		nickdel(b, who);
   1361 	}
   1362 	logfmt("%ld KICK %s on %s (%s)\n", time(NULL), who, chan, oper);
   1363 }
   1364 
   1365 void
   1366 recv_luserme(char *host, char *mynick, char *info) {
   1367 	strcpy(nick, mynick);
   1368 	sel->need_redraw |= REDRAW_BAR;
   1369 }
   1370 
   1371 void
   1372 recv_mode(char *u, char *val, char *u2) {
   1373 	if(*nick)
   1374 		return;
   1375 	strcpy(nick, val);
   1376 	sel->need_redraw |= REDRAW_BAR;
   1377 }
   1378 
   1379 void
   1380 recv_motd(char *u, char *u2, char *txt) {
   1381 	bprintf_prefixed(status, "%s\n", txt);
   1382 }
   1383 
   1384 void
   1385 recv_names(char *host, char *par, char *names) {
   1386 	char *chan = skip(skip(par, ' '), ' '); /* skip user and symbol */
   1387 	Buffer *b = getbuf(chan);
   1388 
   1389 	if(!b)
   1390 		b = status;
   1391 	if(!b->recvnames) {
   1392 		freenames(&b->names);
   1393 		b->totnames = 0;
   1394 		b->recvnames = 1;
   1395 	}
   1396 	nicklist(b, names); /* keep as last since names is altered by skip() */
   1397 }
   1398 
   1399 void
   1400 recv_namesend(char *host, char *par, char *names) {
   1401 	char *chan = skip(par, ' ');
   1402 	Buffer *b = getbuf(chan);
   1403 	Nick *n;
   1404 
   1405 	if(!b)
   1406 		b = status;
   1407 	b->recvnames = 0;
   1408 	if(!b->totnames) {
   1409 		bprintf_prefixed(sel, "No names in %s.\n", chan);
   1410 		return;
   1411 	}
   1412 
   1413 	bprintf_prefixed(sel, _C_"%s"_C_" in %s (%d):", UI_WRAP("NAMES", IRCMessage), chan, b->totnames);
   1414 	logfmt("%ld NAMES %s (%d):", time(NULL), chan, b->totnames);
   1415 	for(n = b->names; n; n = n->next) {
   1416 		bprintf(sel, " %s", n->name);
   1417 		logfmt(" %s", n->name);
   1418 	}
   1419 	bprintf(sel, "\n");
   1420 	logfmt("\n");
   1421 }
   1422 
   1423 void
   1424 recv_nick(char *who, char *u, char *upd) {
   1425 	Buffer *b;
   1426 
   1427 	if(!strcmp(who, nick)) {
   1428 		strcpy(nick, upd);
   1429 		sel->need_redraw |= REDRAW_BAR;
   1430 		bprintf_prefixed(sel, "You're now known as %s\n", upd);
   1431 	}
   1432 	for(b = buffers; b; b = b->next) {
   1433 		if(!(ISCHAN(b) && nickget(b, who)))
   1434 			continue;
   1435 		bprintf_prefixed(b, _C_"%s"_C_" %s is now %s\n", UI_WRAP("NICK", IRCMessage), who, upd);
   1436 	}
   1437 	nickmv(who, upd);
   1438 	logfmt("%ld NICK %s is now %s\n", time(NULL), who, upd);
   1439 }
   1440 
   1441 void
   1442 recv_notice(char *who, char *u, char *txt) {
   1443 	bprintf_prefixed(sel, _C_"%s"_C_" %s: %s\n", UI_WRAP("NOTICE", IRCMessage), who, txt);
   1444 	logfmt("%ld NOTICE %s: %s\n", time(NULL), who, txt);
   1445 }
   1446 
   1447 void
   1448 recv_part(char *who, char *chan, char *txt) {
   1449 	Buffer *b = getbuf(chan);
   1450 
   1451 	/* cmd_close() destroy the buffer before PART is received */
   1452 	if(!b)
   1453 		return;
   1454 	if(!strcmp(who, nick)) {
   1455 		destroy(b);
   1456 	}
   1457 	else {
   1458 		bprintf_prefixed(b, _C_"%s"_C_" %s (%s)\n", UI_WRAP("PART", IRCMessage), who, txt);
   1459 		nickdel(b, who);
   1460 	}
   1461 	logfmt("%ld PART %s from %s (%s)\n", time(NULL), who, chan, txt);
   1462 }
   1463 
   1464 void
   1465 recv_ping(char *u, char *u2, char *txt) {
   1466 	sout("PONG %s", txt);
   1467 }
   1468 
   1469 void
   1470 recv_privmsg(char *from, char *to, char *txt) {
   1471 	Buffer *b;
   1472 	int mention, query;
   1473 
   1474 	query = !strcmp(nick, to);
   1475 	mention = strstr(txt, nick) != NULL;
   1476 
   1477 	if(query)
   1478 		to = from;
   1479 	b = getbuf(to);
   1480 	if(!b)
   1481 		b = newbuf(to);
   1482 
   1483 	if(b != sel && (mention || query)) {
   1484 		++b->notify;
   1485 		sel->need_redraw |= REDRAW_BAR;
   1486 		if(NOTIFY_SCRIPT)
   1487 			spawn((const char *[]){ NOTIFY_SCRIPT, from, to, txt, NULL });
   1488 	}
   1489 	bprintf_prefixed(b, _C_"%s"_C_": %s\n", UI_WRAP(from, mention ? NickMention : NickNormal), txt);
   1490 	if(query)
   1491 		logfmt("%ld PRIVMSG from %s on %s: %s\n", time(NULL), from, to, txt);
   1492 	else
   1493 		logfmt("%ld PRIVMSG from %s: %s\n", time(NULL), from, txt);
   1494 }
   1495 
   1496 void
   1497 recv_quit(char *who, char *u, char *txt) {
   1498 	Buffer *b;
   1499 
   1500 	for(b = buffers; b; b = b->next) {
   1501 		if(!(ISCHAN(b) && nickget(b, who)))
   1502 			continue;
   1503 		bprintf_prefixed(b, _C_"%s"_C_" %s (%s)\n", UI_WRAP("QUIT", IRCMessage), who, txt);
   1504 		nickdel(b, who);
   1505 	}
   1506 	logfmt("%ld QUIT %s (%s)\n", time(NULL), who, txt);
   1507 }
   1508 
   1509 void
   1510 recv_topic(char *who, char *chan, char *txt) {
   1511 	bprintf_prefixed(getbuf(chan), "%s changed topic to %s\n", who, txt);
   1512 	logfmt("%ld TOPIC %s (%s): %s\n", time(NULL), chan, who, txt);
   1513 }
   1514 
   1515 void
   1516 recv_topicrpl(char *usr, char *par, char *txt) {
   1517 	char *chan = skip(par, ' ');
   1518 
   1519 	bprintf_prefixed(sel, "Topic on %s is %s\n", chan, txt); /* TODO: who set the topic? */
   1520 }
   1521 
   1522 void
   1523 resize(int x, int y) {
   1524 	Buffer *b;
   1525 
   1526 	rows = x;
   1527 	cols = y;
   1528 	for(b = buffers; b; b = b->next) {
   1529 		if(b->line && b->lnoff)
   1530 			b->lnoff = bufinfo(b->data, b->len, b->line, LineToOffset);
   1531 		b->nlines = bufinfo(b->data, b->len, 0, TotalLines);
   1532 	}
   1533 }
   1534 
   1535 void
   1536 run(void) {
   1537 	Buffer *b;
   1538 	struct timeval tv;
   1539 	fd_set rd;
   1540 	int n, nfds;
   1541 
   1542 	while(running) {
   1543 		FD_ZERO(&rd);
   1544 		FD_SET(0, &rd);
   1545 		tv.tv_sec = 120;
   1546 		tv.tv_usec = 0;
   1547 		nfds = 0;
   1548 		if(srv) {
   1549 			FD_SET(fileno(srv), &rd);
   1550 			nfds = fileno(srv);
   1551 		}
   1552 		n = select(nfds + 1, &rd, 0, 0, &tv);
   1553 		if(n < 0) {
   1554 			if(errno == EINTR)
   1555 				continue;
   1556 			die("select()");
   1557 		}
   1558 		if(n == 0) {
   1559 			if(srv) {
   1560 				if(time(NULL) - trespond >= 300) {
   1561 					hangsup();
   1562 					for(b = buffers; b; b = b->next)
   1563 						bprintf_prefixed(b, "Connection timeout.\n");
   1564 				}
   1565 				else
   1566 					sout("PING %s", host);
   1567 			}
   1568 		}
   1569 		else {
   1570 			if(srv && FD_ISSET(fileno(srv), &rd)) {
   1571 				/* TODO: we should keep reading until CRLF is found. Only at that
   1572 				 * point parsesrv(), sendident(), etc. should be called. */
   1573 				if(fgets(bufin, sizeof bufin, srv) == NULL) {
   1574 					for(b = buffers; b; b = b->next)
   1575 						bprintf_prefixed(b, "%s.\n", online
   1576 							? "Remote host closed connection"
   1577 							: "Cannot connect to the host");
   1578 					hangsup();
   1579 				}
   1580 				else {
   1581 					trespond = time(NULL);
   1582 					parsesrv();
   1583 					if(!online) {
   1584 						online = 1;
   1585 						sendident();
   1586 					}
   1587 				}
   1588 			}
   1589 			if(FD_ISSET(0, &rd))
   1590 				usrin();
   1591 		}
   1592 		if(sel->need_redraw) {
   1593 			draw();
   1594 			sel->need_redraw = 0;
   1595 		}
   1596 	}
   1597 }
   1598 
   1599 void
   1600 scroll(const Arg *arg) {
   1601 	int bufh = rows - 2;
   1602 
   1603 	if(sel->nlines <= bufh)
   1604 		return;
   1605 	if(arg->i == 0) {
   1606 		sel->line = 0;
   1607 		sel->lnoff = 0;
   1608 		sel->need_redraw |= (REDRAW_BUFFER | REDRAW_BAR);
   1609 		return;
   1610 	}
   1611 	if(!sel->line)
   1612 		sel->line = sel->nlines - bufh + 1;
   1613 	sel->line += arg->i;
   1614 	if(sel->line < 1)
   1615 		sel->line = 1;
   1616 	else if(sel->line > sel->nlines - bufh)
   1617 		sel->line = 0;
   1618 	sel->lnoff = bufinfo(sel->data, sel->len, sel->line, LineToOffset);
   1619 	sel->need_redraw |= (REDRAW_BUFFER | REDRAW_BAR);
   1620 }
   1621 
   1622 void
   1623 sendident(void) {
   1624 	sout("NICK %s", nick);
   1625 	sout("USER %s localhost %s :%s", nick, host, nick);
   1626 }
   1627 
   1628 void
   1629 setup(void) {
   1630 	struct termios ti;
   1631 	struct sigaction sa;
   1632 	struct winsize ws;
   1633 
   1634 	/* clean up any zombies immediately */
   1635 	sigchld(0);
   1636 
   1637 	setlocale(LC_CTYPE, "");
   1638 	sa.sa_flags = 0;
   1639 	sigemptyset(&sa.sa_mask);
   1640 	sa.sa_handler = sigwinch;
   1641 	sigaction(SIGWINCH, &sa, NULL);
   1642 	tcgetattr(0, &origti);
   1643 	cfmakeraw(&ti);
   1644 	ti.c_iflag |= ICRNL;
   1645 	ti.c_cc[VMIN] = 0;
   1646 	ti.c_cc[VTIME] = 0;
   1647 	tcsetattr(0, TCSAFLUSH, &ti);
   1648 	ioctl(0, TIOCGWINSZ, &ws);
   1649 	resize(ws.ws_row, ws.ws_col);
   1650 }
   1651 
   1652 void
   1653 sigchld(int unused) {
   1654 	if (signal(SIGCHLD, sigchld) == SIG_ERR)
   1655 		die("can't install SIGCHLD handler:");
   1656 	while(0 < waitpid(-1, NULL, WNOHANG));
   1657 }
   1658 
   1659 void
   1660 sigwinch(int unused) {
   1661 	struct winsize ws;
   1662 
   1663 	ioctl(0, TIOCGWINSZ, &ws);
   1664 	resize(ws.ws_row, ws.ws_col);
   1665 	sel->need_redraw = REDRAW_ALL;
   1666 	printf(CLEAR);
   1667 	draw();
   1668 }
   1669 
   1670 char *
   1671 skip(char *s, char c) {
   1672 	while(*s != c && *s != '\0')
   1673 		s++;
   1674 	if(*s != '\0')
   1675 		*s++ = '\0';
   1676 	return s;
   1677 }
   1678 
   1679 void
   1680 sout(char *fmt, ...) {
   1681 	va_list ap;
   1682 
   1683 	va_start(ap, fmt);
   1684 	vsnprintf(bufout, sizeof bufout, fmt, ap);
   1685 	va_end(ap);
   1686 	fprintf(srv, "%s\r\n", bufout);
   1687 #ifdef DEBUG
   1688 	logfmt("%ld DEBUG sout() %s\n", time(NULL), bufout);
   1689 #endif
   1690 }
   1691 
   1692 void
   1693 spawn(const char **cmd) {
   1694 	if(fork() == 0) {
   1695 		setsid();
   1696 		execvp(cmd[0], (char **)cmd);
   1697 		fprintf(stderr, "%s: execvp %s", argv0, cmd[0]);
   1698 		perror(" failed");
   1699 		exit(0);
   1700 	}
   1701 }
   1702 
   1703 /* https://modern.ircdocs.horse/formatting.html */
   1704 void
   1705 stripformats(char *s) {
   1706 	char *p = s;
   1707 
   1708 	while(*p) {
   1709 		switch(*p) {
   1710 		case 0x02: /* bold */
   1711 		case 0x1D: /* italic */
   1712 		case 0x1F: /* underline */
   1713 		case 0x1E: /* strikethrough */
   1714 		case 0x11: /* monospace */
   1715 		case 0x16: /* reverse */
   1716 		case 0x0F: /* reset */
   1717 			++p;
   1718 			break;
   1719 		case 0x03: /* colors */
   1720 			if(isdigit(*++p) && isdigit(*++p))
   1721 				++p;
   1722 			if(*p == ',' && isdigit(*++p) && isdigit(*++p))
   1723 				++p;
   1724 			break;
   1725 		default:
   1726 			*s++ = *p++;
   1727 			break;
   1728 		}
   1729 	}
   1730 	*s++ = *p;
   1731 }
   1732 
   1733 void
   1734 trim(char *s) {
   1735 	char *e;
   1736 
   1737 	e = s + strlen(s) - 1;
   1738 	while(isspace(*e) && e > s)
   1739 		e--;
   1740 	*(e + 1) = '\0';
   1741 }
   1742 
   1743 int
   1744 uiset(char *buf, int index) {
   1745 	int *color, i, len = 0, n;
   1746 	char *p;
   1747 
   1748 	if(index == -1)
   1749 		return buf ? sprintf(buf, COLRST) : printf(COLRST);
   1750 	color = colors[index];
   1751 	if(!color)
   1752 		return -1;
   1753 	for(i = 0; color[i] != -1; ++i) {
   1754 		switch(i) {
   1755 		case 0:  p = COLFG; break;
   1756 		case 1:  p = COLBG; break;
   1757 		default: p = ATTR;  break;
   1758 		}
   1759 		n = buf ? sprintf(&buf[len], p, color[i]) : printf(p, color[i]);
   1760 		if(n < 0)
   1761 			return -1;
   1762 		len += n;
   1763 	}
   1764 	return len;
   1765 }
   1766 
   1767 void
   1768 usage(void) {
   1769 	die("Usage: %s [-v] [-hpnl <arg>]", argv0);
   1770 }
   1771 
   1772 void
   1773 usrin(void) {
   1774 	char graph[4];
   1775 	int key, nb, i;
   1776 
   1777 	key = getkey();
   1778 	for(i = 0; i < LENGTH(keys); ++i) {
   1779 		if(keys[i].key == key) {
   1780 			keys[i].func(&keys[i].arg);
   1781 			return;
   1782 		}
   1783 	}
   1784 
   1785 	if(key >= KeyFirst && key <= KeyLast)
   1786 		return;
   1787 	if(iscntrl(key)) {
   1788 		while((key = readchar()) != EOF);
   1789 		return;
   1790 	}
   1791 
   1792 	nb = UTF8BYTES(key);
   1793 	graph[0] = key;
   1794 	for(i = 1; i < nb; ++i) {
   1795 		key = readchar();
   1796 		if(key == EOF) {
   1797 			/* TODO: preserve the state and return */
   1798 			while((key = readchar()) == EOF);
   1799 		}
   1800 		graph[i] = key;
   1801 	}
   1802 
   1803 	/* prevent overflow */
   1804 	if(sel->cmdlen + nb >= sizeof sel->cmdbuf)
   1805 		return;
   1806 
   1807 	/* move nb bytes to the right */
   1808 	memmove(&sel->cmdbuf[sel->cmdoff+nb],
   1809 		&sel->cmdbuf[sel->cmdoff],
   1810 		sel->cmdlen - sel->cmdoff);
   1811 
   1812 	/* insert nb bytes at current offset */
   1813 	memcpy(&sel->cmdbuf[sel->cmdoff], graph, nb);
   1814 
   1815 	sel->cmdlen += nb;
   1816 	sel->cmdbuf[sel->cmdlen] = '\0';
   1817 	sel->cmdoff += nb;
   1818 
   1819 	sel->need_redraw |= REDRAW_CMDLN;
   1820 }
   1821 
   1822 char *
   1823 wordleft(char *str, int offset, int *size) {
   1824 	char *s = &str[offset], *e;
   1825 
   1826 	while(s != str && (*s == ' ' || *s == '\0'))
   1827 		--s;
   1828 	if(!*s || *s == ' ')
   1829 		return NULL;
   1830 	while(s != str && *(s - 1) != ' ')
   1831 		--s;
   1832 	if(size) {
   1833 		for(e = s + 1; *e != '\0' && *e != ' '; ++e);
   1834 		*size = e - s;
   1835 	}
   1836 	return s;
   1837 }
   1838 
   1839 int
   1840 main(int argc, char *argv[]) {
   1841 	const char *user = getenv("USER");
   1842 
   1843 	ARGBEGIN {
   1844 	case 'h': strncpy(host, EARGF(usage()), sizeof host); break;
   1845 	case 'p': strncpy(port, EARGF(usage()), sizeof port); break;
   1846 	case 'n': strncpy(nick, EARGF(usage()), sizeof nick); break;
   1847 	case 'l': strncpy(logfile, EARGF(usage()), sizeof logfile); break;
   1848 	case 'v': die("circo-"VERSION);
   1849 	default: usage();
   1850 	} ARGEND;
   1851 
   1852 	if(!*nick)
   1853 		strncpy(nick, user ? user : "circo", sizeof nick);
   1854 	setup();
   1855 	if(*logfile)
   1856 		logp = fopen(logfile, "a");
   1857 	setbuf(stdout, NULL);
   1858 	sel = status = newbuf("status");
   1859 	printf(CLEAR);
   1860 	draw();
   1861 	run();
   1862 	mvprintf(1, rows, "\n");
   1863 	cleanup();
   1864 	return 0;
   1865 }