1#include "vterm_internal.h" 2 3#include <stdio.h> 4#include <string.h> 5 6#include "rect.h" 7#include "utf8.h" 8 9#define UNICODE_SPACE 0x20 10#define UNICODE_LINEFEED 0x0a 11 12/* State of the pen at some moment in time, also used in a cell */ 13typedef struct 14{ 15 /* After the bitfield */ 16 VTermColor fg, bg; 17 18 unsigned int bold : 1; 19 unsigned int underline : 2; 20 unsigned int italic : 1; 21 unsigned int blink : 1; 22 unsigned int reverse : 1; 23 unsigned int strike : 1; 24 unsigned int font : 4; /* 0 to 9 */ 25 26 /* Extra state storage that isn't strictly pen-related */ 27 unsigned int protected_cell : 1; 28} ScreenPen; 29 30/* Internal representation of a screen cell */ 31typedef struct 32{ 33 uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; 34 ScreenPen pen; 35} ScreenCell; 36 37struct VTermScreen 38{ 39 VTerm *vt; 40 VTermState *state; 41 42 const VTermScreenCallbacks *callbacks; 43 void *cbdata; 44 45 VTermDamageSize damage_merge; 46 /* start_row == -1 => no damage */ 47 VTermRect damaged; 48 VTermRect pending_scrollrect; 49 int pending_scroll_downward, pending_scroll_rightward; 50 51 int rows; 52 int cols; 53 int global_reverse; 54 55 /* Primary and Altscreen. buffers[1] is lazily allocated as needed */ 56 ScreenCell *buffers[2]; 57 58 /* buffer will == buffers[0] or buffers[1], depending on altscreen */ 59 ScreenCell *buffer; 60 61 ScreenPen pen; 62}; 63 64static inline ScreenCell *getcell(VTermScreen *screen, int row, int col) 65{ 66 if(row < 0 || row >= screen->rows) 67 return NULL; 68 if(col < 0 || col >= screen->cols) 69 return NULL; 70 return screen->buffer + (screen->cols * row) + col; 71} 72 73static ScreenCell *realloc_buffer(VTermScreen *screen, ScreenCell *buffer, int new_rows, int new_cols) 74{ 75 ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols); 76 77 for(int row = 0; row < new_rows; row++) { 78 for(int col = 0; col < new_cols; col++) { 79 ScreenCell *new_cell = new_buffer + row*new_cols + col; 80 81 if(buffer && row < screen->rows && col < screen->cols) 82 *new_cell = buffer[row * screen->cols + col]; 83 else { 84 new_cell->chars[0] = 0; 85 new_cell->pen = screen->pen; 86 } 87 } 88 } 89 90 if(buffer) 91 vterm_allocator_free(screen->vt, buffer); 92 93 return new_buffer; 94} 95 96static void damagerect(VTermScreen *screen, VTermRect rect) 97{ 98 VTermRect emit; 99 100 switch(screen->damage_merge) { 101 case VTERM_DAMAGE_CELL: 102 /* Always emit damage event */ 103 emit = rect; 104 break; 105 106 case VTERM_DAMAGE_ROW: 107 /* Emit damage longer than one row. Try to merge with existing damage in 108 * the same row */ 109 if(rect.end_row > rect.start_row + 1) { 110 // Bigger than 1 line - flush existing, emit this 111 vterm_screen_flush_damage(screen); 112 emit = rect; 113 } 114 else if(screen->damaged.start_row == -1) { 115 // None stored yet 116 screen->damaged = rect; 117 return; 118 } 119 else if(rect.start_row == screen->damaged.start_row) { 120 // Merge with the stored line 121 if(screen->damaged.start_col > rect.start_col) 122 screen->damaged.start_col = rect.start_col; 123 if(screen->damaged.end_col < rect.end_col) 124 screen->damaged.end_col = rect.end_col; 125 return; 126 } 127 else { 128 // Emit the currently stored line, store a new one 129 emit = screen->damaged; 130 screen->damaged = rect; 131 } 132 break; 133 134 case VTERM_DAMAGE_SCREEN: 135 case VTERM_DAMAGE_SCROLL: 136 /* Never emit damage event */ 137 if(screen->damaged.start_row == -1) 138 screen->damaged = rect; 139 else { 140 rect_expand(&screen->damaged, &rect); 141 } 142 return; 143 144 default: 145 fprintf(stderr, "TODO: Maybe merge damage for level %d\n", screen->damage_merge); 146 return; 147 } 148 149 if(screen->callbacks && screen->callbacks->damage) 150 (*screen->callbacks->damage)(emit, screen->cbdata); 151} 152 153static void damagescreen(VTermScreen *screen) 154{ 155 VTermRect rect = { 156 .start_row = 0, 157 .end_row = screen->rows, 158 .start_col = 0, 159 .end_col = screen->cols, 160 }; 161 162 damagerect(screen, rect); 163} 164 165static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) 166{ 167 VTermScreen *screen = user; 168 ScreenCell *cell = getcell(screen, pos.row, pos.col); 169 170 if(!cell) 171 return 0; 172 173 int i; 174 for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) { 175 cell->chars[i] = info->chars[i]; 176 cell->pen = screen->pen; 177 } 178 if(i < VTERM_MAX_CHARS_PER_CELL) 179 cell->chars[i] = 0; 180 181 for(int col = 1; col < info->width; col++) 182 getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1; 183 184 VTermRect rect = { 185 .start_row = pos.row, 186 .end_row = pos.row+1, 187 .start_col = pos.col, 188 .end_col = pos.col+info->width, 189 }; 190 191 cell->pen.protected_cell = info->protected_cell; 192 193 damagerect(screen, rect); 194 195 return 1; 196} 197 198static int moverect_internal(VTermRect dest, VTermRect src, void *user) 199{ 200 VTermScreen *screen = user; 201 202 if(screen->callbacks && screen->callbacks->prescroll) { 203 // TODO: These calculations don't properly take account of combined 204 // horizontal and vertical movements 205 if(dest.start_row < src.start_row) { 206 VTermRect rect = { 207 .start_row = dest.start_row, 208 .end_row = src.start_row, 209 .start_col = dest.start_col, 210 .end_col = dest.end_col, 211 }; 212 (*screen->callbacks->prescroll)(rect, screen->cbdata); 213 } 214 else if(dest.start_row > src.start_row) { 215 VTermRect rect = { 216 .start_row = src.end_row, 217 .end_row = dest.end_row, 218 .start_col = dest.start_col, 219 .end_col = dest.end_col, 220 }; 221 (*screen->callbacks->prescroll)(rect, screen->cbdata); 222 } 223 224 if(dest.start_col < src.start_col) { 225 VTermRect rect = { 226 .start_row = dest.start_row, 227 .end_row = dest.end_row, 228 .start_col = dest.start_col, 229 .end_col = src.start_col, 230 }; 231 (*screen->callbacks->prescroll)(rect, screen->cbdata); 232 } 233 else if(dest.start_col > src.start_col) { 234 VTermRect rect = { 235 .start_row = dest.start_row, 236 .end_row = dest.end_row, 237 .start_col = src.end_col, 238 .end_col = dest.end_col, 239 }; 240 (*screen->callbacks->prescroll)(rect, screen->cbdata); 241 } 242 } 243 244 int cols = src.end_col - src.start_col; 245 int downward = src.start_row - dest.start_row; 246 247 int init_row, test_row, inc_row; 248 if(downward < 0) { 249 init_row = dest.end_row - 1; 250 test_row = dest.start_row - 1; 251 inc_row = -1; 252 } 253 else { 254 init_row = dest.start_row; 255 test_row = dest.end_row; 256 inc_row = +1; 257 } 258 259 for(int row = init_row; row != test_row; row += inc_row) 260 memmove(getcell(screen, row, dest.start_col), 261 getcell(screen, row + downward, src.start_col), 262 cols * sizeof(ScreenCell)); 263 264 return 1; 265} 266 267static int moverect_user(VTermRect dest, VTermRect src, void *user) 268{ 269 VTermScreen *screen = user; 270 271 if(screen->callbacks && screen->callbacks->moverect) { 272 if(screen->damage_merge != VTERM_DAMAGE_SCROLL) 273 // Avoid an infinite loop 274 vterm_screen_flush_damage(screen); 275 276 if((*screen->callbacks->moverect)(dest, src, screen->cbdata)) 277 return 1; 278 } 279 280 damagerect(screen, dest); 281 282 return 1; 283} 284 285static int erase_internal(VTermRect rect, int selective, void *user) 286{ 287 VTermScreen *screen = user; 288 289 for(int row = rect.start_row; row < rect.end_row; row++) 290 for(int col = rect.start_col; col < rect.end_col; col++) { 291 ScreenCell *cell = getcell(screen, row, col); 292 293 if(selective && cell->pen.protected_cell) 294 continue; 295 296 cell->chars[0] = 0; 297 cell->pen = screen->pen; 298 } 299 300 return 1; 301} 302 303static int erase_user(VTermRect rect, int selective, void *user) 304{ 305 VTermScreen *screen = user; 306 307 damagerect(screen, rect); 308 309 return 1; 310} 311 312static int erase(VTermRect rect, int selective, void *user) 313{ 314 erase_internal(rect, selective, user); 315 return erase_user(rect, 0, user); 316} 317 318static int scrollrect(VTermRect rect, int downward, int rightward, void *user) 319{ 320 VTermScreen *screen = user; 321 322 vterm_scroll_rect(rect, downward, rightward, 323 moverect_internal, erase_internal, screen); 324 325 if(screen->damage_merge != VTERM_DAMAGE_SCROLL) { 326 vterm_screen_flush_damage(screen); 327 328 vterm_scroll_rect(rect, downward, rightward, 329 moverect_user, erase_user, screen); 330 331 return 1; 332 } 333 334 if(screen->damaged.start_row != -1 && 335 !rect_intersects(&rect, &screen->damaged)) { 336 vterm_screen_flush_damage(screen); 337 } 338 339 if(screen->pending_scrollrect.start_row == -1) { 340 screen->pending_scrollrect = rect; 341 screen->pending_scroll_downward = downward; 342 screen->pending_scroll_rightward = rightward; 343 } 344 else if(rect_equal(&screen->pending_scrollrect, &rect) && 345 ((screen->pending_scroll_downward == 0 && downward == 0) || 346 (screen->pending_scroll_rightward == 0 && rightward == 0))) { 347 screen->pending_scroll_downward += downward; 348 screen->pending_scroll_rightward += rightward; 349 } 350 else { 351 vterm_screen_flush_damage(screen); 352 353 screen->pending_scrollrect = rect; 354 screen->pending_scroll_downward = downward; 355 screen->pending_scroll_rightward = rightward; 356 } 357 358 if(screen->damaged.start_row == -1) 359 return 1; 360 361 if(rect_contains(&rect, &screen->damaged)) { 362 vterm_rect_move(&screen->damaged, -downward, -rightward); 363 rect_clip(&screen->damaged, &rect); 364 } 365 /* There are a number of possible cases here, but lets restrict this to only 366 * the common case where we might actually gain some performance by 367 * optimising it. Namely, a vertical scroll that neatly cuts the damage 368 * region in half. 369 */ 370 else if(rect.start_col <= screen->damaged.start_col && 371 rect.end_col >= screen->damaged.end_col && 372 rightward == 0) { 373 if(screen->damaged.start_row >= rect.start_row && 374 screen->damaged.start_row < rect.end_row) { 375 screen->damaged.start_row -= downward; 376 if(screen->damaged.start_row < rect.start_row) 377 screen->damaged.start_row = rect.start_row; 378 if(screen->damaged.start_row > rect.end_row) 379 screen->damaged.start_row = rect.end_row; 380 } 381 if(screen->damaged.end_row >= rect.start_row && 382 screen->damaged.end_row < rect.end_row) { 383 screen->damaged.end_row -= downward; 384 if(screen->damaged.end_row < rect.start_row) 385 screen->damaged.end_row = rect.start_row; 386 if(screen->damaged.end_row > rect.end_row) 387 screen->damaged.end_row = rect.end_row; 388 } 389 } 390 else { 391 fprintf(stderr, "TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n", 392 ARGSrect(screen->damaged), ARGSrect(rect)); 393 } 394 395 return 1; 396} 397 398static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) 399{ 400 VTermScreen *screen = user; 401 402 if(screen->callbacks && screen->callbacks->movecursor) 403 return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata); 404 405 return 0; 406} 407 408static int setpenattr(VTermAttr attr, VTermValue *val, void *user) 409{ 410 VTermScreen *screen = user; 411 412 switch(attr) { 413 case VTERM_ATTR_BOLD: 414 screen->pen.bold = val->boolean; 415 return 1; 416 case VTERM_ATTR_UNDERLINE: 417 screen->pen.underline = val->number; 418 return 1; 419 case VTERM_ATTR_ITALIC: 420 screen->pen.italic = val->boolean; 421 return 1; 422 case VTERM_ATTR_BLINK: 423 screen->pen.blink = val->boolean; 424 return 1; 425 case VTERM_ATTR_REVERSE: 426 screen->pen.reverse = val->boolean; 427 return 1; 428 case VTERM_ATTR_STRIKE: 429 screen->pen.strike = val->boolean; 430 return 1; 431 case VTERM_ATTR_FONT: 432 screen->pen.font = val->number; 433 return 1; 434 case VTERM_ATTR_FOREGROUND: 435 screen->pen.fg = val->color; 436 return 1; 437 case VTERM_ATTR_BACKGROUND: 438 screen->pen.bg = val->color; 439 return 1; 440 } 441 442 return 0; 443} 444 445static int settermprop(VTermProp prop, VTermValue *val, void *user) 446{ 447 VTermScreen *screen = user; 448 449 switch(prop) { 450 case VTERM_PROP_ALTSCREEN: 451 if(val->boolean && !screen->buffers[1]) 452 return 0; 453 454 screen->buffer = val->boolean ? screen->buffers[1] : screen->buffers[0]; 455 /* only send a damage event on disable; because during enable there's an 456 * erase that sends a damage anyway 457 */ 458 if(!val->boolean) 459 damagescreen(screen); 460 break; 461 case VTERM_PROP_REVERSE: 462 screen->global_reverse = val->boolean; 463 damagescreen(screen); 464 break; 465 default: 466 ; /* ignore */ 467 } 468 469 if(screen->callbacks && screen->callbacks->settermprop) 470 return (*screen->callbacks->settermprop)(prop, val, screen->cbdata); 471 472 return 1; 473} 474 475static int setmousefunc(VTermMouseFunc func, void *data, void *user) 476{ 477 VTermScreen *screen = user; 478 479 if(screen->callbacks && screen->callbacks->setmousefunc) 480 return (*screen->callbacks->setmousefunc)(func, data, screen->cbdata); 481 482 return 0; 483} 484 485static int bell(void *user) 486{ 487 VTermScreen *screen = user; 488 489 if(screen->callbacks && screen->callbacks->bell) 490 return (*screen->callbacks->bell)(screen->cbdata); 491 492 return 0; 493} 494 495static int resize(int new_rows, int new_cols, void *user) 496{ 497 VTermScreen *screen = user; 498 499 int is_altscreen = (screen->buffers[1] && screen->buffer == screen->buffers[1]); 500 501 screen->buffers[0] = realloc_buffer(screen, screen->buffers[0], new_rows, new_cols); 502 if(screen->buffers[1]) 503 screen->buffers[1] = realloc_buffer(screen, screen->buffers[1], new_rows, new_cols); 504 505 screen->buffer = is_altscreen ? screen->buffers[1] : screen->buffers[0]; 506 507 int old_rows = screen->rows; 508 int old_cols = screen->cols; 509 510 screen->rows = new_rows; 511 screen->cols = new_cols; 512 513 if(new_cols > old_cols) { 514 VTermRect rect = { 515 .start_row = 0, 516 .end_row = old_rows, 517 .start_col = old_cols, 518 .end_col = new_cols, 519 }; 520 damagerect(screen, rect); 521 } 522 523 if(new_rows > old_rows) { 524 VTermRect rect = { 525 .start_row = old_rows, 526 .end_row = new_rows, 527 .start_col = 0, 528 .end_col = new_cols, 529 }; 530 damagerect(screen, rect); 531 } 532 533 if(screen->callbacks && screen->callbacks->resize) 534 return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata); 535 536 return 1; 537} 538 539static VTermStateCallbacks state_cbs = { 540 .putglyph = &putglyph, 541 .movecursor = &movecursor, 542 .scrollrect = &scrollrect, 543 .erase = &erase, 544 .setpenattr = &setpenattr, 545 .settermprop = &settermprop, 546 .setmousefunc = &setmousefunc, 547 .bell = &bell, 548 .resize = &resize, 549}; 550 551static VTermScreen *screen_new(VTerm *vt) 552{ 553 VTermState *state = vterm_obtain_state(vt); 554 if(!state) 555 return NULL; 556 557 VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen)); 558 int rows, cols; 559 560 vterm_get_size(vt, &rows, &cols); 561 562 screen->vt = vt; 563 screen->state = state; 564 565 screen->damage_merge = VTERM_DAMAGE_CELL; 566 screen->damaged.start_row = -1; 567 screen->pending_scrollrect.start_row = -1; 568 569 screen->rows = rows; 570 screen->cols = cols; 571 572 screen->buffers[0] = realloc_buffer(screen, NULL, rows, cols); 573 574 screen->buffer = screen->buffers[0]; 575 576 vterm_state_set_callbacks(screen->state, &state_cbs, screen); 577 578 return screen; 579} 580 581void vterm_screen_free(VTermScreen *screen) 582{ 583 vterm_allocator_free(screen->vt, screen->buffers[0]); 584 if(screen->buffers[1]) 585 vterm_allocator_free(screen->vt, screen->buffers[1]); 586 587 vterm_allocator_free(screen->vt, screen); 588} 589 590void vterm_screen_reset(VTermScreen *screen, int hard) 591{ 592 screen->damaged.start_row = -1; 593 screen->pending_scrollrect.start_row = -1; 594 vterm_state_reset(screen->state, hard); 595 vterm_screen_flush_damage(screen); 596} 597 598static size_t _get_chars(VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect) 599{ 600 size_t outpos = 0; 601 int padding = 0; 602 603#define PUT(c) \ 604 if(utf8) { \ 605 size_t thislen = utf8_seqlen(c); \ 606 if(buffer && outpos + thislen <= len) \ 607 outpos += fill_utf8((c), (char *)buffer + outpos); \ 608 else \ 609 outpos += thislen; \ 610 } \ 611 else { \ 612 if(buffer && outpos + 1 <= len) \ 613 ((uint32_t*)buffer)[outpos++] = (c); \ 614 else \ 615 outpos++; \ 616 } 617 618 for(int row = rect.start_row; row < rect.end_row; row++) { 619 for(int col = rect.start_col; col < rect.end_col; col++) { 620 ScreenCell *cell = getcell(screen, row, col); 621 622 if(cell->chars[0] == 0) 623 // Erased cell, might need a space 624 padding++; 625 else if(cell->chars[0] == (uint32_t)-1) 626 // Gap behind a double-width char, do nothing 627 ; 628 else { 629 while(padding) { 630 PUT(UNICODE_SPACE); 631 padding--; 632 } 633 for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) { 634 PUT(cell->chars[i]); 635 } 636 } 637 } 638 639 if(row < rect.end_row - 1) { 640 PUT(UNICODE_LINEFEED); 641 padding = 0; 642 } 643 } 644 645 return outpos; 646} 647 648size_t vterm_screen_get_chars(VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect) 649{ 650 return _get_chars(screen, 0, chars, len, rect); 651} 652 653size_t vterm_screen_get_text(VTermScreen *screen, char *str, size_t len, const VTermRect rect) 654{ 655 return _get_chars(screen, 1, str, len, rect); 656} 657 658/* Copy internal to external representation of a screen cell */ 659int vterm_screen_get_cell(VTermScreen *screen, VTermPos pos, VTermScreenCell *cell) 660{ 661 ScreenCell *intcell = getcell(screen, pos.row, pos.col); 662 if(!intcell) 663 return 0; 664 665 for(int i = 0; ; i++) { 666 cell->chars[i] = intcell->chars[i]; 667 if(!intcell->chars[i]) 668 break; 669 } 670 671 cell->attrs.bold = intcell->pen.bold; 672 cell->attrs.underline = intcell->pen.underline; 673 cell->attrs.italic = intcell->pen.italic; 674 cell->attrs.blink = intcell->pen.blink; 675 cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse; 676 cell->attrs.strike = intcell->pen.strike; 677 cell->attrs.font = intcell->pen.font; 678 679 cell->fg = intcell->pen.fg; 680 cell->bg = intcell->pen.bg; 681 682 if(pos.col < (screen->cols - 1) && 683 getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1) 684 cell->width = 2; 685 else 686 cell->width = 1; 687 688 return 1; 689} 690 691int vterm_screen_is_eol(VTermScreen *screen, VTermPos pos) 692{ 693 /* This cell is EOL if this and every cell to the right is black */ 694 for(; pos.col < screen->cols; pos.col++) { 695 ScreenCell *cell = getcell(screen, pos.row, pos.col); 696 if(cell->chars[0] != 0) 697 return 0; 698 } 699 700 return 1; 701} 702 703VTermScreen *vterm_obtain_screen(VTerm *vt) 704{ 705 if(vt->screen) 706 return vt->screen; 707 708 VTermScreen *screen = screen_new(vt); 709 vt->screen = screen; 710 711 return screen; 712} 713 714void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen) 715{ 716 717 if(!screen->buffers[1] && altscreen) { 718 int rows, cols; 719 vterm_get_size(screen->vt, &rows, &cols); 720 721 screen->buffers[1] = realloc_buffer(screen, NULL, rows, cols); 722 } 723} 724 725void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user) 726{ 727 screen->callbacks = callbacks; 728 screen->cbdata = user; 729} 730 731void vterm_screen_flush_damage(VTermScreen *screen) 732{ 733 if(screen->pending_scrollrect.start_row != -1) { 734 vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward, 735 moverect_user, erase_user, screen); 736 737 screen->pending_scrollrect.start_row = -1; 738 } 739 740 if(screen->damaged.start_row != -1) { 741 if(screen->callbacks && screen->callbacks->damage) 742 (*screen->callbacks->damage)(screen->damaged, screen->cbdata); 743 744 screen->damaged.start_row = -1; 745 } 746} 747 748void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size) 749{ 750 vterm_screen_flush_damage(screen); 751 screen->damage_merge = size; 752} 753