heartbeat

Simple server monitor system using encrypted messages over udp
git clone https://noulin.net/git/heartbeat.git
Log | Files | Refs | README

termbox.c (18963B)


      1 #include <assert.h>
      2 #include <stdlib.h>
      3 #include <string.h>
      4 #include <errno.h>
      5 #include <fcntl.h>
      6 #include <signal.h>
      7 #include <stdio.h>
      8 #include <stdarg.h>
      9 #include <stdbool.h>
     10 #include <sys/select.h>
     11 #include <sys/ioctl.h>
     12 #include <sys/time.h>
     13 #include <sys/stat.h>
     14 #include <termios.h>
     15 #include <unistd.h>
     16 #include <wchar.h>
     17 
     18 #include "termbox.h"
     19 #include "utf8.h"
     20 
     21 #include "bytebuffer.h"
     22 #include "term.h"
     23 #include "input.h"
     24 
     25 uint32_t tb_palette[16] = {
     26 	0x000000, // BLACK
     27 	0xAA2222, // RED
     28 	0x22AA22, // GREEN
     29 	0xAA5522, // YELLOW
     30 	0x3333AA, // BLUE
     31 	0xAA22AA, // MAGENTA
     32 	0x22AAAA, // CYAN
     33 	0xDDDDDD, // WHITE
     34 	0x999999, // LIGHT BLACK
     35 	0xFF5555, // LIGHT RED
     36 	0x55FF55, // LIGHT GREEN
     37 	0xFFFF55, // LIGHT YELLOW
     38 	0x5555FF, // LIGHT BLUE
     39 	0xFF55FF, // LIGHT MAGENTA
     40 	0x55FFFF, // LIGHT CYAN
     41 	0xFFFFFF, // LIGHT WHITE
     42 };
     43 
     44 struct cellbuf {
     45 	int width;
     46 	int height;
     47 	struct tb_cell *cells;
     48 };
     49 
     50 #define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)]
     51 #define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1)
     52 #define LAST_COORD_INIT -1
     53 
     54 static struct termios orig_tios;
     55 
     56 static struct cellbuf back_buffer;
     57 static struct cellbuf front_buffer;
     58 static struct bytebuffer output_buffer;
     59 static struct bytebuffer input_buffer;
     60 
     61 static char print_buf[8192];
     62 
     63 static int termw = -1;
     64 static int termh = -1;
     65 
     66 static int inputmode = TB_INPUT_ESC;
     67 static int outputmode = TB_OUTPUT_NORMAL;
     68 
     69 static int inout;
     70 static int winch_fds[2];
     71 
     72 static int lastx = LAST_COORD_INIT;
     73 static int lasty = LAST_COORD_INIT;
     74 static int cursor_x = -1;
     75 static int cursor_y = -1;
     76 
     77 static uint32_t background = TB_DEFAULT;
     78 static uint32_t foreground = TB_DEFAULT;
     79 
     80 static void write_cursor(int x, int y);
     81 static void write_sgr(uint32_t fg, uint32_t bg);
     82 
     83 static void cellbuf_init(struct cellbuf *buf, int width, int height);
     84 static void cellbuf_resize(struct cellbuf *buf, int width, int height);
     85 static void cellbuf_clear(struct cellbuf *buf);
     86 static void cellbuf_free(struct cellbuf *buf);
     87 
     88 static void update_size(void);
     89 static void update_term_size(void);
     90 static void send_attr(uint32_t fg, uint32_t bg);
     91 static void send_char(int x, int y, uint32_t c);
     92 static void send_clear(void);
     93 static void sigwinch_handler(int xxx);
     94 static int wait_fill_event(struct tb_event *event, struct timeval *timeout, int sock);
     95 
     96 /* may happen in a different thread */
     97 static volatile int buffer_size_change_request;
     98 
     99 /* -------------------------------------------------------- */
    100 
    101 int tb_init_fd(int inout_)
    102 {
    103 	inout = inout_;
    104 	if (inout == -1) {
    105 		return TB_EFAILED_TO_OPEN_TTY;
    106 	}
    107 
    108 	if (init_term() < 0) {
    109 		close(inout);
    110 		return TB_EUNSUPPORTED_TERMINAL;
    111 	}
    112 
    113 	if (pipe(winch_fds) < 0) {
    114 		close(inout);
    115 		return TB_EPIPE_TRAP_ERROR;
    116 	}
    117 
    118 	struct sigaction sa = {0};
    119 	sa.sa_handler = sigwinch_handler;
    120 	sa.sa_flags = 0;
    121 	sigaction(SIGWINCH, &sa, 0);
    122 
    123 	tcgetattr(inout, &orig_tios);
    124 
    125 	struct termios tios;
    126 	memcpy(&tios, &orig_tios, sizeof(tios));
    127 
    128 	tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
    129                            | INLCR | IGNCR | ICRNL | IXON);
    130 	tios.c_oflag &= ~OPOST;
    131 	tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    132 	tios.c_cflag &= ~(CSIZE | PARENB);
    133 	tios.c_cflag |= CS8;
    134 	tios.c_cc[VMIN] = 0;
    135 	tios.c_cc[VTIME] = 0;
    136 	tcsetattr(inout, TCSAFLUSH, &tios);
    137 
    138 	bytebuffer_init(&input_buffer, 128);
    139 	bytebuffer_init(&output_buffer, 32 * 1024);
    140 
    141 	bytebuffer_puts(&output_buffer, funcs[T_ENTER_CA]);
    142 	bytebuffer_puts(&output_buffer, funcs[T_ENTER_KEYPAD]);
    143 	bytebuffer_puts(&output_buffer, funcs[T_HIDE_CURSOR]);
    144 	send_clear();
    145 
    146 	update_term_size();
    147 	cellbuf_init(&back_buffer, termw, termh);
    148 	cellbuf_init(&front_buffer, termw, termh);
    149 	cellbuf_clear(&back_buffer);
    150 	cellbuf_clear(&front_buffer);
    151 
    152 	return 0;
    153 }
    154 
    155 int tb_init_file(const char* name){
    156 	return tb_init_fd(open(name, O_RDWR));
    157 }
    158 
    159 int tb_init(void)
    160 {
    161 	return tb_init_file("/dev/tty");
    162 }
    163 
    164 void tb_shutdown(void)
    165 {
    166 	if (termw == -1) {
    167 		fputs("tb_shutdown() should not be called twice.", stderr);
    168 		abort();
    169 	}
    170 
    171 	bytebuffer_puts(&output_buffer, funcs[T_SHOW_CURSOR]);
    172 	bytebuffer_puts(&output_buffer, funcs[T_SGR0]);
    173 	bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]);
    174 	bytebuffer_puts(&output_buffer, funcs[T_EXIT_CA]);
    175 	bytebuffer_puts(&output_buffer, funcs[T_EXIT_KEYPAD]);
    176 	bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]);
    177 	bytebuffer_flush(&output_buffer, inout);
    178 	tcsetattr(inout, TCSAFLUSH, &orig_tios);
    179 
    180 	shutdown_term();
    181 	close(inout);
    182 	close(winch_fds[0]);
    183 	close(winch_fds[1]);
    184 
    185 	cellbuf_free(&back_buffer);
    186 	cellbuf_free(&front_buffer);
    187 	bytebuffer_free(&output_buffer);
    188 	bytebuffer_free(&input_buffer);
    189 	termw = termh = -1;
    190 }
    191 
    192 void tb_present(void)
    193 {
    194 	int x,y,w,i;
    195 	struct tb_cell *back, *front;
    196 
    197 	/* invalidate cursor position */
    198 	lastx = LAST_COORD_INIT;
    199 	lasty = LAST_COORD_INIT;
    200 
    201 	if (buffer_size_change_request) {
    202 		update_size();
    203 		buffer_size_change_request = 0;
    204 	}
    205 
    206 	for (y = 0; y < front_buffer.height; ++y) {
    207 		for (x = 0; x < front_buffer.width; ) {
    208 			back = &CELL(&back_buffer, x, y);
    209 			front = &CELL(&front_buffer, x, y);
    210 			w = wcwidth(back->ch);
    211 			if (w < 1) w = 1;
    212 			if (memcmp(back, front, sizeof(struct tb_cell)) == 0) {
    213 				x += w;
    214 				continue;
    215 			}
    216 			memcpy(front, back, sizeof(struct tb_cell));
    217 			send_attr(back->fg, back->bg);
    218 			if (w > 1 && x >= front_buffer.width - (w - 1)) {
    219 				// Not enough room for wide ch, so send spaces
    220 				for (i = x; i < front_buffer.width; ++i) {
    221 					send_char(i, y, ' ');
    222 				}
    223 			} else {
    224 				send_char(x, y, back->ch);
    225 				for (i = 1; i < w; ++i) {
    226 					front = &CELL(&front_buffer, x + i, y);
    227 					front->ch = 0;
    228 					front->fg = back->fg;
    229 					front->bg = back->bg;
    230 				}
    231 			}
    232 			x += w;
    233 		}
    234 	}
    235 	if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
    236 		write_cursor(cursor_x, cursor_y);
    237 	bytebuffer_flush(&output_buffer, inout);
    238 }
    239 
    240 void tb_set_cursor(int cx, int cy)
    241 {
    242 	if (IS_CURSOR_HIDDEN(cursor_x, cursor_y) && !IS_CURSOR_HIDDEN(cx, cy))
    243 		bytebuffer_puts(&output_buffer, funcs[T_SHOW_CURSOR]);
    244 
    245 	if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y) && IS_CURSOR_HIDDEN(cx, cy))
    246 		bytebuffer_puts(&output_buffer, funcs[T_HIDE_CURSOR]);
    247 
    248 	cursor_x = cx;
    249 	cursor_y = cy;
    250 	if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
    251 		write_cursor(cursor_x, cursor_y);
    252 }
    253 
    254 void tb_put_cell(int x, int y, const struct tb_cell *cell)
    255 {
    256 	if ((unsigned)x >= (unsigned)back_buffer.width)
    257 		return;
    258 	if ((unsigned)y >= (unsigned)back_buffer.height)
    259 		return;
    260 	CELL(&back_buffer, x, y) = *cell;
    261 }
    262 
    263 void tb_change_cell(int x, int y, uint32_t ch, uint32_t fg, uint32_t bg)
    264 {
    265 	struct tb_cell c = {ch, fg, bg};
    266 	tb_put_cell(x, y, &c);
    267 }
    268 
    269 int tb_string_with_limit(int x, int y, uint32_t fg, uint32_t bg, int limit, const char *str) {
    270 	uint32_t uni;
    271 	int l = 0;
    272 	int userX = x;
    273 
    274 	while (*str && l < limit) {
    275 		if (*str == '\n') {
    276 			// new line
    277 			l = 0;
    278 			x = userX;
    279 			y++;
    280 			str++;
    281 		}
    282 		else {
    283 			str += tb_utf8_char_to_unicode(&uni, str);
    284 			tb_change_cell(x, y, uni, fg, bg);
    285 			x++;
    286 			l++;
    287 		}
    288 	}
    289 
    290 	return l;
    291 }
    292 
    293 int tb_string(int x, int y, uint32_t fg, uint32_t bg, const char *str) {
    294 	return tb_string_with_limit(x, y, fg, bg, sizeof(print_buf), str);
    295 }
    296 
    297 int tb_stringf(int x, int y, uint32_t fg, uint32_t bg, const char *fmt, ...) {
    298 	va_list vl;
    299 	va_start(vl, fmt);
    300 	vsnprintf(print_buf, sizeof(print_buf), fmt, vl);
    301 	va_end(vl);
    302 	return tb_string_with_limit(x, y, fg, bg, sizeof(print_buf), print_buf);
    303 }
    304 
    305 int tb_stringf_with_limit(int x, int y, uint32_t fg, uint32_t bg, int limit, const char *fmt, ...) {
    306 	va_list vl;
    307 	va_start(vl, fmt);
    308 	// the string is utf8 encoded so character count is unknown and different from byte count
    309 	vsnprintf(print_buf, sizeof(print_buf), fmt, vl);
    310 	va_end(vl);
    311 	return tb_string_with_limit(x, y, fg, bg, limit, print_buf);
    312 }
    313 
    314 void tb_blit(int x, int y, int w, int h, const struct tb_cell *cells)
    315 {
    316 	if (x + w < 0 || x >= back_buffer.width)
    317 		return;
    318 	if (y + h < 0 || y >= back_buffer.height)
    319 		return;
    320 	int xo = 0, yo = 0, ww = w, hh = h;
    321 	if (x < 0) {
    322 		xo = -x;
    323 		ww -= xo;
    324 		x = 0;
    325 	}
    326 	if (y < 0) {
    327 		yo = -y;
    328 		hh -= yo;
    329 		y = 0;
    330 	}
    331 	if (ww > back_buffer.width - x)
    332 		ww = back_buffer.width - x;
    333 	if (hh > back_buffer.height - y)
    334 		hh = back_buffer.height - y;
    335 
    336 	int sy;
    337 	struct tb_cell *dst = &CELL(&back_buffer, x, y);
    338 	const struct tb_cell *src = cells + yo * w + xo;
    339 	size_t size = sizeof(struct tb_cell) * ww;
    340 
    341 	for (sy = 0; sy < hh; ++sy) {
    342 		memcpy(dst, src, size);
    343 		dst += back_buffer.width;
    344 		src += w;
    345 	}
    346 }
    347 
    348 struct tb_cell *tb_cell_buffer(void)
    349 {
    350 	return back_buffer.cells;
    351 }
    352 
    353 int tb_poll_event(struct tb_event *event, int sock)
    354 {
    355 	return wait_fill_event(event, NULL, sock);
    356 }
    357 
    358 int tb_peek_event(struct tb_event *event, int timeout)
    359 {
    360 	struct timeval tv;
    361 	tv.tv_sec = timeout / 1000;
    362 	tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
    363 	return wait_fill_event(event, &tv, 0/*sock*/);
    364 }
    365 
    366 int tb_width(void)
    367 {
    368 	return termw;
    369 }
    370 
    371 int tb_height(void)
    372 {
    373 	return termh;
    374 }
    375 
    376 void tb_clear(void)
    377 {
    378 	if (buffer_size_change_request) {
    379 		update_size();
    380 		buffer_size_change_request = 0;
    381 	}
    382 	cellbuf_clear(&back_buffer);
    383 }
    384 
    385 int tb_select_input_mode(int mode)
    386 {
    387 	if (mode) {
    388 		if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0)
    389 			mode |= TB_INPUT_ESC;
    390 
    391 		/* technically termbox can handle that, but let's be nice and show here
    392 		   what mode is actually used */
    393 		if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT))
    394 			mode &= ~TB_INPUT_ALT;
    395 
    396 		inputmode = mode;
    397 		if (mode&TB_INPUT_MOUSE) {
    398 			bytebuffer_puts(&output_buffer, funcs[T_ENTER_MOUSE]);
    399 			bytebuffer_flush(&output_buffer, inout);
    400 		} else {
    401 			bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]);
    402 			bytebuffer_flush(&output_buffer, inout);
    403 		}
    404 	}
    405 	return inputmode;
    406 }
    407 
    408 int tb_select_output_mode(int mode)
    409 {
    410 	if (mode)
    411 		outputmode = mode;
    412 	return outputmode;
    413 }
    414 
    415 void tb_set_clear_attributes(uint32_t fg, uint32_t bg)
    416 {
    417 	foreground = fg;
    418 	background = bg;
    419 }
    420 
    421 /* -------------------------------------------------------- */
    422 
    423 static int convertnum(uint32_t num, char* buf) {
    424 	int i, l = 0;
    425 	int ch;
    426 	do {
    427 		buf[l++] = '0' + (num % 10);
    428 		num /= 10;
    429 	} while (num);
    430 	for(i = 0; i < l / 2; i++) {
    431 		ch = buf[i];
    432 		buf[i] = buf[l - 1 - i];
    433 		buf[l - 1 - i] = ch;
    434 	}
    435 	return l;
    436 }
    437 
    438 #define WRITE_LITERAL(X) bytebuffer_append(&output_buffer, (X), sizeof(X)-1)
    439 #define WRITE_INT(X) bytebuffer_append(&output_buffer, buf, convertnum((X), buf))
    440 
    441 static void write_cursor(int x, int y) {
    442 	char buf[32];
    443 	WRITE_LITERAL("\033[");
    444 	WRITE_INT(y+1);
    445 	WRITE_LITERAL(";");
    446 	WRITE_INT(x+1);
    447 	WRITE_LITERAL("H");
    448 }
    449 
    450 static void write_sgr(uint32_t fg, uint32_t bg) {
    451 	char buf[32];
    452 
    453 	if (fg & TB_DEFAULT && bg & TB_DEFAULT)
    454 		return;
    455 
    456 	switch (outputmode) {
    457 	case TB_OUTPUT_256:
    458 	case TB_OUTPUT_216:
    459 	case TB_OUTPUT_GRAYSCALE:
    460 		WRITE_LITERAL("\033[");
    461 		if (!(fg & TB_DEFAULT)) {
    462 			WRITE_LITERAL("38;5;");
    463 			WRITE_INT(fg);
    464 			if (!(bg & TB_DEFAULT)) {
    465 				WRITE_LITERAL(";");
    466 			}
    467 		}
    468 		if (!(bg & TB_DEFAULT)) {
    469 			WRITE_LITERAL("48;5;");
    470 			WRITE_INT(bg);
    471 		}
    472 		WRITE_LITERAL("m");
    473 		break;
    474 	case TB_OUTPUT_TRUECOLOR:
    475 		WRITE_LITERAL("\033[");
    476 		if (!(fg & TB_DEFAULT)) {
    477 			if (fg & TB_PALETTE) {
    478 				fg = tb_palette[fg & 0xF];
    479 			}
    480 			WRITE_LITERAL("38;2;");
    481 			WRITE_INT(fg >> 16 & 0xFF); // fg R
    482 			WRITE_LITERAL(";");
    483 			WRITE_INT(fg >> 8 & 0xFF);  // fg G
    484 			WRITE_LITERAL(";");
    485 			WRITE_INT(fg & 0xFF);       // fg B
    486 			if (!(bg & TB_DEFAULT)) {
    487 				WRITE_LITERAL(";");
    488 			}
    489 		}
    490 		if (!(bg & TB_DEFAULT)) {
    491 			if (bg & TB_PALETTE) {
    492 				bg = tb_palette[bg & 0xF];
    493 			}
    494 			WRITE_LITERAL("48;2;");
    495 			WRITE_INT(bg >> 16 & 0xFF); // bg R
    496 			WRITE_LITERAL(";");
    497 			WRITE_INT(bg >> 8 & 0xFF);  // bg G
    498 			WRITE_LITERAL(";");
    499 			WRITE_INT(bg & 0xFF);       // bg B
    500 		}
    501 		WRITE_LITERAL("m");
    502 		break;
    503 	case TB_OUTPUT_NORMAL:
    504 	default:
    505 		// 16 color ISO
    506 		// num   fg         bg
    507 		// 0-7   3(N)m      4(N)m
    508 		// 8-15  1;3(N-8)m  1;4(N-8)m
    509 
    510 		// in bold
    511 		// 0-7   9(N)m      10(N)m
    512 		// 8-15  1;9(N-8)m  1;9(N-8)m
    513 		WRITE_LITERAL("\033[");
    514 		if (!(fg & TB_DEFAULT)) {
    515 			if (fg > 7) { // 8 light colors
    516 				WRITE_LITERAL("1;3");
    517 				WRITE_INT(fg - 8);
    518 			}
    519 			else {
    520 				WRITE_LITERAL("3");
    521 				WRITE_INT(fg);
    522 			}
    523 			if (!(bg & TB_DEFAULT)) {
    524 				WRITE_LITERAL(";");
    525 			}
    526 		}
    527 		if (!(bg & TB_DEFAULT)) {
    528 			if (bg > 7 ) { // 8 light colors
    529 				WRITE_LITERAL("10");
    530 				WRITE_INT(bg - 8);
    531 			}
    532 			else {
    533 				WRITE_LITERAL("4");
    534 				WRITE_INT(bg);
    535 			}
    536 		}
    537 		WRITE_LITERAL("m");
    538 		break;
    539 	}
    540 }
    541 
    542 static void cellbuf_init(struct cellbuf *buf, int width, int height)
    543 {
    544 	buf->cells = (struct tb_cell*)malloc(sizeof(struct tb_cell) * width * height);
    545 	assert(buf->cells);
    546 	buf->width = width;
    547 	buf->height = height;
    548 }
    549 
    550 static void cellbuf_resize(struct cellbuf *buf, int width, int height)
    551 {
    552 	if (buf->width == width && buf->height == height)
    553 		return;
    554 
    555 	int oldw = buf->width;
    556 	int oldh = buf->height;
    557 	struct tb_cell *oldcells = buf->cells;
    558 
    559 	cellbuf_init(buf, width, height);
    560 	cellbuf_clear(buf);
    561 
    562 	int minw = (width < oldw) ? width : oldw;
    563 	int minh = (height < oldh) ? height : oldh;
    564 	int i;
    565 
    566 	for (i = 0; i < minh; ++i) {
    567 		struct tb_cell *csrc = oldcells + (i * oldw);
    568 		struct tb_cell *cdst = buf->cells + (i * width);
    569 		memcpy(cdst, csrc, sizeof(struct tb_cell) * minw);
    570 	}
    571 
    572 	free(oldcells);
    573 }
    574 
    575 static void cellbuf_clear(struct cellbuf *buf)
    576 {
    577 	int i;
    578 	int ncells = buf->width * buf->height;
    579 
    580 	for (i = 0; i < ncells; ++i) {
    581 		buf->cells[i].ch = ' ';
    582 		buf->cells[i].fg = foreground;
    583 		buf->cells[i].bg = background;
    584 	}
    585 }
    586 
    587 static void cellbuf_free(struct cellbuf *buf)
    588 {
    589 	free(buf->cells);
    590 }
    591 
    592 static void get_term_size(int *w, int *h)
    593 {
    594 	struct winsize sz = {0};
    595 
    596 	ioctl(inout, TIOCGWINSZ, &sz);
    597 
    598 	if (w) *w = sz.ws_col;
    599 	if (h) *h = sz.ws_row;
    600 }
    601 
    602 static void update_term_size(void)
    603 {
    604 	struct winsize sz = {0};
    605 
    606 	ioctl(inout, TIOCGWINSZ, &sz);
    607 
    608 	termw = sz.ws_col;
    609 	termh = sz.ws_row;
    610 }
    611 
    612 static void send_attr(uint32_t fg, uint32_t bg)
    613 {
    614 #define LAST_ATTR_INIT 0xFFFFFFFF
    615 	static uint32_t lastfg = LAST_ATTR_INIT, lastbg = LAST_ATTR_INIT;
    616 	if (fg != lastfg || bg != lastbg) {
    617 		bytebuffer_puts(&output_buffer, funcs[T_SGR0]);
    618 
    619 		uint32_t fgcol;
    620 		uint32_t bgcol;
    621 
    622 		switch (outputmode) {
    623 		case TB_OUTPUT_256:
    624 			fgcol = fg & (0xFF | TB_DEFAULT);
    625 			bgcol = bg & (0xFF | TB_DEFAULT);
    626 			break;
    627 
    628 		case TB_OUTPUT_216:
    629 			fgcol = fg & 0xFF; if (fgcol > 215) fgcol = 7;
    630 			bgcol = bg & 0xFF; if (bgcol > 215) bgcol = 0;
    631 			fgcol += 0x10 | (fg & TB_DEFAULT);
    632 			bgcol += 0x10 | (bg & TB_DEFAULT);
    633 			break;
    634 
    635 		case TB_OUTPUT_GRAYSCALE:
    636 			fgcol = fg & 0xFF; if (fgcol > 23) fgcol = 23;
    637 			bgcol = bg & 0xFF; if (bgcol > 23) bgcol = 0;
    638 			fgcol += 0xe8 | (fg & TB_DEFAULT);
    639 			bgcol += 0xe8 | (bg & TB_DEFAULT);
    640 			break;
    641 
    642 		case TB_OUTPUT_TRUECOLOR:
    643 			fgcol = fg;
    644 			bgcol = bg;
    645 			break;
    646 		case TB_OUTPUT_NORMAL:
    647 		default:
    648 			fgcol = fg & (0x0F | TB_DEFAULT);
    649 			bgcol = bg & (0x0F | TB_DEFAULT);
    650 		}
    651 
    652 		if (fg & TB_BOLD)
    653 			bytebuffer_puts(&output_buffer, funcs[T_BOLD]);
    654 		if (bg & TB_BLINK)
    655 			bytebuffer_puts(&output_buffer, funcs[T_BLINK]);
    656 		if (fg & TB_UNDERLINE)
    657 			bytebuffer_puts(&output_buffer, funcs[T_UNDERLINE]);
    658 		if (fg & TB_REVERSE)
    659 			bytebuffer_puts(&output_buffer, funcs[T_REVERSE]);
    660 		if (fg & TB_FAINT)
    661 			bytebuffer_puts(&output_buffer, "\x1B[2m");
    662 		if (fg & TB_ITALIC)
    663 			bytebuffer_puts(&output_buffer, "\x1B[3m");
    664 		if (bg & TB_HIDDEN)
    665 			bytebuffer_puts(&output_buffer, "\x1B[8m");
    666 		if (bg & TB_CROSSED)
    667 			bytebuffer_puts(&output_buffer, "\x1B[9m");
    668 
    669 		write_sgr(fgcol, bgcol);
    670 
    671 		lastfg = fg;
    672 		lastbg = bg;
    673 	}
    674 }
    675 
    676 static void send_char(int x, int y, uint32_t c)
    677 {
    678 	char buf[7];
    679 	int bw = tb_utf8_unicode_to_char(buf, c);
    680 	if (x-1 != lastx || y != lasty)
    681 		write_cursor(x, y);
    682 	lastx = x; lasty = y;
    683 	if(!c) buf[0] = ' '; // replace 0 with whitespace
    684 	bytebuffer_append(&output_buffer, buf, bw);
    685 }
    686 
    687 static void send_clear(void)
    688 {
    689 	send_attr(foreground, background);
    690 	bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]);
    691 	if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
    692 		write_cursor(cursor_x, cursor_y);
    693 	bytebuffer_flush(&output_buffer, inout);
    694 
    695 	/* we need to invalidate cursor position too and these two vars are
    696 	 * used only for simple cursor positioning optimization, cursor
    697 	 * actually may be in the correct place, but we simply discard
    698 	 * optimization once and it gives us simple solution for the case when
    699 	 * cursor moved */
    700 	lastx = LAST_COORD_INIT;
    701 	lasty = LAST_COORD_INIT;
    702 }
    703 
    704 static void sigwinch_handler(int xxx)
    705 {
    706 	(void) xxx;
    707 	const int zzz = 1;
    708 	write(winch_fds[1], &zzz, sizeof(int));
    709 }
    710 
    711 static void update_size(void)
    712 {
    713 	update_term_size();
    714 	cellbuf_resize(&back_buffer, termw, termh);
    715 	cellbuf_resize(&front_buffer, termw, termh);
    716 	cellbuf_clear(&front_buffer);
    717 	send_clear();
    718 }
    719 
    720 static int read_up_to(int n) {
    721 	assert(n > 0);
    722 	const int prevlen = input_buffer.len;
    723 	bytebuffer_resize(&input_buffer, prevlen + n);
    724 
    725 	int read_n = 0;
    726 	while (read_n <= n) {
    727 		ssize_t r = 0;
    728 		if (read_n < n) {
    729 			r = read(inout, input_buffer.buf + prevlen + read_n, n - read_n);
    730 		}
    731 #ifdef __CYGWIN__
    732 		// While linux man for tty says when VMIN == 0 && VTIME == 0, read
    733 		// should return 0 when there is nothing to read, cygwin's read returns
    734 		// -1. Not sure why and if it's correct to ignore it, but let's pretend
    735 		// it's zero.
    736 		if (r < 0) r = 0;
    737 #endif
    738 		if (r < 0) {
    739 			// EAGAIN / EWOULDBLOCK shouldn't occur here
    740 			assert(errno != EAGAIN && errno != EWOULDBLOCK);
    741 			return -1;
    742 		} else if (r > 0) {
    743 			read_n += r;
    744 		} else {
    745 			bytebuffer_resize(&input_buffer, prevlen + read_n);
    746 			return read_n;
    747 		}
    748 	}
    749 	assert(!"unreachable");
    750 	return 0;
    751 }
    752 
    753 static int wait_fill_event(struct tb_event *event, struct timeval *timeout, int sock)
    754 {
    755 	// ;-)
    756 #define ENOUGH_DATA_FOR_PARSING 64
    757 	fd_set events = {0};
    758 
    759 	// try to extract event from input buffer, return on success
    760 	event->type = TB_EVENT_KEY;
    761 	if (extract_event(event, &input_buffer, inputmode))
    762 		return event->type;
    763 
    764 	// it looks like input buffer is incomplete, let's try the short path,
    765 	// but first make sure there is enough space
    766 	int n = read_up_to(ENOUGH_DATA_FOR_PARSING);
    767 	if (n < 0)
    768 		return -1;
    769 	if (n > 0 && extract_event(event, &input_buffer, inputmode))
    770 		return event->type;
    771 
    772 	// n == 0, or not enough data, let's go to select
    773 	while (1) {
    774 		FD_ZERO(&events);
    775 		FD_SET(inout, &events);
    776 		FD_SET(winch_fds[0], &events);
    777 		if (sock) FD_SET(sock, &events);
    778 		int maxfd = (winch_fds[0] > inout) ? winch_fds[0] : inout;
    779 		maxfd     = maxfd > sock ? maxfd : sock;
    780 		int result = select(maxfd+1, &events, NULL, NULL, timeout);
    781 		if (!result)
    782 			return 0;
    783 
    784 		if (FD_ISSET(inout, &events)) {
    785 			event->type = TB_EVENT_KEY;
    786 			n = read_up_to(ENOUGH_DATA_FOR_PARSING);
    787 			if (n < 0)
    788 				return -1;
    789 
    790 			if (n == 0)
    791 				continue;
    792 
    793 			if (extract_event(event, &input_buffer, inputmode))
    794 				return event->type;
    795 		}
    796 		if (FD_ISSET(winch_fds[0], &events)) {
    797 			event->type = TB_EVENT_RESIZE;
    798 			int zzz = 0;
    799 			read(winch_fds[0], &zzz, sizeof(int));
    800 			buffer_size_change_request = 1;
    801 			get_term_size(&event->w, &event->h);
    802 			return TB_EVENT_RESIZE;
    803 		}
    804 		if (sock && FD_ISSET(sock, &events)) {
    805 			event->type = TB_EVENT_SOCKET;
    806 			return TB_EVENT_SOCKET;
    807 		}
    808 	}
    809 }