xdebug

next generation of mdebug (work in progress)
git clone http://frotz.net/git/xdebug.git
Log | Files | Refs | README

termbox.c (15767B)


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