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