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