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