memzap

replay memory writes
git clone git@git.2f30.org/memzap.git
Log | Files | Refs | README | LICENSE

termbox.c (12793B)


      1 #include <assert.h>
      2 #include <stdio.h>
      3 #include <stdlib.h>
      4 #include <string.h>
      5 
      6 #include <fcntl.h>
      7 #include <signal.h>
      8 #include <sys/ioctl.h>
      9 #include <sys/time.h>
     10 #include <termios.h>
     11 #include <unistd.h>
     12 
     13 #include "term.h"
     14 #include "termbox.h"
     15 #include "memstream.h"
     16 
     17 struct cellbuf {
     18 	unsigned int width;
     19 	unsigned int height;
     20 	struct tb_cell *cells;
     21 };
     22 
     23 #define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)]
     24 #define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1)
     25 
     26 #define LAST_COORD_INIT 0xFFFFFFFE
     27 
     28 static struct termios orig_tios;
     29 
     30 static struct cellbuf back_buffer;
     31 static struct cellbuf front_buffer;
     32 static unsigned char write_buffer_data[32 * 1024];
     33 static struct memstream write_buffer;
     34 
     35 static unsigned int termw;
     36 static unsigned int termh;
     37 
     38 static int inputmode = TB_INPUT_ESC;
     39 
     40 static struct ringbuffer inbuf;
     41 
     42 static int out;
     43 static FILE *in;
     44 
     45 static int out_fileno;
     46 static int in_fileno;
     47 
     48 static int winch_fds[2];
     49 
     50 static unsigned int lastx = LAST_COORD_INIT;
     51 static unsigned int lasty = LAST_COORD_INIT;
     52 static int cursor_x = -1;
     53 static int cursor_y = -1;
     54 
     55 static uint16_t background = TB_DEFAULT;
     56 static uint16_t foreground = TB_DEFAULT;
     57 
     58 static void write_cursor(unsigned x, unsigned y);
     59 static void write_sgr(uint32_t fg, uint32_t bg);
     60 
     61 static void cellbuf_init(struct cellbuf *buf, unsigned int width, unsigned int height);
     62 static void cellbuf_resize(struct cellbuf *buf, unsigned int width, unsigned int height);
     63 static void cellbuf_clear(struct cellbuf *buf);
     64 static void cellbuf_free(struct cellbuf *buf);
     65 
     66 static void update_size(void);
     67 static void update_term_size(void);
     68 static void send_attr(uint16_t fg, uint16_t bg);
     69 static void send_char(unsigned int x, unsigned int y, uint32_t c);
     70 static void send_clear(void);
     71 static void sigwinch_handler(int xxx);
     72 static int wait_fill_event(struct tb_event *event, struct timeval *timeout);
     73 
     74 /* may happen in a different thread */
     75 static volatile int buffer_size_change_request;
     76 
     77 /* -------------------------------------------------------- */
     78 
     79 int tb_init(void)
     80 {
     81 	out = open("/dev/tty", O_WRONLY);
     82 	in = fopen("/dev/tty", "r");
     83 
     84 	if (out == -1 || !in) {
     85 		if(out != -1)
     86 			close(out);
     87 
     88 		if(in)
     89 			fclose(in);
     90 
     91 		return TB_EFAILED_TO_OPEN_TTY;
     92 	}
     93 
     94 	out_fileno = out;
     95 	in_fileno = fileno(in);
     96 
     97 	if (init_term() < 0) {
     98 		close(out);
     99 		fclose(in);
    100 
    101 		return TB_EUNSUPPORTED_TERMINAL;
    102 	}
    103 
    104 	if (pipe(winch_fds) < 0) {
    105 		close(out);
    106 		fclose(in);
    107 
    108 		return TB_EPIPE_TRAP_ERROR;
    109 	}
    110 
    111 	struct sigaction sa;
    112 	sa.sa_handler = sigwinch_handler;
    113 	sa.sa_flags = 0;
    114 	sigaction(SIGWINCH, &sa, 0);
    115 
    116 	tcgetattr(out_fileno, &orig_tios);
    117 
    118 	struct termios tios;
    119 	memcpy(&tios, &orig_tios, sizeof(tios));
    120 
    121 	tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
    122                            | INLCR | IGNCR | ICRNL | IXON);
    123 	tios.c_oflag &= ~OPOST;
    124 	tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    125 	tios.c_cflag &= ~(CSIZE | PARENB);
    126 	tios.c_cflag |= CS8;
    127 	tios.c_cc[VMIN] = 0;
    128 	tios.c_cc[VTIME] = 0;
    129 	tcsetattr(out_fileno, TCSAFLUSH, &tios);
    130 	
    131 	memstream_init(&write_buffer, out_fileno, write_buffer_data, sizeof(write_buffer_data));
    132 
    133 	memstream_puts(&write_buffer, funcs[T_ENTER_CA]);
    134 	memstream_puts(&write_buffer, funcs[T_ENTER_KEYPAD]);
    135 	memstream_puts(&write_buffer, funcs[T_HIDE_CURSOR]);
    136 	send_clear();
    137 
    138 	update_term_size();
    139 	cellbuf_init(&back_buffer, termw, termh);
    140 	cellbuf_init(&front_buffer, termw, termh);
    141 	cellbuf_clear(&back_buffer);
    142 	cellbuf_clear(&front_buffer);
    143 	init_ringbuffer(&inbuf, 4096);
    144 
    145 	return 0;
    146 }
    147 
    148 void tb_shutdown(void)
    149 {
    150 	memstream_puts(&write_buffer, funcs[T_SHOW_CURSOR]);
    151 	memstream_puts(&write_buffer, funcs[T_SGR0]);
    152 	memstream_puts(&write_buffer, funcs[T_CLEAR_SCREEN]);
    153 	memstream_puts(&write_buffer, funcs[T_EXIT_CA]);
    154 	memstream_puts(&write_buffer, funcs[T_EXIT_KEYPAD]);
    155 	memstream_flush(&write_buffer);
    156 	tcsetattr(out_fileno, TCSAFLUSH, &orig_tios);
    157 
    158 	close(out);
    159 	fclose(in);
    160 	close(winch_fds[0]);
    161 	close(winch_fds[1]);
    162 
    163 	cellbuf_free(&back_buffer);
    164 	cellbuf_free(&front_buffer);
    165 	free_ringbuffer(&inbuf);
    166 }
    167 
    168 void tb_present(void)
    169 {
    170 	unsigned int x,y;
    171 	struct tb_cell *back, *front;
    172 
    173 	/* invalidate cursor position */
    174 	lastx = LAST_COORD_INIT;
    175 	lasty = LAST_COORD_INIT;
    176 
    177 	if (buffer_size_change_request) {
    178 		update_size();
    179 		buffer_size_change_request = 0;
    180 	}
    181 
    182 	for (y = 0; y < front_buffer.height; ++y) {
    183 		for (x = 0; x < front_buffer.width; ++x) {
    184 			back = &CELL(&back_buffer, x, y);
    185 			front = &CELL(&front_buffer, x, y);
    186 			if (memcmp(back, front, sizeof(struct tb_cell)) == 0)
    187 				continue;
    188 			send_attr(back->fg, back->bg);
    189 			send_char(x, y, back->ch);
    190 			memcpy(front, back, sizeof(struct tb_cell));
    191 		}
    192 	}
    193 	if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
    194 		write_cursor(cursor_x, cursor_y);
    195 	memstream_flush(&write_buffer);
    196 }
    197 
    198 void tb_set_cursor(int cx, int cy)
    199 {
    200 	if (IS_CURSOR_HIDDEN(cursor_x, cursor_y) && !IS_CURSOR_HIDDEN(cx, cy))
    201 		memstream_puts(&write_buffer, funcs[T_SHOW_CURSOR]);
    202 
    203 	if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y) && IS_CURSOR_HIDDEN(cx, cy))
    204 		memstream_puts(&write_buffer, funcs[T_HIDE_CURSOR]);
    205 
    206 	cursor_x = cx;
    207 	cursor_y = cy;
    208 	if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
    209 		write_cursor(cursor_x, cursor_y);
    210 }
    211 
    212 void tb_put_cell(unsigned int x, unsigned int y, const struct tb_cell *cell)
    213 {
    214 	if (x >= back_buffer.width || y >= back_buffer.height)
    215 		return;
    216 	CELL(&back_buffer, x, y) = *cell;
    217 }
    218 
    219 void tb_change_cell(unsigned int x, unsigned int y, uint32_t ch, uint16_t fg, uint16_t bg)
    220 {
    221 	struct tb_cell c = {ch, fg, bg};
    222 	tb_put_cell(x, y, &c);
    223 }
    224 
    225 void tb_blit(unsigned int x, unsigned int y, unsigned int w, unsigned int h, const struct tb_cell *cells)
    226 {
    227 	if (x+w > back_buffer.width || y+h > back_buffer.height)
    228 		return;
    229 
    230 	unsigned int sy;
    231 	struct tb_cell *dst = &CELL(&back_buffer, x, y);
    232 	size_t size = sizeof(struct tb_cell) * w;
    233 
    234 	for (sy = 0; sy < h; ++sy) {
    235 		memcpy(dst, cells, size);
    236 		dst += back_buffer.width;
    237 		cells += w;
    238 	}
    239 }
    240 
    241 int tb_poll_event(struct tb_event *event)
    242 {
    243 	return wait_fill_event(event, 0);
    244 }
    245 
    246 int tb_peek_event(struct tb_event *event, unsigned int timeout)
    247 {
    248 	struct timeval tv;
    249 	tv.tv_sec = timeout / 1000;
    250 	tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
    251 	return wait_fill_event(event, &tv);
    252 }
    253 
    254 unsigned int tb_width(void)
    255 {
    256 	return termw;
    257 }
    258 
    259 unsigned int tb_height(void)
    260 {
    261 	return termh;
    262 }
    263 
    264 void tb_clear(void)
    265 {
    266 	if (buffer_size_change_request) {
    267 		update_size();
    268 		buffer_size_change_request = 0;
    269 	}
    270 	cellbuf_clear(&back_buffer);
    271 }
    272 
    273 int tb_select_input_mode(int mode)
    274 {
    275 	if (mode)
    276 		inputmode = mode;
    277 	return inputmode;
    278 }
    279 
    280 void tb_set_clear_attributes(uint16_t fg, uint16_t bg)
    281 {
    282 	foreground = fg;
    283 	background = bg;
    284 }
    285 
    286 /* -------------------------------------------------------- */
    287 
    288 static unsigned convertnum(uint32_t num, char* buf) {
    289 	unsigned i, l = 0;
    290 	int ch;
    291 	do { 
    292 		buf[l++] = '0' + (num % 10);
    293 		num /= 10;
    294 	} while (num);
    295 	for(i = 0; i < l / 2; i++) {
    296 		ch = buf[i];
    297 		buf[i] = buf[l - 1 - i];
    298 		buf[l - 1 - i] = ch;
    299 	}
    300 	return l;
    301 }
    302 
    303 #define WRITE_LITERAL(X) memstream_write(&write_buffer, (X), sizeof(X) -1)
    304 #define WRITE_INT(X) memstream_write(&write_buffer, buf, convertnum((X), buf))
    305 
    306 static void write_cursor(unsigned x, unsigned y) {
    307 	char buf[32];
    308 	WRITE_LITERAL("\033[");
    309 	WRITE_INT(y+1);
    310 	WRITE_LITERAL(";");
    311 	WRITE_INT(x+1);
    312 	WRITE_LITERAL("H");
    313 }
    314 
    315 static void write_sgr(uint32_t fg, uint32_t bg) {
    316 	char buf[32];
    317 	if (fg != TB_DEFAULT) {
    318 		WRITE_LITERAL("\033[3");
    319 		WRITE_INT(fg);
    320 		if (bg != TB_DEFAULT) {
    321 			WRITE_LITERAL(";4");
    322 			WRITE_INT(bg);
    323 		} else {
    324 			WRITE_LITERAL(";49");
    325 		}
    326 		WRITE_LITERAL("m");
    327 	} else {
    328 		WRITE_LITERAL("\033[39");
    329 		if (bg != TB_DEFAULT) {
    330 			WRITE_LITERAL(";4");
    331 			WRITE_INT(bg);
    332 		} else {
    333 			WRITE_LITERAL(";49");
    334 		}
    335 		WRITE_LITERAL("m");
    336 	}
    337 }
    338 
    339 static void cellbuf_init(struct cellbuf *buf, unsigned int width, unsigned int height)
    340 {
    341 	buf->cells = (struct tb_cell*)malloc(sizeof(struct tb_cell) * width * height);
    342 	assert(buf->cells);
    343 	buf->width = width;
    344 	buf->height = height;
    345 }
    346 
    347 static void cellbuf_resize(struct cellbuf *buf, unsigned int width, unsigned int height)
    348 {
    349 	if (buf->width == width && buf->height == height)
    350 		return;
    351 
    352 	unsigned int oldw = buf->width;
    353 	unsigned int oldh = buf->height;
    354 	struct tb_cell *oldcells = buf->cells;
    355 
    356 	cellbuf_init(buf, width, height);
    357 	cellbuf_clear(buf);
    358 
    359 	unsigned int minw = (width < oldw) ? width : oldw;
    360 	unsigned int minh = (height < oldh) ? height : oldh;
    361 	unsigned int i;
    362 
    363 	for (i = 0; i < minh; ++i) {
    364 		struct tb_cell *csrc = oldcells + (i * oldw);
    365 		struct tb_cell *cdst = buf->cells + (i * width);
    366 		memcpy(cdst, csrc, sizeof(struct tb_cell) * minw);
    367 	}
    368 
    369 	free(oldcells);
    370 }
    371 
    372 static void cellbuf_clear(struct cellbuf *buf)
    373 {
    374 	unsigned int i;
    375 	unsigned int ncells = buf->width * buf->height;
    376 
    377 	for (i = 0; i < ncells; ++i) {
    378 		buf->cells[i].ch = ' ';
    379 		buf->cells[i].fg = foreground;
    380 		buf->cells[i].bg = background;
    381 	}
    382 }
    383 
    384 static void cellbuf_free(struct cellbuf *buf)
    385 {
    386 	free(buf->cells);
    387 }
    388 
    389 static void get_term_size(int *w, int *h)
    390 {
    391 	struct winsize sz;
    392 	memset(&sz, 0, sizeof(sz));
    393 
    394 	ioctl(out_fileno, TIOCGWINSZ, &sz);
    395 
    396 	if (w) *w = sz.ws_col;
    397 	if (h) *h = sz.ws_row;
    398 }
    399 
    400 static void update_term_size(void)
    401 {
    402 	struct winsize sz;
    403 	memset(&sz, 0, sizeof(sz));
    404 
    405 	ioctl(out_fileno, TIOCGWINSZ, &sz);
    406 
    407 	termw = sz.ws_col;
    408 	termh = sz.ws_row;
    409 }
    410 
    411 static void send_attr(uint16_t fg, uint16_t bg)
    412 {
    413 #define LAST_ATTR_INIT 0xFFFF
    414 	static uint16_t lastfg = LAST_ATTR_INIT, lastbg = LAST_ATTR_INIT;
    415 	if (fg != lastfg || bg != lastbg) {
    416 		memstream_puts(&write_buffer, funcs[T_SGR0]);
    417 		write_sgr(fg & 0x0F, bg & 0x0F);
    418 		if (fg & TB_BOLD)
    419 			memstream_puts(&write_buffer, funcs[T_BOLD]);
    420 		if (bg & TB_BOLD)
    421 			memstream_puts(&write_buffer, funcs[T_BLINK]);
    422 		if (fg & TB_UNDERLINE)
    423 			memstream_puts(&write_buffer, funcs[T_UNDERLINE]);
    424 
    425 		lastfg = fg;
    426 		lastbg = bg;
    427 	}
    428 }
    429 
    430 static void send_char(unsigned int x, unsigned int y, uint32_t c)
    431 {
    432 	char buf[7];
    433 	int bw = utf8_unicode_to_char(buf, c);
    434 	buf[bw] = '\0';
    435 	if (x-1 != lastx || y != lasty)
    436 		write_cursor(x, y);
    437 	lastx = x; lasty = y;
    438 	if(!c) buf[0] = ' '; // replace 0 with whitespace
    439 	memstream_puts(&write_buffer, buf);
    440 }
    441 
    442 static void send_clear(void)
    443 {
    444 	send_attr(foreground, background);
    445 	memstream_puts(&write_buffer, funcs[T_CLEAR_SCREEN]);
    446 	if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
    447 		write_cursor(cursor_x, cursor_y);
    448 	memstream_flush(&write_buffer);
    449 
    450 	/* we need to invalidate cursor position too and these two vars are
    451 	 * used only for simple cursor positioning optimization, cursor
    452 	 * actually may be in the correct place, but we simply discard
    453 	 * optimization once and it gives us simple solution for the case when
    454 	 * cursor moved */
    455 	lastx = LAST_COORD_INIT;
    456 	lasty = LAST_COORD_INIT;
    457 }
    458 
    459 static void sigwinch_handler(int xxx)
    460 {
    461 	(void) xxx;
    462 	const int zzz = 1;
    463 	write(winch_fds[1], &zzz, sizeof(int));
    464 }
    465 
    466 static void update_size(void)
    467 {
    468 	update_term_size();
    469 	cellbuf_resize(&back_buffer, termw, termh);
    470 	cellbuf_resize(&front_buffer, termw, termh);
    471 	cellbuf_clear(&front_buffer);
    472 	send_clear();
    473 }
    474 
    475 static int wait_fill_event(struct tb_event *event, struct timeval *timeout)
    476 {
    477 	/* ;-) */
    478 #define ENOUGH_DATA_FOR_INPUT_PARSING 128
    479 	int result;
    480 	char buf[ENOUGH_DATA_FOR_INPUT_PARSING];
    481 	fd_set events;
    482 	memset(event, 0, sizeof(struct tb_event));
    483 
    484 	/* try to extract event from input buffer, return on success */
    485 	event->type = TB_EVENT_KEY;
    486 	if (extract_event(event, &inbuf, inputmode))
    487 		return TB_EVENT_KEY;
    488 
    489 	/* it looks like input buffer is incomplete, let's try the short path */
    490 	size_t r = fread(buf, 1, ENOUGH_DATA_FOR_INPUT_PARSING, in);
    491 	if (r < ENOUGH_DATA_FOR_INPUT_PARSING && feof(in))
    492 		clearerr(in);
    493 	if (r > 0) {
    494 		if (ringbuffer_free_space(&inbuf) < r)
    495 			return -1;
    496 		ringbuffer_push(&inbuf, buf, r);
    497 		if (extract_event(event, &inbuf, inputmode))
    498 			return TB_EVENT_KEY;
    499 	}
    500 
    501 	/* no stuff in FILE's internal buffer, block in select */
    502 	while (1) {
    503 		FD_ZERO(&events);
    504 		FD_SET(in_fileno, &events);
    505 		FD_SET(winch_fds[0], &events);
    506 		int maxfd = (winch_fds[0] > in_fileno) ? winch_fds[0] : in_fileno;
    507 		result = select(maxfd+1, &events, 0, 0, timeout);
    508 		if (!result)
    509 			return 0;
    510 
    511 		if (FD_ISSET(in_fileno, &events)) {
    512 			event->type = TB_EVENT_KEY;
    513 			size_t r = fread(buf, 1, ENOUGH_DATA_FOR_INPUT_PARSING, in);
    514 			if (r < ENOUGH_DATA_FOR_INPUT_PARSING && feof(in))
    515 				clearerr(in);
    516 			if (r == 0)
    517 				continue;
    518 			/* if there is no free space in input buffer, return error */
    519 			if (ringbuffer_free_space(&inbuf) < r)
    520 				return -1;
    521 			/* fill buffer */
    522 			ringbuffer_push(&inbuf, buf, r);
    523 			if (extract_event(event, &inbuf, inputmode))
    524 				return TB_EVENT_KEY;
    525 		}
    526 		if (FD_ISSET(winch_fds[0], &events)) {
    527 			event->type = TB_EVENT_RESIZE;
    528 			int zzz = 0;
    529 			read(winch_fds[0], &zzz, sizeof(int));
    530 			buffer_size_change_request = 1;
    531 			get_term_size(&event->w, &event->h);
    532 			return TB_EVENT_RESIZE;
    533 		}
    534 	}
    535 }
    536