vt.c (47410B)
1 /* 2 * Copyright © 2004 Bruno T. C. de Oliveira 3 * Copyright © 2006 Pierre Habouzit 4 * Copyright © 2008-2016 Marc André Tanner 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 #include <stdlib.h> 19 #include <stdint.h> 20 #include <unistd.h> 21 #include <ctype.h> 22 #include <errno.h> 23 #include <fcntl.h> 24 #include <langinfo.h> 25 #include <limits.h> 26 #include <signal.h> 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <stddef.h> 30 #include <string.h> 31 #include <sys/ioctl.h> 32 #include <sys/types.h> 33 #include <termios.h> 34 #include <wchar.h> 35 #if defined(__linux__) || defined(__CYGWIN__) 36 # include <pty.h> 37 #elif defined(__FreeBSD__) || defined(__DragonFly__) 38 # include <libutil.h> 39 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) 40 # include <util.h> 41 #endif 42 43 #include "vt.h" 44 45 #ifdef _AIX 46 # include "forkpty-aix.c" 47 #elif defined __sun 48 # include "forkpty-sunos.c" 49 #endif 50 51 #ifndef NCURSES_ATTR_SHIFT 52 # define NCURSES_ATTR_SHIFT 8 53 #endif 54 55 #ifndef NCURSES_ACS 56 # ifdef PDCURSES 57 # define NCURSES_ACS(c) (acs_map[(unsigned char)(c)]) 58 # else /* BSD curses */ 59 # define NCURSES_ACS(c) (_acs_map[(unsigned char)(c)]) 60 # endif 61 #endif 62 63 #ifdef NCURSES_VERSION 64 # ifndef NCURSES_EXT_COLORS 65 # define NCURSES_EXT_COLORS 0 66 # endif 67 # if !NCURSES_EXT_COLORS 68 # define MAX_COLOR_PAIRS MIN(COLOR_PAIRS, 256) 69 # endif 70 #endif 71 #ifndef MAX_COLOR_PAIRS 72 # define MAX_COLOR_PAIRS COLOR_PAIRS 73 #endif 74 75 #if defined _AIX && defined CTRL 76 # undef CTRL 77 #endif 78 #ifndef CTRL 79 # define CTRL(k) ((k) & 0x1F) 80 #endif 81 82 #define IS_CONTROL(ch) !((ch) & 0xffffff60UL) 83 #define MIN(x, y) ((x) < (y) ? (x) : (y)) 84 #define LENGTH(arr) (sizeof(arr) / sizeof((arr)[0])) 85 86 static bool is_utf8, has_default_colors; 87 static short color_pairs_reserved, color_pairs_max, color_pair_current; 88 static short *color2palette, default_fg, default_bg; 89 static char vt_term[32]; 90 91 typedef struct { 92 wchar_t text; 93 attr_t attr; 94 short fg; 95 short bg; 96 } Cell; 97 98 typedef struct { 99 Cell *cells; 100 unsigned dirty:1; 101 } Row; 102 103 /* Buffer holding the current terminal window content (as an array) as well 104 * as the scroll back buffer content (as a circular/ring buffer). 105 * 106 * If new content is added to terminal the view port slides down and the 107 * previously top most line is moved into the scroll back buffer at postion 108 * scroll_index. This index will eventually wrap around and thus overwrite 109 * the oldest lines. 110 * 111 * In the scenerio below a scroll up has been performed. That is 'scroll_above' 112 * lines still lie above the current view port. Further scrolling up will show 113 * them. Similarly 'scroll_below' is the amount of lines below the current 114 * viewport. 115 * 116 * The function buffer_boundary sets the row pointers to the start/end range 117 * of the section delimiting the region before/after the viewport. The functions 118 * buffer_row_{first,last} return the first/last logical row. And 119 * buffer_row_{next,prev} allows to iterate over the logical lines in either 120 * direction. 121 * 122 * scroll back buffer 123 * 124 * scroll_buf->+----------------+-----+ 125 * | | | ^ \ 126 * | before | | | | 127 * current terminal content | viewport | | | | 128 * | | | | 129 * +----------------+-----+\ | | | s > scroll_above 130 * ^ | | i | \ | | i | c | 131 * | | | n | \ | | n | r | 132 * | | v | \ | | v | o | 133 * r | | i | \ | | i | l / 134 * o | viewport | s | >|<- scroll_index | s | l \ 135 * w | | i | / | | i | | 136 * s | | b | / | after | b | s > scroll_below 137 * | | l | / | viewport | l | i | 138 * v | | e | / | | e | z / 139 * +----------------+-----+/ | unused | | e 140 * <- maxcols -> | scroll back | | 141 * <- cols -> | buffer | | | 142 * | | | | 143 * | | | v 144 * roll_buf + scroll_size->+----------------+-----+ 145 * <- maxcols -> 146 * <- cols -> 147 */ 148 typedef struct { 149 Row *lines; /* array of Row pointers of size 'rows' */ 150 Row *curs_row; /* row on which the cursor currently resides */ 151 Row *scroll_buf; /* a ring buffer holding the scroll back content */ 152 Row *scroll_top; /* row in lines where scrolling region starts */ 153 Row *scroll_bot; /* row in lines where scrolling region ends */ 154 bool *tabs; /* a boolean flag for each column whether it is a tab */ 155 int scroll_size; /* maximal capacity of scroll back buffer (in lines) */ 156 int scroll_index; /* current index into the ring buffer */ 157 int scroll_above; /* number of lines above current viewport */ 158 int scroll_below; /* number of lines below current viewport */ 159 int rows, cols; /* current dimension of buffer */ 160 int maxcols; /* allocated cells (maximal cols over time) */ 161 attr_t curattrs, savattrs; /* current and saved attributes for cells */ 162 int curs_col; /* current cursor column (zero based) */ 163 int curs_srow, curs_scol; /* saved cursor row/colmn (zero based) */ 164 short curfg, curbg; /* current fore and background colors */ 165 short savfg, savbg; /* saved colors */ 166 } Buffer; 167 168 struct Vt { 169 Buffer buffer_normal; /* normal screen buffer */ 170 Buffer buffer_alternate; /* alternate screen buffer */ 171 Buffer *buffer; /* currently active buffer (one of the above) */ 172 attr_t defattrs; /* attributes to use for normal/empty cells */ 173 short deffg, defbg; /* colors to use for back normal/empty cells (white/black) */ 174 int pty; /* master side pty file descriptor */ 175 pid_t pid; /* process id of the process running in this vt */ 176 /* flags */ 177 unsigned seen_input:1; 178 unsigned insert:1; 179 unsigned escaped:1; 180 unsigned curshid:1; 181 unsigned curskeymode:1; 182 unsigned bell:1; 183 unsigned relposmode:1; 184 unsigned mousetrack:1; 185 unsigned graphmode:1; 186 unsigned savgraphmode:1; 187 bool charsets[2]; 188 /* buffers and parsing state */ 189 char rbuf[BUFSIZ]; 190 char ebuf[BUFSIZ]; 191 unsigned int rlen, elen; 192 int srow, scol; /* last known offset to display start row, start column */ 193 char title[256]; /* xterm style window title */ 194 vt_title_handler_t title_handler; /* hook which is called when title changes */ 195 vt_urgent_handler_t urgent_handler; /* hook which is called upon bell */ 196 void *data; /* user supplied data */ 197 }; 198 199 static const char *keytable[KEY_MAX+1] = { 200 [KEY_ENTER] = "\r", 201 ['\n'] = "\n", 202 /* for the arrow keys the CSI / SS3 sequences are not stored here 203 * because they depend on the current cursor terminal mode 204 */ 205 [KEY_UP] = "A", 206 [KEY_DOWN] = "B", 207 [KEY_RIGHT] = "C", 208 [KEY_LEFT] = "D", 209 #ifdef KEY_SUP 210 [KEY_SUP] = "\e[1;2A", 211 #endif 212 #ifdef KEY_SDOWN 213 [KEY_SDOWN] = "\e[1;2B", 214 #endif 215 [KEY_SRIGHT] = "\e[1;2C", 216 [KEY_SLEFT] = "\e[1;2D", 217 [KEY_BACKSPACE] = "\177", 218 [KEY_IC] = "\e[2~", 219 [KEY_DC] = "\e[3~", 220 [KEY_PPAGE] = "\e[5~", 221 [KEY_NPAGE] = "\e[6~", 222 [KEY_HOME] = "\e[7~", 223 [KEY_END] = "\e[8~", 224 [KEY_BTAB] = "\e[Z", 225 [KEY_SUSPEND] = "\x1A", /* Ctrl+Z gets mapped to this */ 226 [KEY_F(1)] = "\e[11~", 227 [KEY_F(2)] = "\e[12~", 228 [KEY_F(3)] = "\e[13~", 229 [KEY_F(4)] = "\e[14~", 230 [KEY_F(5)] = "\e[15~", 231 [KEY_F(6)] = "\e[17~", 232 [KEY_F(7)] = "\e[18~", 233 [KEY_F(8)] = "\e[19~", 234 [KEY_F(9)] = "\e[20~", 235 [KEY_F(10)] = "\e[21~", 236 [KEY_F(11)] = "\e[23~", 237 [KEY_F(12)] = "\e[24~", 238 [KEY_F(13)] = "\e[23~", 239 [KEY_F(14)] = "\e[24~", 240 [KEY_F(15)] = "\e[25~", 241 [KEY_F(16)] = "\e[26~", 242 [KEY_F(17)] = "\e[28~", 243 [KEY_F(18)] = "\e[29~", 244 [KEY_F(19)] = "\e[31~", 245 [KEY_F(20)] = "\e[32~", 246 [KEY_F(21)] = "\e[33~", 247 [KEY_F(22)] = "\e[34~", 248 [KEY_RESIZE] = "", 249 #ifdef KEY_EVENT 250 [KEY_EVENT] = "", 251 #endif 252 }; 253 254 static void puttab(Vt *t, int count); 255 static void process_nonprinting(Vt *t, wchar_t wc); 256 static void send_curs(Vt *t); 257 258 __attribute__ ((const)) 259 static attr_t build_attrs(attr_t curattrs) 260 { 261 return ((curattrs & ~A_COLOR) | COLOR_PAIR(curattrs & 0xff)) 262 >> NCURSES_ATTR_SHIFT; 263 } 264 265 static void row_set(Row *row, int start, int len, Buffer *t) 266 { 267 Cell cell = { 268 .text = L'\0', 269 .attr = t ? build_attrs(t->curattrs) : 0, 270 .fg = t ? t->curfg : -1, 271 .bg = t ? t->curbg : -1, 272 }; 273 274 for (int i = start; i < len + start; i++) 275 row->cells[i] = cell; 276 row->dirty = true; 277 } 278 279 static void row_roll(Row *start, Row *end, int count) 280 { 281 int n = end - start; 282 283 count %= n; 284 if (count < 0) 285 count += n; 286 287 if (count) { 288 char buf[count * sizeof(Row)]; 289 memcpy(buf, start, count * sizeof(Row)); 290 memmove(start, start + count, (n - count) * sizeof(Row)); 291 memcpy(end - count, buf, count * sizeof(Row)); 292 for (Row *row = start; row < end; row++) 293 row->dirty = true; 294 } 295 } 296 297 static void buffer_clear(Buffer *b) 298 { 299 Cell cell = { 300 .text = L'\0', 301 .attr = A_NORMAL, 302 .fg = -1, 303 .bg = -1, 304 }; 305 306 for (int i = 0; i < b->rows; i++) { 307 Row *row = b->lines + i; 308 for (int j = 0; j < b->cols; j++) { 309 row->cells[j] = cell; 310 row->dirty = true; 311 } 312 } 313 } 314 315 static void buffer_free(Buffer *b) 316 { 317 for (int i = 0; i < b->rows; i++) 318 free(b->lines[i].cells); 319 free(b->lines); 320 for (int i = 0; i < b->scroll_size; i++) 321 free(b->scroll_buf[i].cells); 322 free(b->scroll_buf); 323 free(b->tabs); 324 } 325 326 static void buffer_scroll(Buffer *b, int s) 327 { 328 /* work in screenfuls */ 329 int ssz = b->scroll_bot - b->scroll_top; 330 if (s > ssz) { 331 buffer_scroll(b, ssz); 332 buffer_scroll(b, s - ssz); 333 return; 334 } 335 if (s < -ssz) { 336 buffer_scroll(b, -ssz); 337 buffer_scroll(b, s + ssz); 338 return; 339 } 340 341 b->scroll_above += s; 342 if (b->scroll_above >= b->scroll_size) 343 b->scroll_above = b->scroll_size; 344 345 if (s > 0 && b->scroll_size) { 346 for (int i = 0; i < s; i++) { 347 Row tmp = b->scroll_top[i]; 348 b->scroll_top[i] = b->scroll_buf[b->scroll_index]; 349 b->scroll_buf[b->scroll_index] = tmp; 350 351 b->scroll_index++; 352 if (b->scroll_index == b->scroll_size) 353 b->scroll_index = 0; 354 } 355 } 356 row_roll(b->scroll_top, b->scroll_bot, s); 357 if (s < 0 && b->scroll_size) { 358 for (int i = (-s) - 1; i >= 0; i--) { 359 b->scroll_index--; 360 if (b->scroll_index == -1) 361 b->scroll_index = b->scroll_size - 1; 362 363 Row tmp = b->scroll_top[i]; 364 b->scroll_top[i] = b->scroll_buf[b->scroll_index]; 365 b->scroll_buf[b->scroll_index] = tmp; 366 b->scroll_top[i].dirty = true; 367 } 368 } 369 } 370 371 static void buffer_resize(Buffer *b, int rows, int cols) 372 { 373 Row *lines = b->lines; 374 375 if (b->rows != rows) { 376 if (b->curs_row >= lines + rows) { 377 /* scroll up instead of simply chopping off bottom */ 378 buffer_scroll(b, (b->curs_row - b->lines) - rows + 1); 379 } 380 while (b->rows > rows) { 381 free(lines[b->rows - 1].cells); 382 b->rows--; 383 } 384 385 lines = realloc(lines, sizeof(Row) * rows); 386 } 387 388 if (b->maxcols < cols) { 389 for (int row = 0; row < b->rows; row++) { 390 lines[row].cells = realloc(lines[row].cells, sizeof(Cell) * cols); 391 if (b->cols < cols) 392 row_set(lines + row, b->cols, cols - b->cols, NULL); 393 lines[row].dirty = true; 394 } 395 Row *sbuf = b->scroll_buf; 396 for (int row = 0; row < b->scroll_size; row++) { 397 sbuf[row].cells = realloc(sbuf[row].cells, sizeof(Cell) * cols); 398 if (b->cols < cols) 399 row_set(sbuf + row, b->cols, cols - b->cols, NULL); 400 } 401 b->tabs = realloc(b->tabs, sizeof(*b->tabs) * cols); 402 for (int col = b->cols; col < cols; col++) 403 b->tabs[col] = !(col & 7); 404 b->maxcols = cols; 405 b->cols = cols; 406 } else if (b->cols != cols) { 407 for (int row = 0; row < b->rows; row++) 408 lines[row].dirty = true; 409 b->cols = cols; 410 } 411 412 int deltarows = 0; 413 if (b->rows < rows) { 414 while (b->rows < rows) { 415 lines[b->rows].cells = calloc(b->maxcols, sizeof(Cell)); 416 row_set(lines + b->rows, 0, b->maxcols, b); 417 b->rows++; 418 } 419 420 /* prepare for backfill */ 421 if (b->curs_row >= b->scroll_bot - 1) { 422 deltarows = b->lines + rows - b->curs_row - 1; 423 if (deltarows > b->scroll_above) 424 deltarows = b->scroll_above; 425 } 426 } 427 428 b->curs_row += lines - b->lines; 429 b->scroll_top = lines; 430 b->scroll_bot = lines + rows; 431 b->lines = lines; 432 433 /* perform backfill */ 434 if (deltarows > 0) { 435 buffer_scroll(b, -deltarows); 436 b->curs_row += deltarows; 437 } 438 } 439 440 static bool buffer_init(Buffer *b, int rows, int cols, int scroll_size) 441 { 442 b->curattrs = A_NORMAL; /* white text over black background */ 443 b->curfg = b->curbg = -1; 444 if (scroll_size < 0) 445 scroll_size = 0; 446 if (scroll_size && !(b->scroll_buf = calloc(scroll_size, sizeof(Row)))) 447 return false; 448 b->scroll_size = scroll_size; 449 buffer_resize(b, rows, cols); 450 return true; 451 } 452 453 static void buffer_boundry(Buffer *b, Row **bs, Row **be, Row **as, Row **ae) { 454 if (bs) 455 *bs = NULL; 456 if (be) 457 *be = NULL; 458 if (as) 459 *as = NULL; 460 if (ae) 461 *ae = NULL; 462 if (!b->scroll_size) 463 return; 464 465 if (b->scroll_above) { 466 if (bs) 467 *bs = &b->scroll_buf[(b->scroll_index - b->scroll_above + b->scroll_size) % b->scroll_size]; 468 if (be) 469 *be = &b->scroll_buf[(b->scroll_index-1 + b->scroll_size) % b->scroll_size]; 470 } 471 if (b->scroll_below) { 472 if (as) 473 *as = &b->scroll_buf[b->scroll_index]; 474 if (ae) 475 *ae = &b->scroll_buf[(b->scroll_index + b->scroll_below-1) % b->scroll_size]; 476 } 477 } 478 479 static Row *buffer_row_first(Buffer *b) { 480 Row *bstart; 481 if (!b->scroll_size || !b->scroll_above) 482 return b->lines; 483 buffer_boundry(b, &bstart, NULL, NULL, NULL); 484 return bstart; 485 } 486 487 static Row *buffer_row_last(Buffer *b) { 488 Row *aend; 489 if (!b->scroll_size || !b->scroll_below) 490 return b->lines + b->rows - 1; 491 buffer_boundry(b, NULL, NULL, NULL, &aend); 492 return aend; 493 } 494 495 static Row *buffer_row_next(Buffer *b, Row *row) 496 { 497 Row *before_start, *before_end, *after_start, *after_end; 498 Row *first = b->lines, *last = b->lines + b->rows - 1; 499 500 if (!row) 501 return NULL; 502 503 buffer_boundry(b, &before_start, &before_end, &after_start, &after_end); 504 505 if (row >= first && row < last) 506 return ++row; 507 if (row == last) 508 return after_start; 509 if (row == before_end) 510 return first; 511 if (row == after_end) 512 return NULL; 513 if (row == &b->scroll_buf[b->scroll_size - 1]) 514 return b->scroll_buf; 515 return ++row; 516 } 517 518 static Row *buffer_row_prev(Buffer *b, Row *row) 519 { 520 Row *before_start, *before_end, *after_start, *after_end; 521 Row *first = b->lines, *last = b->lines + b->rows - 1; 522 523 if (!row) 524 return NULL; 525 526 buffer_boundry(b, &before_start, &before_end, &after_start, &after_end); 527 528 if (row > first && row <= last) 529 return --row; 530 if (row == first) 531 return before_end; 532 if (row == before_start) 533 return NULL; 534 if (row == after_start) 535 return last; 536 if (row == b->scroll_buf) 537 return &b->scroll_buf[b->scroll_size - 1]; 538 return --row; 539 } 540 541 static void cursor_clamp(Vt *t) 542 { 543 Buffer *b = t->buffer; 544 Row *lines = t->relposmode ? b->scroll_top : b->lines; 545 int rows = t->relposmode ? b->scroll_bot - b->scroll_top : b->rows; 546 547 if (b->curs_row < lines) 548 b->curs_row = lines; 549 if (b->curs_row >= lines + rows) 550 b->curs_row = lines + rows - 1; 551 if (b->curs_col < 0) 552 b->curs_col = 0; 553 if (b->curs_col >= b->cols) 554 b->curs_col = b->cols - 1; 555 } 556 557 static void cursor_line_down(Vt *t) 558 { 559 Buffer *b = t->buffer; 560 row_set(b->curs_row, b->cols, b->maxcols - b->cols, NULL); 561 b->curs_row++; 562 if (b->curs_row < b->scroll_bot) 563 return; 564 565 vt_noscroll(t); 566 567 b->curs_row = b->scroll_bot - 1; 568 buffer_scroll(b, 1); 569 row_set(b->curs_row, 0, b->cols, b); 570 } 571 572 static void cursor_save(Vt *t) 573 { 574 Buffer *b = t->buffer; 575 b->curs_srow = b->curs_row - b->lines; 576 b->curs_scol = b->curs_col; 577 } 578 579 static void cursor_restore(Vt *t) 580 { 581 Buffer *b = t->buffer; 582 b->curs_row = b->lines + b->curs_srow; 583 b->curs_col = b->curs_scol; 584 cursor_clamp(t); 585 } 586 587 static void attributes_save(Vt *t) 588 { 589 Buffer *b = t->buffer; 590 b->savattrs = b->curattrs; 591 b->savfg = b->curfg; 592 b->savbg = b->curbg; 593 t->savgraphmode = t->graphmode; 594 } 595 596 static void attributes_restore(Vt *t) 597 { 598 Buffer *b = t->buffer; 599 b->curattrs = b->savattrs; 600 b->curfg = b->savfg; 601 b->curbg = b->savbg; 602 t->graphmode = t->savgraphmode; 603 } 604 605 static void new_escape_sequence(Vt *t) 606 { 607 t->escaped = true; 608 t->elen = 0; 609 t->ebuf[0] = '\0'; 610 } 611 612 static void cancel_escape_sequence(Vt *t) 613 { 614 t->escaped = false; 615 t->elen = 0; 616 t->ebuf[0] = '\0'; 617 } 618 619 static bool is_valid_csi_ender(int c) 620 { 621 return (c >= 'a' && c <= 'z') 622 || (c >= 'A' && c <= 'Z') 623 || (c == '@' || c == '`'); 624 } 625 626 /* interprets a 'set attribute' (SGR) CSI escape sequence */ 627 static void interpret_csi_sgr(Vt *t, int param[], int pcount) 628 { 629 Buffer *b = t->buffer; 630 if (pcount == 0) { 631 /* special case: reset attributes */ 632 b->curattrs = A_NORMAL; 633 b->curfg = b->curbg = -1; 634 return; 635 } 636 637 for (int i = 0; i < pcount; i++) { 638 switch (param[i]) { 639 case 0: 640 b->curattrs = A_NORMAL; 641 b->curfg = b->curbg = -1; 642 break; 643 case 1: 644 b->curattrs |= A_BOLD; 645 break; 646 case 2: 647 b->curattrs |= A_DIM; 648 break; 649 #ifdef A_ITALIC 650 case 3: 651 b->curattrs |= A_ITALIC; 652 break; 653 #endif 654 case 4: 655 b->curattrs |= A_UNDERLINE; 656 break; 657 case 5: 658 b->curattrs |= A_BLINK; 659 break; 660 case 7: 661 b->curattrs |= A_REVERSE; 662 break; 663 case 8: 664 b->curattrs |= A_INVIS; 665 break; 666 case 22: 667 b->curattrs &= ~(A_BOLD | A_DIM); 668 break; 669 #ifdef A_ITALIC 670 case 23: 671 b->curattrs &= ~A_ITALIC; 672 break; 673 #endif 674 case 24: 675 b->curattrs &= ~A_UNDERLINE; 676 break; 677 case 25: 678 b->curattrs &= ~A_BLINK; 679 break; 680 case 27: 681 b->curattrs &= ~A_REVERSE; 682 break; 683 case 28: 684 b->curattrs &= ~A_INVIS; 685 break; 686 case 30 ... 37: /* fg */ 687 b->curfg = param[i] - 30; 688 break; 689 case 38: 690 if ((i + 2) < pcount && param[i + 1] == 5) { 691 b->curfg = param[i + 2]; 692 i += 2; 693 } 694 break; 695 case 39: 696 b->curfg = -1; 697 break; 698 case 40 ... 47: /* bg */ 699 b->curbg = param[i] - 40; 700 break; 701 case 48: 702 if ((i + 2) < pcount && param[i + 1] == 5) { 703 b->curbg = param[i + 2]; 704 i += 2; 705 } 706 break; 707 case 49: 708 b->curbg = -1; 709 break; 710 case 90 ... 97: /* hi fg */ 711 b->curfg = param[i] - 82; 712 break; 713 case 100 ... 107: /* hi bg */ 714 b->curbg = param[i] - 92; 715 break; 716 default: 717 break; 718 } 719 } 720 } 721 722 /* interprets an 'erase display' (ED) escape sequence */ 723 static void interpret_csi_ed(Vt *t, int param[], int pcount) 724 { 725 Row *row, *start, *end; 726 Buffer *b = t->buffer; 727 728 attributes_save(t); 729 b->curattrs = A_NORMAL; 730 b->curfg = b->curbg = -1; 731 732 if (pcount && param[0] == 2) { 733 start = b->lines; 734 end = b->lines + b->rows; 735 } else if (pcount && param[0] == 1) { 736 start = b->lines; 737 end = b->curs_row; 738 row_set(b->curs_row, 0, b->curs_col + 1, b); 739 } else { 740 row_set(b->curs_row, b->curs_col, b->cols - b->curs_col, b); 741 start = b->curs_row + 1; 742 end = b->lines + b->rows; 743 } 744 745 for (row = start; row < end; row++) 746 row_set(row, 0, b->cols, b); 747 748 attributes_restore(t); 749 } 750 751 /* interprets a 'move cursor' (CUP) escape sequence */ 752 static void interpret_csi_cup(Vt *t, int param[], int pcount) 753 { 754 Buffer *b = t->buffer; 755 Row *lines = t->relposmode ? b->scroll_top : b->lines; 756 757 if (pcount == 0) { 758 b->curs_row = lines; 759 b->curs_col = 0; 760 } else if (pcount == 1) { 761 b->curs_row = lines + param[0] - 1; 762 b->curs_col = 0; 763 } else { 764 b->curs_row = lines + param[0] - 1; 765 b->curs_col = param[1] - 1; 766 } 767 768 cursor_clamp(t); 769 } 770 771 /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL, 772 * CPL, CHA, HPR, VPA, VPR, HPA */ 773 static void interpret_csi_c(Vt *t, char verb, int param[], int pcount) 774 { 775 Buffer *b = t->buffer; 776 int n = (pcount && param[0] > 0) ? param[0] : 1; 777 778 switch (verb) { 779 case 'A': 780 b->curs_row -= n; 781 break; 782 case 'B': 783 case 'e': 784 b->curs_row += n; 785 break; 786 case 'C': 787 case 'a': 788 b->curs_col += n; 789 break; 790 case 'D': 791 b->curs_col -= n; 792 break; 793 case 'E': 794 b->curs_row += n; 795 b->curs_col = 0; 796 break; 797 case 'F': 798 b->curs_row -= n; 799 b->curs_col = 0; 800 break; 801 case 'G': 802 case '`': 803 b->curs_col = n - 1; 804 break; 805 case 'd': 806 b->curs_row = b->lines + n - 1; 807 break; 808 } 809 810 cursor_clamp(t); 811 } 812 813 /* Interpret the 'erase line' escape sequence */ 814 static void interpret_csi_el(Vt *t, int param[], int pcount) 815 { 816 Buffer *b = t->buffer; 817 switch (pcount ? param[0] : 0) { 818 case 1: 819 row_set(b->curs_row, 0, b->curs_col + 1, b); 820 break; 821 case 2: 822 row_set(b->curs_row, 0, b->cols, b); 823 break; 824 default: 825 row_set(b->curs_row, b->curs_col, b->cols - b->curs_col, b); 826 break; 827 } 828 } 829 830 /* Interpret the 'insert blanks' sequence (ICH) */ 831 static void interpret_csi_ich(Vt *t, int param[], int pcount) 832 { 833 Buffer *b = t->buffer; 834 Row *row = b->curs_row; 835 int n = (pcount && param[0] > 0) ? param[0] : 1; 836 837 if (b->curs_col + n > b->cols) 838 n = b->cols - b->curs_col; 839 840 for (int i = b->cols - 1; i >= b->curs_col + n; i--) 841 row->cells[i] = row->cells[i - n]; 842 843 row_set(row, b->curs_col, n, b); 844 } 845 846 /* Interpret the 'delete chars' sequence (DCH) */ 847 static void interpret_csi_dch(Vt *t, int param[], int pcount) 848 { 849 Buffer *b = t->buffer; 850 Row *row = b->curs_row; 851 int n = (pcount && param[0] > 0) ? param[0] : 1; 852 853 if (b->curs_col + n > b->cols) 854 n = b->cols - b->curs_col; 855 856 for (int i = b->curs_col; i < b->cols - n; i++) 857 row->cells[i] = row->cells[i + n]; 858 859 row_set(row, b->cols - n, n, b); 860 } 861 862 /* Interpret an 'insert line' sequence (IL) */ 863 static void interpret_csi_il(Vt *t, int param[], int pcount) 864 { 865 Buffer *b = t->buffer; 866 int n = (pcount && param[0] > 0) ? param[0] : 1; 867 868 if (b->curs_row + n >= b->scroll_bot) { 869 for (Row *row = b->curs_row; row < b->scroll_bot; row++) 870 row_set(row, 0, b->cols, b); 871 } else { 872 row_roll(b->curs_row, b->scroll_bot, -n); 873 for (Row *row = b->curs_row; row < b->curs_row + n; row++) 874 row_set(row, 0, b->cols, b); 875 } 876 } 877 878 /* Interpret a 'delete line' sequence (DL) */ 879 static void interpret_csi_dl(Vt *t, int param[], int pcount) 880 { 881 Buffer *b = t->buffer; 882 int n = (pcount && param[0] > 0) ? param[0] : 1; 883 884 if (b->curs_row + n >= b->scroll_bot) { 885 for (Row *row = b->curs_row; row < b->scroll_bot; row++) 886 row_set(row, 0, b->cols, b); 887 } else { 888 row_roll(b->curs_row, b->scroll_bot, n); 889 for (Row *row = b->scroll_bot - n; row < b->scroll_bot; row++) 890 row_set(row, 0, b->cols, b); 891 } 892 } 893 894 /* Interpret an 'erase characters' (ECH) sequence */ 895 static void interpret_csi_ech(Vt *t, int param[], int pcount) 896 { 897 Buffer *b = t->buffer; 898 int n = (pcount && param[0] > 0) ? param[0] : 1; 899 900 if (b->curs_col + n > b->cols) 901 n = b->cols - b->curs_col; 902 903 row_set(b->curs_row, b->curs_col, n, b); 904 } 905 906 /* Interpret a 'set scrolling region' (DECSTBM) sequence */ 907 static void interpret_csi_decstbm(Vt *t, int param[], int pcount) 908 { 909 Buffer *b = t->buffer; 910 int new_top, new_bot; 911 912 switch (pcount) { 913 case 0: 914 b->scroll_top = b->lines; 915 b->scroll_bot = b->lines + b->rows; 916 break; 917 case 2: 918 new_top = param[0] - 1; 919 new_bot = param[1]; 920 921 /* clamp to bounds */ 922 if (new_top < 0) 923 new_top = 0; 924 if (new_top >= b->rows) 925 new_top = b->rows - 1; 926 if (new_bot < 0) 927 new_bot = 0; 928 if (new_bot >= b->rows) 929 new_bot = b->rows; 930 931 /* check for range validity */ 932 if (new_top < new_bot) { 933 b->scroll_top = b->lines + new_top; 934 b->scroll_bot = b->lines + new_bot; 935 } 936 break; 937 default: 938 return; /* malformed */ 939 } 940 b->curs_row = b->scroll_top; 941 b->curs_col = 0; 942 } 943 944 static void interpret_csi_mode(Vt *t, int param[], int pcount, bool set) 945 { 946 for (int i = 0; i < pcount; i++) { 947 switch (param[i]) { 948 case 4: /* insert/replace mode */ 949 t->insert = set; 950 break; 951 } 952 } 953 } 954 955 static void interpret_csi_priv_mode(Vt *t, int param[], int pcount, bool set) 956 { 957 for (int i = 0; i < pcount; i++) { 958 switch (param[i]) { 959 case 1: /* set application/normal cursor key mode (DECCKM) */ 960 t->curskeymode = set; 961 break; 962 case 6: /* set origin to relative/absolute (DECOM) */ 963 t->relposmode = set; 964 break; 965 case 25: /* make cursor visible/invisible (DECCM) */ 966 t->curshid = !set; 967 break; 968 case 1049: /* combine 1047 + 1048 */ 969 case 47: /* use alternate/normal screen buffer */ 970 case 1047: 971 if (!set) 972 buffer_clear(&t->buffer_alternate); 973 t->buffer = set ? &t->buffer_alternate : &t->buffer_normal; 974 cursor_clamp(t); 975 vt_dirty(t); 976 if (param[i] != 1049) 977 break; 978 /* fall through */ 979 case 1048: /* save/restore cursor */ 980 if (set) 981 cursor_save(t); 982 else 983 cursor_restore(t); 984 break; 985 case 1000: /* enable/disable normal mouse tracking */ 986 t->mousetrack = set; 987 break; 988 } 989 } 990 } 991 992 static void interpret_csi(Vt *t) 993 { 994 Buffer *b = t->buffer; 995 int csiparam[16]; 996 unsigned int param_count = 0; 997 const char *p = t->ebuf + 1; 998 char verb = t->ebuf[t->elen - 1]; 999 1000 /* parse numeric parameters */ 1001 for (p += (t->ebuf[1] == '?'); *p; p++) { 1002 if (IS_CONTROL(*p)) { 1003 process_nonprinting(t, *p); 1004 } else if (*p == ';') { 1005 if (param_count >= LENGTH(csiparam)) 1006 return; /* too long! */ 1007 csiparam[param_count++] = 0; 1008 } else if (isdigit((unsigned char)*p)) { 1009 if (param_count == 0) 1010 csiparam[param_count++] = 0; 1011 csiparam[param_count - 1] *= 10; 1012 csiparam[param_count - 1] += *p - '0'; 1013 } 1014 } 1015 1016 if (t->ebuf[1] == '?') { 1017 switch (verb) { 1018 case 'h': 1019 case 'l': /* private set/reset mode */ 1020 interpret_csi_priv_mode(t, csiparam, param_count, verb == 'h'); 1021 break; 1022 } 1023 return; 1024 } 1025 1026 /* delegate handling depending on command character (verb) */ 1027 switch (verb) { 1028 case 'h': 1029 case 'l': /* set/reset mode */ 1030 interpret_csi_mode(t, csiparam, param_count, verb == 'h'); 1031 break; 1032 case 'm': /* set attribute */ 1033 interpret_csi_sgr(t, csiparam, param_count); 1034 break; 1035 case 'J': /* erase display */ 1036 interpret_csi_ed(t, csiparam, param_count); 1037 break; 1038 case 'H': 1039 case 'f': /* move cursor */ 1040 interpret_csi_cup(t, csiparam, param_count); 1041 break; 1042 case 'A': 1043 case 'B': 1044 case 'C': 1045 case 'D': 1046 case 'E': 1047 case 'F': 1048 case 'G': 1049 case 'e': 1050 case 'a': 1051 case 'd': 1052 case '`': /* relative move */ 1053 interpret_csi_c(t, verb, csiparam, param_count); 1054 break; 1055 case 'K': /* erase line */ 1056 interpret_csi_el(t, csiparam, param_count); 1057 break; 1058 case '@': /* insert characters */ 1059 interpret_csi_ich(t, csiparam, param_count); 1060 break; 1061 case 'P': /* delete characters */ 1062 interpret_csi_dch(t, csiparam, param_count); 1063 break; 1064 case 'L': /* insert lines */ 1065 interpret_csi_il(t, csiparam, param_count); 1066 break; 1067 case 'M': /* delete lines */ 1068 interpret_csi_dl(t, csiparam, param_count); 1069 break; 1070 case 'X': /* erase chars */ 1071 interpret_csi_ech(t, csiparam, param_count); 1072 break; 1073 case 'S': /* SU: scroll up */ 1074 vt_scroll(t, param_count ? -csiparam[0] : -1); 1075 break; 1076 case 'T': /* SD: scroll down */ 1077 vt_scroll(t, param_count ? csiparam[0] : 1); 1078 break; 1079 case 'Z': /* CBT: cursor backward tabulation */ 1080 puttab(t, param_count ? -csiparam[0] : -1); 1081 break; 1082 case 'g': /* TBC: tabulation clear */ 1083 switch (param_count ? csiparam[0] : 0) { 1084 case 0: 1085 b->tabs[b->curs_col] = false; 1086 break; 1087 case 3: 1088 memset(b->tabs, 0, sizeof(*b->tabs) * b->maxcols); 1089 break; 1090 } 1091 break; 1092 case 'r': /* set scrolling region */ 1093 interpret_csi_decstbm(t, csiparam, param_count); 1094 break; 1095 case 's': /* save cursor location */ 1096 cursor_save(t); 1097 break; 1098 case 'u': /* restore cursor location */ 1099 cursor_restore(t); 1100 break; 1101 case 'n': /* query cursor location */ 1102 if (param_count == 1 && csiparam[0] == 6) 1103 send_curs(t); 1104 break; 1105 default: 1106 break; 1107 } 1108 } 1109 1110 /* Interpret an 'index' (IND) sequence */ 1111 static void interpret_csi_ind(Vt *t) 1112 { 1113 Buffer *b = t->buffer; 1114 if (b->curs_row < b->lines + b->rows - 1) 1115 b->curs_row++; 1116 } 1117 1118 /* Interpret a 'reverse index' (RI) sequence */ 1119 static void interpret_csi_ri(Vt *t) 1120 { 1121 Buffer *b = t->buffer; 1122 if (b->curs_row > b->scroll_top) 1123 b->curs_row--; 1124 else { 1125 row_roll(b->scroll_top, b->scroll_bot, -1); 1126 row_set(b->scroll_top, 0, b->cols, b); 1127 } 1128 } 1129 1130 /* Interpret a 'next line' (NEL) sequence */ 1131 static void interpret_csi_nel(Vt *t) 1132 { 1133 Buffer *b = t->buffer; 1134 if (b->curs_row < b->lines + b->rows - 1) { 1135 b->curs_row++; 1136 b->curs_col = 0; 1137 } 1138 } 1139 1140 /* Interpret a 'select character set' (SCS) sequence */ 1141 static void interpret_csi_scs(Vt *t) 1142 { 1143 /* ESC ( sets G0, ESC ) sets G1 */ 1144 t->charsets[!!(t->ebuf[0] == ')')] = (t->ebuf[1] == '0'); 1145 t->graphmode = t->charsets[0]; 1146 } 1147 1148 /* Interpret an 'operating system command' (OSC) sequence */ 1149 static void interpret_osc(Vt *t) 1150 { 1151 /* ESC ] command ; data BEL 1152 * ESC ] command ; data ESC \\ 1153 * Note that BEL or ESC \\ have already been replaced with NUL. 1154 */ 1155 char *data = NULL; 1156 int command = strtoul(t->ebuf + 1, &data, 10); 1157 if (data && *data == ';') { 1158 switch (command) { 1159 case 0: /* icon name and window title */ 1160 case 2: /* window title */ 1161 if (t->title_handler) 1162 0; 1163 break; 1164 case 1: /* icon name */ 1165 break; 1166 default: 1167 #ifndef NDEBUG 1168 fprintf(stderr, "unknown OSC command: %d\n", command); 1169 #endif 1170 break; 1171 } 1172 } 1173 } 1174 1175 static void try_interpret_escape_seq(Vt *t) 1176 { 1177 char lastchar = t->ebuf[t->elen - 1]; 1178 1179 if (!*t->ebuf) 1180 return; 1181 1182 switch (*t->ebuf) { 1183 case '#': /* ignore DECDHL, DECSWL, DECDWL, DECHCP, DECFPP */ 1184 if (t->elen == 2) { 1185 if (lastchar == '8') { /* DECALN */ 1186 interpret_csi_ed(t, (int []){ 2 }, 1); 1187 goto handled; 1188 } 1189 goto cancel; 1190 } 1191 break; 1192 case '(': 1193 case ')': 1194 if (t->elen == 2) { 1195 interpret_csi_scs(t); 1196 goto handled; 1197 } 1198 break; 1199 case ']': /* OSC - operating system command */ 1200 if (lastchar == '\a' || 1201 (lastchar == '\\' && t->elen >= 2 && t->ebuf[t->elen - 2] == '\e')) { 1202 t->elen -= lastchar == '\a' ? 1 : 2; 1203 t->ebuf[t->elen] = '\0'; 1204 interpret_osc(t); 1205 goto handled; 1206 } 1207 break; 1208 case '[': /* CSI - control sequence introducer */ 1209 if (is_valid_csi_ender(lastchar)) { 1210 interpret_csi(t); 1211 goto handled; 1212 } 1213 break; 1214 case '7': /* DECSC: save cursor and attributes */ 1215 attributes_save(t); 1216 cursor_save(t); 1217 goto handled; 1218 case '8': /* DECRC: restore cursor and attributes */ 1219 attributes_restore(t); 1220 cursor_restore(t); 1221 goto handled; 1222 case 'D': /* IND: index */ 1223 interpret_csi_ind(t); 1224 goto handled; 1225 case 'M': /* RI: reverse index */ 1226 interpret_csi_ri(t); 1227 goto handled; 1228 case 'E': /* NEL: next line */ 1229 interpret_csi_nel(t); 1230 goto handled; 1231 case 'H': /* HTS: horizontal tab set */ 1232 t->buffer->tabs[t->buffer->curs_col] = true; 1233 goto handled; 1234 default: 1235 goto cancel; 1236 } 1237 1238 if (t->elen + 1 >= sizeof(t->ebuf)) { 1239 cancel: 1240 #ifndef NDEBUG 1241 fprintf(stderr, "cancelled: \\033"); 1242 for (unsigned int i = 0; i < t->elen; i++) { 1243 if (isprint(t->ebuf[i])) { 1244 fputc(t->ebuf[i], stderr); 1245 } else { 1246 fprintf(stderr, "\\%03o", t->ebuf[i]); 1247 } 1248 } 1249 fputc('\n', stderr); 1250 #endif 1251 handled: 1252 cancel_escape_sequence(t); 1253 } 1254 } 1255 1256 static void puttab(Vt *t, int count) 1257 { 1258 Buffer *b = t->buffer; 1259 int direction = count >= 0 ? 1 : -1; 1260 for (int col = b->curs_col + direction; count; col += direction) { 1261 if (col < 0) { 1262 b->curs_col = 0; 1263 break; 1264 } 1265 if (col >= b->cols) { 1266 b->curs_col = b->cols - 1; 1267 break; 1268 } 1269 if (b->tabs[col]) { 1270 b->curs_col = col; 1271 count -= direction; 1272 } 1273 } 1274 } 1275 1276 static void process_nonprinting(Vt *t, wchar_t wc) 1277 { 1278 Buffer *b = t->buffer; 1279 switch (wc) { 1280 case '\e': /* ESC */ 1281 new_escape_sequence(t); 1282 break; 1283 case '\a': /* BEL */ 1284 if (t->urgent_handler) 1285 t->urgent_handler(t); 1286 break; 1287 case '\b': /* BS */ 1288 if (b->curs_col > 0) 1289 b->curs_col--; 1290 break; 1291 case '\t': /* HT */ 1292 puttab(t, 1); 1293 break; 1294 case '\r': /* CR */ 1295 b->curs_col = 0; 1296 break; 1297 case '\v': /* VT */ 1298 case '\f': /* FF */ 1299 case '\n': /* LF */ 1300 cursor_line_down(t); 1301 break; 1302 case '\016': /* SO: shift out, invoke the G1 character set */ 1303 t->graphmode = t->charsets[1]; 1304 break; 1305 case '\017': /* SI: shift in, invoke the G0 character set */ 1306 t->graphmode = t->charsets[0]; 1307 break; 1308 } 1309 } 1310 1311 static void is_utf8_locale(void) 1312 { 1313 const char *cset = nl_langinfo(CODESET); 1314 if (!cset) 1315 cset = "ANSI_X3.4-1968"; 1316 is_utf8 = !strcmp(cset, "UTF-8"); 1317 } 1318 1319 static wchar_t get_vt100_graphic(char c) 1320 { 1321 static char vt100_acs[] = "`afgjklmnopqrstuvwxyz{|}~"; 1322 1323 /* 1324 * 5f-7e standard vt100 1325 * 40-5e rxvt extension for extra curses acs chars 1326 */ 1327 static uint16_t const vt100_utf8[62] = { 1328 0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259a, 0x2603, // 41-47 1329 0, 0, 0, 0, 0, 0, 0, 0, // 48-4f 1330 0, 0, 0, 0, 0, 0, 0, 0, // 50-57 1331 0, 0, 0, 0, 0, 0, 0, 0x0020, // 58-5f 1332 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, // 60-67 1333 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, // 68-6f 1334 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, // 70-77 1335 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, // 78-7e 1336 }; 1337 1338 if (is_utf8) 1339 return vt100_utf8[c - 0x41]; 1340 else if (strchr(vt100_acs, c)) 1341 return NCURSES_ACS(c); 1342 return '\0'; 1343 } 1344 1345 static void put_wc(Vt *t, wchar_t wc) 1346 { 1347 int width = 0; 1348 1349 if (!t->seen_input) { 1350 t->seen_input = 1; 1351 kill(-t->pid, SIGWINCH); 1352 } 1353 1354 if (t->escaped) { 1355 if (t->elen + 1 < sizeof(t->ebuf)) { 1356 t->ebuf[t->elen] = wc; 1357 t->ebuf[++t->elen] = '\0'; 1358 try_interpret_escape_seq(t); 1359 } else { 1360 cancel_escape_sequence(t); 1361 } 1362 } else if (IS_CONTROL(wc)) { 1363 process_nonprinting(t, wc); 1364 } else { 1365 if (t->graphmode) { 1366 if (wc >= 0x41 && wc <= 0x7e) { 1367 wchar_t gc = get_vt100_graphic(wc); 1368 if (gc) 1369 wc = gc; 1370 } 1371 width = 1; 1372 } else if ((width = wcwidth(wc)) < 1) { 1373 width = 1; 1374 } 1375 Buffer *b = t->buffer; 1376 Cell blank_cell = { L'\0', build_attrs(b->curattrs), b->curfg, b->curbg }; 1377 if (width == 2 && b->curs_col == b->cols - 1) { 1378 b->curs_row->cells[b->curs_col++] = blank_cell; 1379 b->curs_row->dirty = true; 1380 } 1381 1382 if (b->curs_col >= b->cols) { 1383 b->curs_col = 0; 1384 cursor_line_down(t); 1385 } 1386 1387 if (t->insert) { 1388 Cell *src = b->curs_row->cells + b->curs_col; 1389 Cell *dest = src + width; 1390 size_t len = b->cols - b->curs_col - width; 1391 memmove(dest, src, len * sizeof *dest); 1392 } 1393 1394 b->curs_row->cells[b->curs_col] = blank_cell; 1395 b->curs_row->cells[b->curs_col++].text = wc; 1396 b->curs_row->dirty = true; 1397 if (width == 2) 1398 b->curs_row->cells[b->curs_col++] = blank_cell; 1399 } 1400 } 1401 1402 int vt_process(Vt *t) 1403 { 1404 int res; 1405 unsigned int pos = 0; 1406 mbstate_t ps; 1407 memset(&ps, 0, sizeof(ps)); 1408 1409 if (t->pty < 0) { 1410 errno = EINVAL; 1411 return -1; 1412 } 1413 1414 res = read(t->pty, t->rbuf + t->rlen, sizeof(t->rbuf) - t->rlen); 1415 if (res < 0) 1416 return -1; 1417 1418 t->rlen += res; 1419 while (pos < t->rlen) { 1420 wchar_t wc; 1421 ssize_t len; 1422 1423 len = (ssize_t)mbrtowc(&wc, t->rbuf + pos, t->rlen - pos, &ps); 1424 if (len == -2) { 1425 t->rlen -= pos; 1426 memmove(t->rbuf, t->rbuf + pos, t->rlen); 1427 return 0; 1428 } 1429 1430 if (len == -1) { 1431 len = 1; 1432 wc = t->rbuf[pos]; 1433 } 1434 1435 pos += len ? len : 1; 1436 put_wc(t, wc); 1437 } 1438 1439 t->rlen -= pos; 1440 memmove(t->rbuf, t->rbuf + pos, t->rlen); 1441 return 0; 1442 } 1443 1444 void vt_default_colors_set(Vt *t, attr_t attrs, short fg, short bg) 1445 { 1446 t->defattrs = attrs; 1447 t->deffg = fg; 1448 t->defbg = bg; 1449 } 1450 1451 Vt *vt_create(int rows, int cols, int scroll_size) 1452 { 1453 if (rows <= 0 || cols <= 0) 1454 return NULL; 1455 1456 Vt *t = calloc(1, sizeof(Vt)); 1457 if (!t) 1458 return NULL; 1459 1460 t->pty = -1; 1461 t->deffg = t->defbg = -1; 1462 t->buffer = &t->buffer_normal; 1463 1464 if (!buffer_init(&t->buffer_normal, rows, cols, scroll_size) || 1465 !buffer_init(&t->buffer_alternate, rows, cols, 0)) { 1466 free(t); 1467 return NULL; 1468 } 1469 1470 return t; 1471 } 1472 1473 void vt_resize(Vt *t, int rows, int cols) 1474 { 1475 struct winsize ws = { .ws_row = rows, .ws_col = cols }; 1476 1477 if (rows <= 0 || cols <= 0) 1478 return; 1479 1480 vt_noscroll(t); 1481 buffer_resize(&t->buffer_normal, rows, cols); 1482 buffer_resize(&t->buffer_alternate, rows, cols); 1483 cursor_clamp(t); 1484 ioctl(t->pty, TIOCSWINSZ, &ws); 1485 kill(-t->pid, SIGWINCH); 1486 } 1487 1488 void vt_destroy(Vt *t) 1489 { 1490 if (!t) 1491 return; 1492 buffer_free(&t->buffer_normal); 1493 buffer_free(&t->buffer_alternate); 1494 close(t->pty); 1495 free(t); 1496 } 1497 1498 void vt_dirty(Vt *t) 1499 { 1500 Buffer *b = t->buffer; 1501 for (Row *row = b->lines, *end = row + b->rows; row < end; row++) 1502 row->dirty = true; 1503 } 1504 1505 void vt_draw(Vt *t, WINDOW *win, int srow, int scol) 1506 { 1507 Buffer *b = t->buffer; 1508 1509 if (srow != t->srow || scol != t->scol) { 1510 vt_dirty(t); 1511 t->srow = srow; 1512 t->scol = scol; 1513 } 1514 1515 for (int i = 0; i < b->rows; i++) { 1516 Row *row = b->lines + i; 1517 1518 if (!row->dirty) 1519 continue; 1520 1521 wmove(win, srow + i, scol); 1522 Cell *cell = NULL; 1523 for (int j = 0; j < b->cols; j++) { 1524 Cell *prev_cell = cell; 1525 cell = row->cells + j; 1526 if (!prev_cell || cell->attr != prev_cell->attr 1527 || cell->fg != prev_cell->fg 1528 || cell->bg != prev_cell->bg) { 1529 if (cell->attr == A_NORMAL) 1530 cell->attr = t->defattrs; 1531 if (cell->fg == -1) 1532 cell->fg = t->deffg; 1533 if (cell->bg == -1) 1534 cell->bg = t->defbg; 1535 wattrset(win, cell->attr << NCURSES_ATTR_SHIFT); 1536 wcolor_set(win, vt_color_get(t, cell->fg, cell->bg), NULL); 1537 } 1538 1539 if (is_utf8 && cell->text >= 128) { 1540 char buf[MB_CUR_MAX + 1]; 1541 size_t len = wcrtomb(buf, cell->text, NULL); 1542 if (len > 0) { 1543 waddnstr(win, buf, len); 1544 if (wcwidth(cell->text) > 1) 1545 j++; 1546 } 1547 } else { 1548 waddch(win, cell->text > ' ' ? cell->text : ' '); 1549 } 1550 } 1551 1552 int x, y; 1553 getyx(win, y, x); 1554 (void)y; 1555 if (x && x < b->cols - 1) 1556 whline(win, ' ', b->cols - x); 1557 1558 row->dirty = false; 1559 } 1560 1561 wmove(win, srow + b->curs_row - b->lines, scol + b->curs_col); 1562 } 1563 1564 void vt_scroll(Vt *t, int rows) 1565 { 1566 Buffer *b = t->buffer; 1567 if (!b->scroll_size) 1568 return; 1569 if (rows < 0) { /* scroll back */ 1570 if (rows < -b->scroll_above) 1571 rows = -b->scroll_above; 1572 } else { /* scroll forward */ 1573 if (rows > b->scroll_below) 1574 rows = b->scroll_below; 1575 } 1576 buffer_scroll(b, rows); 1577 b->scroll_below -= rows; 1578 } 1579 1580 void vt_noscroll(Vt *t) 1581 { 1582 int scroll_below = t->buffer->scroll_below; 1583 if (scroll_below) 1584 vt_scroll(t, scroll_below); 1585 } 1586 1587 pid_t vt_forkpty(Vt *t, const char *p, const char *argv[], const char *cwd, const char *env[], int *to, int *from) 1588 { 1589 int vt2ed[2], ed2vt[2]; 1590 struct winsize ws; 1591 ws.ws_row = t->buffer->rows; 1592 ws.ws_col = t->buffer->cols; 1593 ws.ws_xpixel = ws.ws_ypixel = 0; 1594 1595 if (to && pipe(vt2ed)) { 1596 *to = -1; 1597 to = NULL; 1598 } 1599 if (from && pipe(ed2vt)) { 1600 *from = -1; 1601 from = NULL; 1602 } 1603 1604 pid_t pid = forkpty(&t->pty, NULL, NULL, &ws); 1605 if (pid < 0) 1606 return -1; 1607 1608 if (pid == 0) { 1609 setsid(); 1610 1611 sigset_t emptyset; 1612 sigemptyset(&emptyset); 1613 sigprocmask(SIG_SETMASK, &emptyset, NULL); 1614 1615 if (to) { 1616 close(vt2ed[1]); 1617 dup2(vt2ed[0], STDIN_FILENO); 1618 close(vt2ed[0]); 1619 } 1620 1621 if (from) { 1622 close(ed2vt[0]); 1623 dup2(ed2vt[1], STDOUT_FILENO); 1624 close(ed2vt[1]); 1625 } 1626 1627 int maxfd = sysconf(_SC_OPEN_MAX); 1628 for (int fd = 3; fd < maxfd; fd++) 1629 if (close(fd) == -1 && errno == EBADF) 1630 break; 1631 1632 for (const char **envp = env; envp && envp[0]; envp += 2) 1633 setenv(envp[0], envp[1], 1); 1634 setenv("TERM", vt_term, 1); 1635 1636 if (cwd) { 1637 int err = chdir(cwd); 1638 if (err) { 1639 fprintf(stderr, "\nchdir() failed. "); 1640 perror(cwd); 1641 exit(1); 1642 } 1643 } 1644 1645 struct sigaction sa; 1646 memset(&sa, 0, sizeof sa); 1647 sa.sa_flags = 0; 1648 sigemptyset(&sa.sa_mask); 1649 sa.sa_handler = SIG_DFL; 1650 sigaction(SIGPIPE, &sa, NULL); 1651 1652 execvp(p, (char *const *)argv); 1653 fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]); 1654 exit(1); 1655 } 1656 1657 if (to) { 1658 close(vt2ed[0]); 1659 *to = vt2ed[1]; 1660 } 1661 1662 if (from) { 1663 close(ed2vt[1]); 1664 *from = ed2vt[0]; 1665 } 1666 1667 return t->pid = pid; 1668 } 1669 1670 int vt_pty_get(Vt *t) 1671 { 1672 return t->pty; 1673 } 1674 1675 ssize_t vt_write(Vt *t, const char *buf, size_t len) 1676 { 1677 ssize_t ret = len; 1678 1679 while (len > 0) { 1680 ssize_t res = write(t->pty, buf, len); 1681 if (res < 0) { 1682 if (errno != EAGAIN && errno != EINTR) 1683 return -1; 1684 continue; 1685 } 1686 buf += res; 1687 len -= res; 1688 } 1689 1690 return ret; 1691 } 1692 1693 static void send_curs(Vt *t) 1694 { 1695 Buffer *b = t->buffer; 1696 char keyseq[16]; 1697 snprintf(keyseq, sizeof keyseq, "\e[%d;%dR", (int)(b->curs_row - b->lines), b->curs_col); 1698 vt_write(t, keyseq, strlen(keyseq)); 1699 } 1700 1701 void vt_keypress(Vt *t, int keycode) 1702 { 1703 vt_noscroll(t); 1704 1705 if (keycode >= 0 && keycode <= KEY_MAX && keytable[keycode]) { 1706 switch (keycode) { 1707 case KEY_UP: 1708 case KEY_DOWN: 1709 case KEY_RIGHT: 1710 case KEY_LEFT: { 1711 char keyseq[3] = { '\e', (t->curskeymode ? 'O' : '['), keytable[keycode][0] }; 1712 vt_write(t, keyseq, sizeof keyseq); 1713 break; 1714 } 1715 default: 1716 vt_write(t, keytable[keycode], strlen(keytable[keycode])); 1717 } 1718 } else if (keycode <= UCHAR_MAX) { 1719 char c = keycode; 1720 vt_write(t, &c, 1); 1721 } else { 1722 #ifndef NDEBUG 1723 fprintf(stderr, "unhandled key %#o\n", keycode); 1724 #endif 1725 } 1726 } 1727 1728 void vt_mouse(Vt *t, int x, int y, mmask_t mask) 1729 { 1730 #ifdef NCURSES_MOUSE_VERSION 1731 char seq[6] = { '\e', '[', 'M' }, state = 0, button = 0; 1732 1733 if (!t->mousetrack) 1734 return; 1735 1736 if (mask & (BUTTON1_PRESSED | BUTTON1_CLICKED)) 1737 button = 0; 1738 else if (mask & (BUTTON2_PRESSED | BUTTON2_CLICKED)) 1739 button = 1; 1740 else if (mask & (BUTTON3_PRESSED | BUTTON3_CLICKED)) 1741 button = 2; 1742 else if (mask & (BUTTON1_RELEASED | BUTTON2_RELEASED | BUTTON3_RELEASED)) 1743 button = 3; 1744 1745 if (mask & BUTTON_SHIFT) 1746 state |= 4; 1747 if (mask & BUTTON_ALT) 1748 state |= 8; 1749 if (mask & BUTTON_CTRL) 1750 state |= 16; 1751 1752 seq[3] = 32 + button + state; 1753 seq[4] = 32 + x; 1754 seq[5] = 32 + y; 1755 1756 vt_write(t, seq, sizeof seq); 1757 1758 if (mask & (BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED)) { 1759 /* send a button release event */ 1760 button = 3; 1761 seq[3] = 32 + button + state; 1762 vt_write(t, seq, sizeof seq); 1763 } 1764 #endif /* NCURSES_MOUSE_VERSION */ 1765 } 1766 1767 static unsigned int color_hash(short fg, short bg) 1768 { 1769 if (fg == -1) 1770 fg = COLORS; 1771 if (bg == -1) 1772 bg = COLORS + 1; 1773 return fg * (COLORS + 2) + bg; 1774 } 1775 1776 short vt_color_get(Vt *t, short fg, short bg) 1777 { 1778 if (fg >= COLORS) 1779 fg = (t ? t->deffg : default_fg); 1780 if (bg >= COLORS) 1781 bg = (t ? t->defbg : default_bg); 1782 1783 if (!has_default_colors) { 1784 if (fg == -1) 1785 fg = (t && t->deffg != -1 ? t->deffg : default_fg); 1786 if (bg == -1) 1787 bg = (t && t->defbg != -1 ? t->defbg : default_bg); 1788 } 1789 1790 if (!color2palette || (fg == -1 && bg == -1)) 1791 return 0; 1792 unsigned int index = color_hash(fg, bg); 1793 if (color2palette[index] == 0) { 1794 short oldfg, oldbg; 1795 for (;;) { 1796 if (++color_pair_current >= color_pairs_max) 1797 color_pair_current = color_pairs_reserved + 1; 1798 pair_content(color_pair_current, &oldfg, &oldbg); 1799 unsigned int old_index = color_hash(oldfg, oldbg); 1800 if (color2palette[old_index] >= 0) { 1801 if (init_pair(color_pair_current, fg, bg) == OK) { 1802 color2palette[old_index] = 0; 1803 color2palette[index] = color_pair_current; 1804 } 1805 break; 1806 } 1807 } 1808 } 1809 1810 short color_pair = color2palette[index]; 1811 return color_pair >= 0 ? color_pair : -color_pair; 1812 } 1813 1814 short vt_color_reserve(short fg, short bg) 1815 { 1816 if (!color2palette || fg >= COLORS || bg >= COLORS) 1817 return 0; 1818 if (!has_default_colors && fg == -1) 1819 fg = default_fg; 1820 if (!has_default_colors && bg == -1) 1821 bg = default_bg; 1822 if (fg == -1 && bg == -1) 1823 return 0; 1824 unsigned int index = color_hash(fg, bg); 1825 if (color2palette[index] >= 0) { 1826 if (init_pair(color_pairs_reserved + 1, fg, bg) == OK) 1827 color2palette[index] = -(++color_pairs_reserved); 1828 } 1829 short color_pair = color2palette[index]; 1830 return color_pair >= 0 ? color_pair : -color_pair; 1831 } 1832 1833 static void init_colors(void) 1834 { 1835 pair_content(0, &default_fg, &default_bg); 1836 if (default_fg == -1) 1837 default_fg = COLOR_WHITE; 1838 if (default_bg == -1) 1839 default_bg = COLOR_BLACK; 1840 has_default_colors = (use_default_colors() == OK); 1841 color_pairs_max = MIN(MAX_COLOR_PAIRS, SHRT_MAX); 1842 if (COLORS) 1843 color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short)); 1844 /* 1845 * XXX: On undefined color-pairs NetBSD curses pair_content() set fg 1846 * and bg to default colors while ncurses set them respectively to 1847 * 0 and 0. Initialize all color-pairs in order to have consistent 1848 * behaviour despite the implementation used. 1849 */ 1850 for (short i = 1; i < color_pairs_max; i++) 1851 init_pair(i, 0, 0); 1852 vt_color_reserve(COLOR_WHITE, COLOR_BLACK); 1853 } 1854 1855 void vt_init(void) 1856 { 1857 init_colors(); 1858 is_utf8_locale(); 1859 char *term = getenv("DVTM_TERM"); 1860 if (!term) 1861 term = "dvtm"; 1862 snprintf(vt_term, sizeof vt_term, "%s%s", term, COLORS >= 256 ? "-256color" : ""); 1863 } 1864 1865 void vt_keytable_set(const char * const keytable_overlay[], int count) 1866 { 1867 for (int k = 0; k < count && k < KEY_MAX; k++) { 1868 const char *keyseq = keytable_overlay[k]; 1869 if (keyseq) 1870 keytable[k] = keyseq; 1871 } 1872 } 1873 1874 void vt_shutdown(void) 1875 { 1876 free(color2palette); 1877 } 1878 1879 void vt_title_handler_set(Vt *t, vt_title_handler_t handler) 1880 { 1881 t->title_handler = handler; 1882 } 1883 1884 void vt_urgent_handler_set(Vt *t, vt_urgent_handler_t handler) 1885 { 1886 t->urgent_handler = handler; 1887 } 1888 1889 void vt_data_set(Vt *t, void *data) 1890 { 1891 t->data = data; 1892 } 1893 1894 void *vt_data_get(Vt *t) 1895 { 1896 return t->data; 1897 } 1898 1899 bool vt_cursor_visible(Vt *t) 1900 { 1901 return t->buffer->scroll_below ? false : !t->curshid; 1902 } 1903 1904 pid_t vt_pid_get(Vt *t) 1905 { 1906 return t->pid; 1907 } 1908 1909 size_t vt_content_get(Vt *t, char **buf, bool colored) 1910 { 1911 Buffer *b = t->buffer; 1912 int lines = b->scroll_above + b->scroll_below + b->rows + 1; 1913 size_t size = lines * ((b->cols + 1) * ((colored ? 64 : 0) + MB_CUR_MAX)); 1914 mbstate_t ps; 1915 memset(&ps, 0, sizeof(ps)); 1916 1917 if (!(*buf = malloc(size))) 1918 return 0; 1919 1920 char *s = *buf; 1921 Cell *prev_cell = NULL; 1922 1923 for (Row *row = buffer_row_first(b); row; row = buffer_row_next(b, row)) { 1924 size_t len = 0; 1925 char *last_non_space = s; 1926 for (int col = 0; col < b->cols; col++) { 1927 Cell *cell = row->cells + col; 1928 if (colored) { 1929 int esclen = 0; 1930 if (!prev_cell || cell->attr != prev_cell->attr) { 1931 attr_t attr = cell->attr << NCURSES_ATTR_SHIFT; 1932 esclen = sprintf(s, "\033[0%s%s%s%s%s%sm", 1933 attr & A_BOLD ? ";1" : "", 1934 attr & A_DIM ? ";2" : "", 1935 attr & A_UNDERLINE ? ";4" : "", 1936 attr & A_BLINK ? ";5" : "", 1937 attr & A_REVERSE ? ";7" : "", 1938 attr & A_INVIS ? ";8" : ""); 1939 if (esclen > 0) 1940 s += esclen; 1941 } 1942 if (!prev_cell || cell->fg != prev_cell->fg || cell->attr != prev_cell->attr) { 1943 if (cell->fg == -1) 1944 esclen = sprintf(s, "\033[39m"); 1945 else 1946 esclen = sprintf(s, "\033[38;5;%dm", cell->fg); 1947 if (esclen > 0) 1948 s += esclen; 1949 } 1950 if (!prev_cell || cell->bg != prev_cell->bg || cell->attr != prev_cell->attr) { 1951 if (cell->bg == -1) 1952 esclen = sprintf(s, "\033[49m"); 1953 else 1954 esclen = sprintf(s, "\033[48;5;%dm", cell->bg); 1955 if (esclen > 0) 1956 s += esclen; 1957 } 1958 prev_cell = cell; 1959 } 1960 if (cell->text) { 1961 len = wcrtomb(s, cell->text, &ps); 1962 if (len > 0) 1963 s += len; 1964 last_non_space = s; 1965 } else if (len) { 1966 len = 0; 1967 } else { 1968 *s++ = ' '; 1969 } 1970 } 1971 1972 s = last_non_space; 1973 *s++ = '\n'; 1974 } 1975 1976 return s - *buf; 1977 } 1978 1979 int vt_content_start(Vt *t) 1980 { 1981 return t->buffer->scroll_above; 1982 } 1983 1984 int vt_content_end(Vt *t) 1985 { 1986 int curs_row = t->buffer->curs_row - t->buffer->lines; 1987 return vt_content_start(t) + curs_row; 1988 }