hirc

[archived] IRC client
git clone https://hhvn.uk/hirc
git clone git://hhvn.uk/hirc
Log | Files | Refs

ui.c (26711B)


      1 /*
      2  * src/ui.c from hirc
      3  *
      4  * Copyright (c) 2021-2022 hhvn <dev@hhvn.uk>
      5  *
      6  * Permission to use, copy, modify, and distribute this software for any
      7  * purpose with or without fee is hereby granted, provided that the above
      8  * copyright notice and this permission notice appear in all copies.
      9  *
     10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     17  *
     18  */
     19 
     20 #include <errno.h>
     21 #include <ctype.h>
     22 #include <wchar.h>
     23 #include <wctype.h>
     24 #include <stdarg.h>
     25 #include <string.h>
     26 #include <stdlib.h>
     27 #include <locale.h>
     28 #include <ncurses.h>
     29 #ifdef TLS
     30 #include <tls.h>
     31 #endif /* TLS */
     32 #include "hirc.h"
     33 #include "data/colours.h"
     34 
     35 int uineedredraw = 0;
     36 int nouich = 0;
     37 
     38 struct Window windows[Win_last] = {
     39 	[Win_dummy]	= {.handler = NULL, .scroll = -1},
     40 	[Win_main]	= {.handler = ui_draw_main, .scroll = -1},
     41 	[Win_input]	= {.handler = ui_draw_input, .scroll = -1},
     42 	[Win_nicklist]	= {.handler = ui_draw_nicklist, .scroll = -1},
     43 	[Win_buflist]	= {.handler = ui_draw_buflist, .scroll = -1},
     44 };
     45 
     46 struct {
     47 	wchar_t string[INPUT_MAX];
     48 	unsigned counter;
     49 	char *history[INPUT_HIST_MAX];
     50 	int histindex;
     51 } input;
     52 
     53 struct Selected selected;
     54 struct Keybind *keybinds = NULL;
     55 
     56 void
     57 ui_error_(char *file, int line, const char *func, char *fmt, ...) {
     58 	char msg[1024];
     59 	va_list ap;
     60 
     61 	va_start(ap, fmt);
     62 	vsnprintf(msg, sizeof(msg), fmt, ap);
     63 	va_end(ap);
     64 
     65 	hist_format(selected.history, Activity_error, HIST_UI|HIST_ERR|HIST_NIGN,
     66 			"SELF_ERROR %s %d %s :%s",
     67 			file, line, func, msg);
     68 }
     69 
     70 void
     71 ui_perror_(char *file, int line, const char *func, char *str) {
     72 	hist_format(selected.history, Activity_error, HIST_UI|HIST_ERR|HIST_NIGN,
     73 			"SELF_ERROR %s %d %s :%s: %s",
     74 			file, line, func, str, strerror(errno));
     75 }
     76 
     77 #ifdef TLS
     78 void
     79 ui_tls_config_error_(char *file, int line, const char *func, struct tls_config *config, char *str) {
     80 	hist_format(selected.history, Activity_error, HIST_UI|HIST_ERR|HIST_NIGN,
     81 			"SELF_ERROR %s %d %s :%s: %s",
     82 			file, line, func, str, tls_config_error(config));
     83 }
     84 
     85 void
     86 ui_tls_error_(char *file, int line, const char *func, struct tls *ctx, char *str) {
     87 	hist_format(selected.history, Activity_error, HIST_UI|HIST_ERR|HIST_NIGN,
     88 			"SELF_ERROR %s %d %s :%s: %s",
     89 			file, line, func, str, tls_error(ctx));
     90 }
     91 #endif /* TLS */
     92 
     93 void
     94 ui_init(void) {
     95 	setlocale(LC_ALL, "en_US.UTF-8");
     96 	initscr();
     97 	start_color();
     98 	use_default_colors();
     99 	raw();
    100 	noecho();
    101 	nonl(); /* get ^j */
    102 
    103 	input.string[0] = L'\0';
    104 	memset(input.history, 0, sizeof(input.history));
    105 	input.counter = 0;
    106 	input.histindex = -1;
    107 
    108 	windows[Win_nicklist].location = config_getl("nicklist.location");
    109 	windows[Win_buflist].location = config_getl("buflist.location");
    110 
    111 	windows[Win_dummy].window = stdscr;
    112 	windows[Win_main].window = newwin(0, 0, 0, 0);
    113 	windows[Win_input].window = newwin(0, 0, 0, 0);
    114 
    115 	windows[Win_dummy].location = Location_hidden;
    116 	windows[Win_main].location = -1;
    117 	windows[Win_input].location = -1;
    118 	if (windows[Win_nicklist].location)
    119 		windows[Win_nicklist].window = newwin(0, 0, 0, 0);
    120 	if (windows[Win_buflist].location)
    121 		windows[Win_buflist].window = newwin(0, 0, 0, 0);
    122 
    123 	nodelay(windows[Win_input].window, TRUE);
    124 	keypad(windows[Win_input].window, TRUE);
    125 
    126 	ui_redraw();
    127 	ui_select(NULL, NULL);
    128 }
    129 
    130 int
    131 ui_get_pair(short fg, short bg) {
    132 	static unsigned short pair_map[HIRC_COLOURS][HIRC_COLOURS];
    133 	static int needinit = 1;
    134 	short j, k;
    135 	int i;
    136 
    137 	if (needinit) {
    138 		init_pair(1, -1, -1);
    139 		for (i=2, j=0; j < HIRC_COLOURS; j++) {
    140 			for (k=0; k < HIRC_COLOURS; k++) {
    141 				init_pair(i, colourmap[j], colourmap[k]);
    142 				pair_map[j][k] = i;
    143 				i++;
    144 			}
    145 		}
    146 		needinit = 0;
    147 	}
    148 
    149 	if (fg >= HIRC_COLOURS || bg >= HIRC_COLOURS)
    150 		return 1;
    151 
    152 	return pair_map[fg][bg];
    153 }
    154 
    155 void
    156 ui_placewindow(struct Window *window) {
    157 	if (window->location != Location_hidden) {
    158 		wresize(window->window, window->h, window->w);
    159 		mvwin(window->window, window->y, window->x);
    160 		wrefresh(window->window);
    161 	}
    162 }
    163 
    164 void
    165 ui_read(void) {
    166 	static wchar_t *backup = NULL;
    167 	static int didcomplete = 0;
    168 	struct Keybind *kp;
    169 	char *str;
    170 	wint_t key;
    171 	int ret;
    172 	int savecounter;
    173 
    174 	savecounter = input.counter;
    175 
    176 	/* Loop over input, return only if ERR is received.
    177 	 * Normally wget_wch exits fast enough that unless something
    178 	 * is being pasted in this won't waste any time that should
    179 	 * be used for other stuff */
    180 	for (;;) {
    181 		ret = wget_wch(windows[Win_input].window, &key);
    182 		if (ret == ERR) {
    183 			/* no input received */
    184 			/* Match keybinds here - this allows multikey
    185 			 * bindings such as those with alt, but since
    186 			 * there is no delay with wgetch() it's unlikely
    187 			 * that the user pressing multiple keys will
    188 			 * trigger one. */
    189 			if (input.counter != savecounter) {
    190 				for (kp = keybinds; kp; kp = kp->next) {
    191 					if ((input.counter - savecounter) == wcslen(kp->wbinding) &&
    192 							wcsncmp(kp->wbinding, &input.string[savecounter], (input.counter - savecounter)) == 0) {
    193 						command_eval(selected.server, kp->cmd);
    194 						memmove(&input.string[savecounter],
    195 								&input.string[input.counter],
    196 								(wcslen(&input.string[input.counter]) + 1) * sizeof(wchar_t));
    197 						input.counter = savecounter;
    198 						return;
    199 					}
    200 				}
    201 
    202 				if (input.histindex) {
    203 					pfree(&backup);
    204 					backup = NULL;
    205 					input.histindex = -1;
    206 				}
    207 			}
    208 
    209 			windows[Win_input].handler();
    210 			wrefresh(windows[Win_input].window);
    211 			windows[Win_input].refresh = 0;
    212 			return;
    213 		}
    214 
    215 		switch (key) {
    216 		case KEY_RESIZE:
    217 			ui_redraw();
    218 			break;
    219 		case KEY_BACKSPACE:
    220 			if (input.counter) {
    221 				memmove(input.string + input.counter - 1,
    222 						input.string + input.counter,
    223 						(wcslen(input.string + input.counter) + 1) * sizeof(wchar_t));
    224 				input.counter--;
    225 			}
    226 			break;
    227 		case KEY_UP:
    228 			if (input.histindex < INPUT_HIST_MAX && input.history[input.histindex + 1]) {
    229 				if (input.histindex == -1)
    230 					backup = ewcsdup(input.string);
    231 				input.histindex++;
    232 				mbstowcs(input.string, input.history[input.histindex], sizeof(input.string));
    233 				input.counter = wcslen(input.string);
    234 			}
    235 			return; /* return so histindex and backup aren't reset */
    236 		case KEY_DOWN:
    237 			if (input.histindex > -1) {
    238 				input.histindex--;
    239 				if (input.histindex == -1) {
    240 					if (backup)
    241 						wcslcpy(input.string, backup, sizeof(input.string));
    242 					pfree(&backup);
    243 					backup = NULL;
    244 				} else {
    245 					mbstowcs(input.string, input.history[input.histindex], sizeof(input.string));
    246 				}
    247 				input.counter = wcslen(input.string);
    248 			}
    249 			return; /* return so histindex and backup aren't reset */
    250 		case KEY_LEFT:
    251 			if (input.counter)
    252 				input.counter--;
    253 			break;
    254 		case KEY_RIGHT:
    255 			if (input.string[input.counter])
    256 				input.counter++;
    257 			break;
    258 		case '\t':
    259 			complete(input.string, sizeof(input.string), &input.counter);
    260 			break;
    261 		case KEY_ENTER:
    262 		case '\r':
    263 			if (didcomplete && input.string[input.counter - 1] == L' ')
    264 				input.string[input.counter - 1] = '\0';
    265 			if (*input.string != L'\0') {
    266 				/* no need to free str as assigned to input.history[0] */
    267 				str = wctos(input.string);
    268 				command_eval(selected.server, str);
    269 				pfree(&input.history[INPUT_HIST_MAX - 1]);
    270 				memmove(input.history + 1, input.history, (sizeof(input.history) / INPUT_HIST_MAX) * (INPUT_HIST_MAX - 1));
    271 				input.history[0] = str;
    272 				input.string[0] = '\0';
    273 				input.counter = 0;
    274 				input.histindex = -1;
    275 			}
    276 			break;
    277 		default:
    278 			if (iswprint(key) || (iscntrl(key) && input.counter + 1 != sizeof(input.string))) {
    279 					memmove(input.string + input.counter + 1,
    280 							input.string + input.counter,
    281 							(wcslen(input.string + input.counter) + 1) * sizeof(wchar_t));
    282 					input.string[input.counter++] = (wchar_t)key;
    283 			}
    284 			break;
    285 		}
    286 
    287 		/* Completion adds a space after the completed text: this
    288 		 * serves the purpose of showing the user that it has been
    289 		 * completely successfully and unambiguously, and allowing them
    290 		 * to begin writing quickly without having to type a space
    291 		 * (this is common in most tab-completion systems).
    292 		 *
    293 		 * The problem is that if something is completed at the end of
    294 		 * a command, and the user then hits enter, that space would be
    295 		 * passed to command_* functions (previous versions of hirc did
    296 		 * this, and put the onus on commands to strip trailing
    297 		 * spaces).
    298 		 *
    299 		 * Instead, this variable is set so that trailing spaces will
    300 		 * be stripped when tab completed, but not ones inserted
    301 		 * manually, inside ui_read() - see the KEY_ENTER case. */
    302 		didcomplete = (key == '\t');
    303 	}
    304 }
    305 
    306 void
    307 ui_redraw(void) {
    308 	struct History *p;
    309 	char *fmt;
    310 	long nicklistwidth, buflistwidth;
    311 	int x = 0, rx = 0;
    312 	int i;
    313 
    314 	nicklistwidth = config_getl("nicklist.width");
    315 	buflistwidth = config_getl("buflist.width");
    316 
    317 	/* TODO: what if nicklistwidth or buflistwidth is too big? */
    318 	if (windows[Win_buflist].location == Location_left) {
    319 		windows[Win_buflist].x = windows[Win_buflist].y = 0;
    320 		windows[Win_buflist].h = LINES;
    321 		windows[Win_buflist].w = buflistwidth;
    322 		x = windows[Win_buflist].w + 1;
    323 	}
    324 	if (windows[Win_nicklist].location == Location_left) {
    325 		windows[Win_nicklist].x = windows[Win_buflist].y = 0;
    326 		windows[Win_nicklist].h = LINES;
    327 		windows[Win_nicklist].w = nicklistwidth;
    328 		x = windows[Win_nicklist].w + 1;
    329 	}
    330 	if (windows[Win_buflist].location == Location_right) {
    331 		windows[Win_buflist].x = COLS - buflistwidth;
    332 		windows[Win_buflist].y = 0;
    333 		windows[Win_buflist].h = LINES;
    334 		windows[Win_buflist].w = buflistwidth;
    335 		rx = buflistwidth + 1;
    336 	}
    337 	if (windows[Win_nicklist].location == Location_right) {
    338 		windows[Win_nicklist].x = COLS - nicklistwidth;
    339 		windows[Win_nicklist].y = 0;
    340 		windows[Win_nicklist].h = LINES;
    341 		windows[Win_nicklist].w = nicklistwidth;
    342 		rx = nicklistwidth + 1;
    343 	}
    344 
    345 	windows[Win_main].x = x;
    346 	windows[Win_main].y = 0;
    347 	windows[Win_main].h = LINES - 2;
    348 	windows[Win_main].w = COLS - x - rx;
    349 
    350 	windows[Win_input].x = x;
    351 	windows[Win_input].y = LINES - 1;
    352 	windows[Win_input].h = 1;
    353 	windows[Win_input].w = COLS - x - rx;
    354 
    355 	windows[Win_dummy].x = 0;
    356 	windows[Win_dummy].y = 0;
    357 	windows[Win_dummy].h = LINES;
    358 	windows[Win_dummy].w = COLS;
    359 
    360 	fmt = format(NULL, config_gets("format.ui.separator.horizontal"), NULL);
    361 	for (i = x; i <= COLS - rx; i++) {
    362 		wmove(windows[Win_dummy].window, LINES - 2, i);
    363 		ui_wprintc(&windows[Win_dummy], 1, "%s", fmt);
    364 	}
    365 
    366 	if (x) {
    367 		fmt = format(NULL, config_gets("format.ui.separator.vertical"), NULL);
    368 		for (i = 0; i <= LINES; i++) {
    369 			wmove(windows[Win_dummy].window, i, x - 1);
    370 			ui_wprintc(&windows[Win_dummy], 1, "%s", fmt);
    371 		}
    372 
    373 		fmt = format(NULL, config_gets("format.ui.separator.split.left"), NULL);
    374 		wmove(windows[Win_dummy].window, LINES - 2, x - 1);
    375 		ui_wprintc(&windows[Win_dummy], 1, "%s", fmt);
    376 	}
    377 
    378 	if (rx) {
    379 		fmt = format(NULL, config_gets("format.ui.separator.vertical"), NULL);
    380 		for (i = 0; i <= LINES; i++) {
    381 			wmove(windows[Win_dummy].window, i, COLS - rx);
    382 			ui_wprintc(&windows[Win_dummy], 1, "%s", fmt);
    383 		}
    384 
    385 		fmt = format(NULL, config_gets("format.ui.separator.split.right"), NULL);
    386 		wmove(windows[Win_dummy].window, LINES - 2, COLS - rx);
    387 		ui_wprintc(&windows[Win_dummy], 1, "%s", fmt);
    388 	}
    389 
    390 	refresh();
    391 
    392 	for (i = 0; i < Win_last; i++) {
    393 		if (windows[i].location) {
    394 			ui_placewindow(&windows[i]);
    395 			windows[i].refresh = 1;
    396 		}
    397 	}
    398 
    399 	/* Clear format element of history.
    400 	 * Formats need updating if the windows are resized,
    401 	 * or format.* settings are changed. */
    402 	if (selected.history) {
    403 		for (p = selected.history->history; p; p = p->next) {
    404 			if (p->format)
    405 				pfree(&p->format);
    406 			if (p->rformat)
    407 				pfree(&p->rformat);
    408 		}
    409 	}
    410 }
    411 
    412 void
    413 ui_draw_input(void) {
    414 	wchar_t *p;
    415 	int offset;
    416 	int x;
    417 
    418 	werase(windows[Win_input].window);
    419 
    420 	/* Round input.counter down to the nearest windows[Win_input].w.
    421 	 * This gives "pages" that are each as long as the width of the input window */
    422 	offset = ((int) input.counter / windows[Win_input].w) * windows[Win_input].w;
    423 	for (x=0, p = input.string + offset; p && *p && x < windows[Win_input].w; p++, x++) {
    424 		if (iscntrl(*p)) {
    425 			/* adding 64 will turn ^C into C */
    426 			wattron(windows[Win_input].window, A_REVERSE);
    427 			waddch(windows[Win_input].window, *p + 64);
    428 			wattroff(windows[Win_input].window, A_REVERSE);
    429 		} else {
    430 			waddnwstr(windows[Win_input].window, p, 1);
    431 		}
    432 	}
    433 	wmove(windows[Win_input].window, 0, input.counter - offset);
    434 }
    435 
    436 void
    437 ui_draw_nicklist(void) {
    438 	struct Nick *p;
    439 	int y = 0, i;
    440 
    441 	werase(windows[Win_nicklist].window);
    442 
    443 	if (!selected.channel || !windows[Win_nicklist].location)
    444 		return;
    445 
    446 	wmove(windows[Win_nicklist].window, 0, 0);
    447 
    448 	nick_sort(&selected.channel->nicks, selected.server);
    449 
    450 	for (i=0, p = selected.channel->nicks; p && p->next && p->next->next && i < windows[Win_nicklist].scroll; i++)
    451 		p = p->next;
    452 	if (i != 0) {
    453 		ui_wprintc(&windows[Win_nicklist], 1, "%s\n", format(NULL, config_gets("format.ui.nicklist.more"), NULL));
    454 		y++;
    455 		p = p->next;
    456 		windows[Win_nicklist].scroll = i;
    457 	}
    458 
    459 	for (; p && y < windows[Win_nicklist].h - (p->next ? 1 : 0); p = p->next, y++) {
    460 		ui_wprintc(&windows[Win_nicklist], 1, "%c%02d%c%s\n",
    461 				3 /* ^C */, nick_getcolour(p), p->priv, p->nick);
    462 	}
    463 
    464 	if (p)
    465 		ui_wprintc(&windows[Win_nicklist], 1, "%s\n", format(NULL, config_gets("format.ui.nicklist.more"), NULL));
    466 }
    467 
    468 int
    469 ui_buflist_count(int *ret_servers, int *ret_channels, int *ret_queries) {
    470 	struct Server *sp;
    471 	struct Channel *chp;
    472 	int sc, cc, pc;
    473 
    474 	for (sc = cc = pc = 0, sp = servers; sp; sp = sp->next, sc++) {
    475 		for (chp = sp->channels; chp; chp = chp->next, cc++)
    476 			;
    477 		for (chp = sp->queries; chp; chp = chp->next, pc++)
    478 			;
    479 	}
    480 
    481 	if (ret_servers)
    482 		*ret_servers = sc;
    483 	if (ret_channels)
    484 		*ret_channels = cc;
    485 	if (ret_queries)
    486 		*ret_channels = pc;
    487 
    488 	return sc + cc + pc + 1;
    489 }
    490 
    491 int
    492 ui_buflist_get(int num, struct Server **server, struct Channel **chan) {
    493 	struct Server *sp;
    494 	struct Channel *chp;
    495 	int i;
    496 
    497 	if (num <= 0) {
    498 		ui_error("buffer index greater than 0 expected", NULL);
    499 		return -1;
    500 	}
    501 
    502 	if (num == 1) {
    503 		*server = NULL;
    504 		*chan = NULL;
    505 		return 0;
    506 	}
    507 
    508 	for (i = 2, sp = servers; sp; sp = sp->next) {
    509 		if (i == num) {
    510 			*server = sp;
    511 			*chan = NULL;
    512 			return 0;
    513 		}
    514 		i++; /* increment before moving
    515 			to channel section, not
    516 			int for (;; ..) */
    517 
    518 		for (chp = sp->channels; chp; chp = chp->next, i++) {
    519 			if (i == num) {
    520 				*server = sp;
    521 				*chan = chp;
    522 				return 0;
    523 			}
    524 		}
    525 		for (chp = sp->queries; chp; chp = chp->next, i++) {
    526 			if (i == num) {
    527 				*server = sp;
    528 				*chan = chp;
    529 				return 0;
    530 			}
    531 		}
    532 	}
    533 
    534 	ui_error("couldn't find buffer with index %d", num);
    535 	return -1;
    536 }
    537 
    538 void
    539 ui_draw_buflist(void) {
    540 	struct Server *sp;
    541 	struct Channel *chp, *prp;
    542 	int i = 1, scroll;
    543 	char *actind[Activity_last];
    544 	char *oldind, *indicator;
    545 
    546 	oldind = estrdup(format(NULL, config_gets("format.ui.buflist.old"), NULL));
    547 	actind[Activity_none] = estrdup(format(NULL, config_gets("format.ui.buflist.activity.none"), NULL));
    548 	actind[Activity_status] = estrdup(format(NULL, config_gets("format.ui.buflist.activity.status"), NULL));
    549 	actind[Activity_error] = estrdup(format(NULL, config_gets("format.ui.buflist.activity.error"), NULL));
    550 	actind[Activity_message] = estrdup(format(NULL, config_gets("format.ui.buflist.activity.message"), NULL));
    551 	actind[Activity_hilight] = estrdup(format(NULL, config_gets("format.ui.buflist.activity.hilight"), NULL));
    552 
    553 	werase(windows[Win_buflist].window);
    554 
    555 	if (windows[Win_buflist].scroll < 0)
    556 		scroll = 0;
    557 	else
    558 		scroll = windows[Win_buflist].scroll;
    559 
    560 	if (!windows[Win_buflist].location)
    561 		return;
    562 
    563 	if (scroll > 0) {
    564 		ui_wprintc(&windows[Win_buflist], 1, "%s\n", format(NULL, config_gets("format.ui.buflist.more"), NULL));
    565 	} else if (scroll < i) {
    566 		if (selected.history == main_buf)
    567 			wattron(windows[Win_buflist].window, A_BOLD);
    568 		ui_wprintc(&windows[Win_buflist], 1, "%02d: %s\n", i, "hirc");
    569 		wattroff(windows[Win_buflist].window, A_BOLD);
    570 	}
    571 	i++;
    572 
    573 	for (sp = servers; sp && (i - scroll - 1) < windows[Win_buflist].h; sp = sp->next) {
    574 		if (scroll < i - 1) {
    575 			if (selected.server == sp && !selected.channel)
    576 				wattron(windows[Win_buflist].window, A_BOLD);
    577 			indicator = (sp->status == ConnStatus_notconnected) ? oldind : actind[sp->history->activity];
    578 			ui_wprintc(&windows[Win_buflist], 1, "%02d: %s─ %s%s\n", i, sp->next ? "├" : "└", indicator, sp->name);
    579 			wattrset(windows[Win_buflist].window, A_NORMAL);
    580 		}
    581 		i++;
    582 
    583 		for (chp = sp->channels; chp && (i - scroll - 1) < windows[Win_buflist].h; chp = chp->next) {
    584 			if (scroll < i - 1) {
    585 				if (selected.channel == chp)
    586 					wattron(windows[Win_buflist].window, A_BOLD);
    587 				indicator = (chp->old) ? oldind : actind[chp->history->activity];
    588 				ui_wprintc(&windows[Win_buflist], 1, "%02d: %s  %s─ %s%s\n", i,
    589 						sp->next ? "│" : " ", chp->next || sp->queries ? "├" : "└", indicator, chp->name);
    590 				wattrset(windows[Win_buflist].window, A_NORMAL);
    591 			}
    592 			i++;
    593 		}
    594 
    595 		for (prp = sp->queries; prp && (i - scroll - 1) < windows[Win_buflist].h; prp = prp->next) {
    596 			if (scroll < i - 1) {
    597 				if (selected.channel == prp)
    598 					wattron(windows[Win_buflist].window, A_BOLD);
    599 				indicator = (prp->old) ? oldind : actind[prp->history->activity];
    600 				ui_wprintc(&windows[Win_buflist], 1, "%02d: %s  %s─ %s%s\n", i,
    601 						sp->next ? "│" : " ", prp->next ? "├" : "└", indicator, prp->name);
    602 				wattrset(windows[Win_buflist].window, A_NORMAL);
    603 			}
    604 			i++;
    605 		}
    606 	}
    607 
    608 	if (i <= ui_buflist_count(NULL, NULL, NULL)) {
    609 		wmove(windows[Win_buflist].window, windows[Win_buflist].h - 1, 0);
    610 		ui_wprintc(&windows[Win_buflist], 1, "%s\n", format(NULL, config_gets("format.ui.buflist.more"), NULL));
    611 		wclrtoeol(windows[Win_buflist].window);
    612 	}
    613 }
    614 
    615 int
    616 ui_wprintc(struct Window *window, int lines, char *fmt, ...) {
    617 	char *str;
    618 	wchar_t *wcs, *s;
    619 	va_list ap;
    620 	int ret;
    621 	attr_t curattr;
    622 	short temp; /* used only for wattr_get,
    623 		     because ncurses is dumb */
    624 	int cc, lc, elc;
    625 	char colourbuf[2][3];
    626 	int colours[2];
    627 	int bold = 0;
    628 	int underline = 0;
    629 	int reverse = 0;
    630 	int italic = 0;
    631 
    632 	va_start(ap, fmt);
    633 	ret = vsnprintf(str, 0, fmt, ap) + 1;
    634 	va_end(ap);
    635 	str = emalloc(ret);
    636 
    637 	va_start(ap, fmt);
    638 	ret = vsnprintf(str, ret, fmt, ap);
    639 	va_end(ap);
    640 	if (ret < 0)
    641 		return ret;
    642 
    643 	if (lines < 0)
    644 		ui_strlenc(window, str, &elc);
    645 	elc -= 1;
    646 
    647 	wcs = stowc(str);
    648 	pfree(&str);
    649 
    650 	for (ret = cc = lc = 0, s = wcs; s && *s; s++) {
    651 		switch (*s) {
    652 		case 2: /* ^B */
    653 			if (bold)
    654 				wattroff(window->window, A_BOLD);
    655 			else
    656 				wattron(window->window, A_BOLD);
    657 			bold = bold ? 0 : 1;
    658 			break;
    659 		case 3: /* ^C */
    660 			memset(colourbuf, '\0', sizeof(colourbuf));
    661 			/* This section may look a little confusing, but I didn't know
    662 			 * what better way I could do it (a loop for two things? ehm).
    663 			 *
    664 			 * If you want to understand it, I would start with simulating
    665 			 * it on a peice of paper, something like this:
    666 			 *
    667 			 * {   ,   ,'\0'}   ^C01,02
    668 			 * {   ,   ,'\0'}
    669 			 *
    670 			 * Draw a line over *s each time you advance s. */
    671 			if (*s && isdigit(*(s+1))) {
    672 				colourbuf[0][0] = *(s+1);
    673 				s += 1;
    674 			}
    675 			if (*s && isdigit(*(s+1))) {
    676 				colourbuf[0][1] = *(s+1);
    677 				s += 1;
    678 			}
    679 			if (*s && *(s+1) == ',' && isdigit(*(s+2))) {
    680 				colourbuf[1][0] = *(s+2);
    681 				s += 2;
    682 			}
    683 			if (colourbuf[1][0] && *s && isdigit(*(s+1))) {
    684 				colourbuf[1][1] = *(s+1);
    685 				s += 1;
    686 			}
    687 
    688 			colours[0] = colourbuf[0][0] ? atoi(colourbuf[0]) : 99;
    689 			colours[1] = colourbuf[1][0] ? atoi(colourbuf[1]) : 99;
    690 
    691 			wattr_get(window->window, &curattr, &temp, NULL);
    692 			wattr_set(window->window, curattr, ui_get_pair(colours[0], colours[1]), NULL);
    693 			break;
    694 		case 9: /* ^I */
    695 #ifdef A_ITALIC
    696 			if (italic)
    697 				wattroff(window->window, A_ITALIC);
    698 			else
    699 				wattron(window->window, A_ITALIC);
    700 			italic = italic ? 0 : 1;
    701 #endif /* A_ITALIC */
    702 			break;
    703 		case 15: /* ^O */
    704 			bold = 0;
    705 			underline = 0;
    706 			reverse = 0;
    707 			italic = 0;
    708 			/* Setting A_NORMAL turns everything off,
    709 			 * without using 5 different attroffs */
    710 			wattrset(window->window, A_NORMAL);
    711 			break;
    712 		case 18: /* ^R */
    713 			if (reverse)
    714 				wattroff(window->window, A_REVERSE);
    715 			else
    716 				wattron(window->window, A_REVERSE);
    717 			reverse = reverse ? 0 : 1;
    718 			break;
    719 		case 21: /* ^U */
    720 			if (underline)
    721 				wattroff(window->window, A_UNDERLINE);
    722 			else
    723 				wattron(window->window, A_UNDERLINE);
    724 			underline = underline ? 0 : 1;
    725 			break;
    726 		default:
    727 			if (lines > 0 && lc >= lines)
    728 				goto end;
    729 			if (!lines || lines > 0 || (lines < 0 && lc >= elc + lines)) {
    730 				waddnwstr(window->window, s, 1);
    731 				ret++;
    732 				cc++;
    733 			}
    734 			if (cc == window->w || *s == L'\n') {
    735 				lc++;
    736 				cc = 0;
    737 			}
    738 			break;
    739 		}
    740 	}
    741 
    742 end:
    743 	pfree(&wcs);
    744 	bold = 0;
    745 	underline =0;
    746 	reverse = 0;
    747 	italic = 0;
    748 	wattrset(window->window, A_NORMAL);
    749 
    750 	return ret;
    751 }
    752 
    753 int
    754 ui_strlenc(struct Window *window, char *s, int *lines) {
    755 	int ret, cc, lc;
    756 
    757 	for (ret = cc = lc = 0; s && *s; s++) {
    758 		switch (*s) {
    759 		case 2:  /* ^B */
    760 		case 9:  /* ^I */
    761 		case 15: /* ^O */
    762 		case 18: /* ^R */
    763 		case 21: /* ^U */
    764 			break;
    765 		case 3:  /* ^C */
    766 			if (*s && isdigit(*(s+1)))
    767 				s += 1;
    768 			if (*s && isdigit(*(s+1)))
    769 				s += 1;
    770 			if (*s && *(s+1) == ',' && isdigit(*(s+2)))
    771 				s += 2;
    772 			if (*s && *(s-1) == ',' && isdigit(*(s+1)))
    773 				s += 1;
    774 			break;
    775 		default:
    776 			/* Naive utf-8 handling: the 2-nth byte always follows
    777 			 * 10xxxxxxx, so don't count it.
    778 			 *
    779 			 * I figured it would be better to do it this way than
    780 			 * to use widechars, as then there would need to be a
    781 			 * conversion for each call. */
    782 			if ((*s & 0xC0) != 0x80)
    783 				cc++;
    784 			ret++;
    785 			if ((window && cc == window->w) || *s == '\n') {
    786 				lc++;
    787 				cc = 0;
    788 			}
    789 			break;
    790 		}
    791 	}
    792 
    793 	if (lines)
    794 		*lines = lc + 1;
    795 	return ret;
    796 }
    797 
    798 void
    799 ui_draw_main(void) {
    800 	struct History *p, *hp;
    801 	int y, lines;
    802 	int i;
    803 
    804 	werase(windows[Win_main].window);
    805 
    806 	for (i=0, p = selected.history->history, hp = NULL; p; p = p->next) {
    807 		if (!(p->options & HIST_SHOW) || ((p->options & HIST_IGN) && !selected.showign))
    808 			continue;
    809 		if (windows[Win_main].scroll > 0 && !hp && !p->next)
    810 			hp = p;
    811 		else if (i == windows[Win_main].scroll && !hp)
    812 			hp = p;
    813 
    814 		if (i < windows[Win_main].scroll)
    815 			i++;
    816 		if (!p->format)
    817 			p->format = estrdup(format(&windows[Win_main], NULL, p));
    818 	}
    819 
    820 	if (windows[Win_main].scroll > 0)
    821 		windows[Win_main].scroll = i;
    822 	if (!hp)
    823 		hp = selected.history->history;
    824 
    825 	for (y = windows[Win_main].h; hp && y > 0; hp = hp->next) {
    826 		if (!(hp->options & HIST_SHOW) || !hp->format || ((hp->options & HIST_IGN) && !selected.showign))
    827 			continue;
    828 		if (ui_strlenc(&windows[Win_main], hp->format, &lines) <= 0)
    829 			continue;
    830 		y = y - lines;
    831 		if (y < lines) {
    832 			y *= -1;
    833 			wmove(windows[Win_main].window, 0, 0);
    834 			ui_wprintc(&windows[Win_main], y, "%s\n", hp->format);
    835 			break;
    836 		}
    837 		wmove(windows[Win_main].window, y, 0);
    838 		ui_wprintc(&windows[Win_main], 0, "%s\n", hp->format);
    839 	}
    840 
    841 	if (selected.channel && selected.channel->topic) {
    842 		wmove(windows[Win_main].window, 0, 0);
    843 		ui_wprintc(&windows[Win_main], 0, "%s\n", format(&windows[Win_main], config_gets("format.ui.topic"), NULL));
    844 	}
    845 }
    846 
    847 void
    848 ui_select(struct Server *server, struct Channel *channel) {
    849 	struct History *hp, *ind;
    850 	int i, total;
    851 
    852 	if (selected.history)
    853 		hist_purgeopt(selected.history, HIST_TMP);
    854 
    855 	selected.channel  = channel;
    856 	selected.server   = server;
    857 	selected.history  = channel ? channel->history : server ? server->history : main_buf;
    858 	selected.name     = channel ? channel->name    : server ? server->name    : "hirc";
    859 	selected.hasnicks = channel ? !channel->query && !channel->old : 0;
    860 	selected.showign  = 0;
    861 
    862 	if (selected.history->unread || selected.history->ignored) {
    863 		for (i = 0, hp = selected.history->history; hp && hp->next; hp = hp->next, i++);
    864 		if (i == (HIST_MAX-1)) {
    865 			pfree(&hp->next);
    866 			hp->next = NULL;
    867 		}
    868 
    869 		total = selected.history->unread + selected.history->ignored;
    870 
    871 		for (i = 0, hp = selected.history->history; hp && hp->next && i < total; hp = hp->next)
    872 			if (hp->options & HIST_SHOW)
    873 				i++;
    874 		if (hp) {
    875 			ind = hist_format(NULL, Activity_none, HIST_SHOW|HIST_TMP, "SELF_UNREAD %d %d :unread, ignored",
    876 					selected.history->unread, selected.history->ignored);
    877 			ind->origin = selected.history;
    878 			ind->next = hp;
    879 			ind->prev = hp->prev;
    880 			if (hp->prev)
    881 				hp->prev->next = ind;
    882 			hp->prev = ind;
    883 		}
    884 	}
    885 
    886 
    887 	selected.history->activity = Activity_none;
    888 	selected.history->unread = selected.history->ignored = 0;
    889 
    890 	if (!selected.hasnicks || config_getl("nicklist.hidden"))
    891 		windows[Win_nicklist].location = Location_hidden;
    892 	else
    893 		windows[Win_nicklist].location = config_getl("nicklist.location");
    894 	windows[Win_main].scroll = -1;
    895 	ui_redraw();
    896 }
    897 
    898 char *
    899 ui_rectrl(char *str) {
    900 	static char ret[8192];
    901 	static char *rp = NULL;
    902 	int caret, rc;
    903 	char c;
    904 
    905 	if (rp) {
    906 		pfree(&rp);
    907 		rp = NULL;
    908 	}
    909 
    910 	for (rc = 0, caret = 0; str && *str; str++) {
    911 		if (caret) {
    912 			c = toupper(*str) - 64;
    913 			if (c <= 31 && c >= 0) {
    914 				ret[rc++] = c;
    915 			} else {
    916 				ret[rc++] = '^';
    917 				ret[rc++] = *str;
    918 			}
    919 			caret = 0;
    920 		} else if (*str == '^') {
    921 			caret = 1;
    922 		} else {
    923 			ret[rc++] = *str;
    924 		}
    925 	}
    926 
    927 	if (caret)
    928 		ret[rc++] = '^';
    929 	ret[rc] = '\0';
    930 	rp = estrdup(ret);
    931 
    932 	return rp;
    933 }
    934 
    935 char *
    936 ui_unctrl(char *str) {
    937 	static char ret[8192];
    938 	static char *rp = NULL;;
    939 	int rc;
    940 
    941 	if (rp) {
    942 		pfree(&rp);
    943 		rp = NULL;
    944 	}
    945 
    946 	for (rc = 0; str && *str; str++) {
    947 		if (*str <= 31 && *str >= 0) {
    948 			ret[rc++] = '^';
    949 			ret[rc++] = (*str) + 64;
    950 		} else {
    951 			ret[rc++] = *str;
    952 		}
    953 	}
    954 
    955 	ret[rc] = '\0';
    956 	rp = estrdup(ret);
    957 
    958 	return rp;
    959 }
    960 
    961 int
    962 ui_bind(char *binding, char *cmd) {
    963 	struct Keybind *p;
    964 	char *tmp, *b;
    965 
    966 	assert_warn(binding && cmd, -1);
    967 	b = ui_rectrl(binding);
    968 
    969 	for (p = keybinds; p; p = p->next)
    970 		if (strcmp(p->binding, b) == 0)
    971 			return -1;
    972 
    973 	p = emalloc(sizeof(struct Keybind));
    974 	p->binding = estrdup(b);
    975 	p->wbinding = stowc(p->binding);
    976 	if (*cmd != '/') {
    977 		tmp = smprintf(strlen(cmd) + 2, "/%s", cmd);
    978 		p->cmd = tmp;
    979 	} else {
    980 		p->cmd = estrdup(cmd);
    981 	}
    982 	p->prev = NULL;
    983 	p->next = keybinds;
    984 	if (keybinds)
    985 		keybinds->prev = p;
    986 	keybinds = p;
    987 
    988 	return 0;
    989 }
    990 
    991 int
    992 ui_unbind(char *binding) {
    993 	struct Keybind *p;
    994 	char *b;
    995 
    996 	assert_warn(binding, -1);
    997 	b = ui_rectrl(binding);
    998 
    999 	for (p=keybinds; p; p = p->next) {
   1000 		if (strcmp(p->binding, b) == 0) {
   1001 			if (p->prev)
   1002 				p->prev->next = p->next;
   1003 			else
   1004 				keybinds = p->next;
   1005 
   1006 			if (p->next)
   1007 				p->next->prev = p->prev;
   1008 
   1009 			pfree(&p->binding);
   1010 			pfree(&p->wbinding);
   1011 			pfree(&p->cmd);
   1012 			pfree(&p);
   1013 			return 0;
   1014 		}
   1015 	}
   1016 
   1017 	return -1;
   1018 }