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 }