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