1 #include "vterm_internal.h" 2 3 #include <stdio.h> 4 #include <string.h> 5 6 #define strneq(a,b,n) (strncmp(a,b,n)==0) 7 8 #if defined(DEBUG) && DEBUG > 1 9 # define DEBUG_GLYPH_COMBINE 10 #endif 11 12 /* Some convenient wrappers to make callback functions easier */ 13 14 static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos) 15 { 16 VTermGlyphInfo info = { 17 .chars = chars, 18 .width = width, 19 .protected_cell = state->protected_cell, 20 .dwl = state->lineinfo[pos.row].doublewidth, 21 .dhl = state->lineinfo[pos.row].doubleheight, 22 }; 23 24 if(state->callbacks && state->callbacks->putglyph) 25 if((*state->callbacks->putglyph)(&info, pos, state->cbdata)) 26 return; 27 28 DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); 29 } 30 31 static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom) 32 { 33 if(state->pos.col == oldpos->col && state->pos.row == oldpos->row) 34 return; 35 36 if(cancel_phantom) 37 state->at_phantom = 0; 38 39 if(state->callbacks && state->callbacks->movecursor) 40 if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata)) 41 return; 42 } 43 44 static void erase(VTermState *state, VTermRect rect, int selective) 45 { 46 if(state->callbacks && state->callbacks->erase) 47 if((*state->callbacks->erase)(rect, selective, state->cbdata)) 48 return; 49 } 50 51 static VTermState *vterm_state_new(VTerm *vt) 52 { 53 VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState)); 54 55 state->vt = vt; 56 57 state->rows = vt->rows; 58 state->cols = vt->cols; 59 60 state->mouse_col = 0; 61 state->mouse_row = 0; 62 state->mouse_buttons = 0; 63 64 state->mouse_protocol = MOUSE_X10; 65 66 state->callbacks = NULL; 67 state->cbdata = NULL; 68 69 vterm_state_newpen(state); 70 71 state->bold_is_highbright = 0; 72 73 return state; 74 } 75 76 INTERNAL void vterm_state_free(VTermState *state) 77 { 78 vterm_allocator_free(state->vt, state->tabstops); 79 vterm_allocator_free(state->vt, state->lineinfo); 80 vterm_allocator_free(state->vt, state->combine_chars); 81 vterm_allocator_free(state->vt, state); 82 } 83 84 static void scroll(VTermState *state, VTermRect rect, int downward, int rightward) 85 { 86 if(!downward && !rightward) 87 return; 88 89 int rows = rect.end_row - rect.start_row; 90 if(downward > rows) 91 downward = rows; 92 else if(downward < -rows) 93 downward = -rows; 94 95 int cols = rect.end_col - rect.start_col; 96 if(rightward > cols) 97 rightward = cols; 98 else if(rightward < -cols) 99 rightward = -cols; 100 101 // Update lineinfo if full line 102 if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) { 103 int height = rect.end_row - rect.start_row - abs(downward); 104 105 if(downward > 0) 106 memmove(state->lineinfo + rect.start_row, 107 state->lineinfo + rect.start_row + downward, 108 height * sizeof(state->lineinfo[0])); 109 else 110 memmove(state->lineinfo + rect.start_row - downward, 111 state->lineinfo + rect.start_row, 112 height * sizeof(state->lineinfo[0])); 113 } 114 115 if(state->callbacks && state->callbacks->scrollrect) 116 if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) 117 return; 118 119 if(state->callbacks) 120 vterm_scroll_rect(rect, downward, rightward, 121 state->callbacks->moverect, state->callbacks->erase, state->cbdata); 122 } 123 124 static void linefeed(VTermState *state) 125 { 126 if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) { 127 VTermRect rect = { 128 .start_row = state->scrollregion_top, 129 .end_row = SCROLLREGION_BOTTOM(state), 130 .start_col = SCROLLREGION_LEFT(state), 131 .end_col = SCROLLREGION_RIGHT(state), 132 }; 133 134 scroll(state, rect, 1, 0); 135 } 136 else if(state->pos.row < state->rows-1) 137 state->pos.row++; 138 } 139 140 static void grow_combine_buffer(VTermState *state) 141 { 142 size_t new_size = state->combine_chars_size * 2; 143 uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0])); 144 145 memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0])); 146 147 vterm_allocator_free(state->vt, state->combine_chars); 148 149 state->combine_chars = new_chars; 150 state->combine_chars_size = new_size; 151 } 152 153 static void set_col_tabstop(VTermState *state, int col) 154 { 155 unsigned char mask = 1 << (col & 7); 156 state->tabstops[col >> 3] |= mask; 157 } 158 159 static void clear_col_tabstop(VTermState *state, int col) 160 { 161 unsigned char mask = 1 << (col & 7); 162 state->tabstops[col >> 3] &= ~mask; 163 } 164 165 static int is_col_tabstop(VTermState *state, int col) 166 { 167 unsigned char mask = 1 << (col & 7); 168 return state->tabstops[col >> 3] & mask; 169 } 170 171 static int is_cursor_in_scrollregion(const VTermState *state) 172 { 173 if(state->pos.row < state->scrollregion_top || 174 state->pos.row >= SCROLLREGION_BOTTOM(state)) 175 return 0; 176 if(state->pos.col < SCROLLREGION_LEFT(state) || 177 state->pos.col >= SCROLLREGION_RIGHT(state)) 178 return 0; 179 180 return 1; 181 } 182 183 static void tab(VTermState *state, int count, int direction) 184 { 185 while(count > 0) { 186 if(direction > 0) { 187 if(state->pos.col >= THISROWWIDTH(state)-1) 188 return; 189 190 state->pos.col++; 191 } 192 else if(direction < 0) { 193 if(state->pos.col < 1) 194 return; 195 196 state->pos.col--; 197 } 198 199 if(is_col_tabstop(state, state->pos.col)) 200 count--; 201 } 202 } 203 204 #define NO_FORCE 0 205 #define FORCE 1 206 207 #define DWL_OFF 0 208 #define DWL_ON 1 209 210 #define DHL_OFF 0 211 #define DHL_TOP 1 212 #define DHL_BOTTOM 2 213 214 static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl) 215 { 216 VTermLineInfo info = state->lineinfo[row]; 217 218 if(dwl == DWL_OFF) 219 info.doublewidth = DWL_OFF; 220 else if(dwl == DWL_ON) 221 info.doublewidth = DWL_ON; 222 // else -1 to ignore 223 224 if(dhl == DHL_OFF) 225 info.doubleheight = DHL_OFF; 226 else if(dhl == DHL_TOP) 227 info.doubleheight = DHL_TOP; 228 else if(dhl == DHL_BOTTOM) 229 info.doubleheight = DHL_BOTTOM; 230 231 if((state->callbacks && 232 state->callbacks->setlineinfo && 233 (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata)) 234 || force) 235 state->lineinfo[row] = info; 236 } 237 238 static int on_text(const char bytes[], size_t len, void *user) 239 { 240 VTermState *state = user; 241 242 VTermPos oldpos = state->pos; 243 244 // We'll have at most len codepoints 245 uint32_t codepoints[len]; 246 int npoints = 0; 247 size_t eaten = 0; 248 249 VTermEncodingInstance *encoding = 250 state->gsingle_set ? &state->encoding[state->gsingle_set] : 251 !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] : 252 state->vt->mode.utf8 ? &state->encoding_utf8 : 253 &state->encoding[state->gr_set]; 254 255 (*encoding->enc->decode)(encoding->enc, encoding->data, 256 codepoints, &npoints, state->gsingle_set ? 1 : len, 257 bytes, &eaten, len); 258 259 /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet 260 * for even a single codepoint 261 */ 262 if(!npoints) 263 return eaten; 264 265 if(state->gsingle_set && npoints) 266 state->gsingle_set = 0; 267 268 int i = 0; 269 270 /* This is a combining char. that needs to be merged with the previous 271 * glyph output */ 272 if(vterm_unicode_is_combining(codepoints[i])) { 273 /* See if the cursor has moved since */ 274 if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) { 275 #ifdef DEBUG_GLYPH_COMBINE 276 int printpos; 277 printf("DEBUG: COMBINING SPLIT GLYPH of chars {"); 278 for(printpos = 0; state->combine_chars[printpos]; printpos++) 279 printf("U+%04x ", state->combine_chars[printpos]); 280 printf("} + {"); 281 #endif 282 283 /* Find where we need to append these combining chars */ 284 int saved_i = 0; 285 while(state->combine_chars[saved_i]) 286 saved_i++; 287 288 /* Add extra ones */ 289 while(i < npoints && vterm_unicode_is_combining(codepoints[i])) { 290 if(saved_i >= state->combine_chars_size) 291 grow_combine_buffer(state); 292 state->combine_chars[saved_i++] = codepoints[i++]; 293 } 294 if(saved_i >= state->combine_chars_size) 295 grow_combine_buffer(state); 296 state->combine_chars[saved_i] = 0; 297 298 #ifdef DEBUG_GLYPH_COMBINE 299 for(; state->combine_chars[printpos]; printpos++) 300 printf("U+%04x ", state->combine_chars[printpos]); 301 printf("}\n"); 302 #endif 303 304 /* Now render it */ 305 putglyph(state, state->combine_chars, state->combine_width, state->combine_pos); 306 } 307 else { 308 DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n"); 309 } 310 } 311 312 for(; i < npoints; i++) { 313 // Try to find combining characters following this 314 int glyph_starts = i; 315 int glyph_ends; 316 for(glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++) 317 if(!vterm_unicode_is_combining(codepoints[glyph_ends])) 318 break; 319 320 int width = 0; 321 322 uint32_t chars[glyph_ends - glyph_starts + 1]; 323 324 for( ; i < glyph_ends; i++) { 325 chars[i - glyph_starts] = codepoints[i]; 326 int this_width = vterm_unicode_width(codepoints[i]); 327 #ifdef DEBUG 328 if(this_width < 0) { 329 fprintf(stderr, "Text with negative-width codepoint U+%04x\n", codepoints[i]); 330 abort(); 331 } 332 #endif 333 width += this_width; 334 } 335 336 chars[glyph_ends - glyph_starts] = 0; 337 i--; 338 339 #ifdef DEBUG_GLYPH_COMBINE 340 int printpos; 341 printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts); 342 for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++) 343 printf("U+%04x ", chars[printpos]); 344 printf("}, onscreen width %d\n", width); 345 #endif 346 347 if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) { 348 linefeed(state); 349 state->pos.col = 0; 350 state->at_phantom = 0; 351 } 352 353 if(state->mode.insert) { 354 /* TODO: This will be a little inefficient for large bodies of text, as 355 * it'll have to 'ICH' effectively before every glyph. We should scan 356 * ahead and ICH as many times as required 357 */ 358 VTermRect rect = { 359 .start_row = state->pos.row, 360 .end_row = state->pos.row + 1, 361 .start_col = state->pos.col, 362 .end_col = THISROWWIDTH(state), 363 }; 364 scroll(state, rect, 0, -1); 365 } 366 367 putglyph(state, chars, width, state->pos); 368 369 if(i == npoints - 1) { 370 /* End of the buffer. Save the chars in case we have to combine with 371 * more on the next call */ 372 int save_i; 373 for(save_i = 0; chars[save_i]; save_i++) { 374 if(save_i >= state->combine_chars_size) 375 grow_combine_buffer(state); 376 state->combine_chars[save_i] = chars[save_i]; 377 } 378 if(save_i >= state->combine_chars_size) 379 grow_combine_buffer(state); 380 state->combine_chars[save_i] = 0; 381 state->combine_width = width; 382 state->combine_pos = state->pos; 383 } 384 385 if(state->pos.col + width >= THISROWWIDTH(state)) { 386 if(state->mode.autowrap) 387 state->at_phantom = 1; 388 } 389 else { 390 state->pos.col += width; 391 } 392 } 393 394 updatecursor(state, &oldpos, 0); 395 396 #ifdef DEBUG 397 if(state->pos.row < 0 || state->pos.row >= state->rows || 398 state->pos.col < 0 || state->pos.col >= state->cols) { 399 fprintf(stderr, "Position out of bounds after text: (%d,%d)\n", 400 state->pos.row, state->pos.col); 401 abort(); 402 } 403 #endif 404 405 return eaten; 406 } 407 408 static int on_control(unsigned char control, void *user) 409 { 410 VTermState *state = user; 411 412 VTermPos oldpos = state->pos; 413 414 switch(control) { 415 case 0x07: // BEL - ECMA-48 8.3.3 416 if(state->callbacks && state->callbacks->bell) 417 (*state->callbacks->bell)(state->cbdata); 418 break; 419 420 case 0x08: // BS - ECMA-48 8.3.5 421 if(state->pos.col > 0) 422 state->pos.col--; 423 break; 424 425 case 0x09: // HT - ECMA-48 8.3.60 426 tab(state, 1, +1); 427 break; 428 429 case 0x0a: // LF - ECMA-48 8.3.74 430 case 0x0b: // VT 431 case 0x0c: // FF 432 linefeed(state); 433 if(state->mode.newline) 434 state->pos.col = 0; 435 break; 436 437 case 0x0d: // CR - ECMA-48 8.3.15 438 state->pos.col = 0; 439 break; 440 441 case 0x0e: // LS1 - ECMA-48 8.3.76 442 state->gl_set = 1; 443 break; 444 445 case 0x0f: // LS0 - ECMA-48 8.3.75 446 state->gl_set = 0; 447 break; 448 449 case 0x84: // IND - DEPRECATED but implemented for completeness 450 linefeed(state); 451 break; 452 453 case 0x85: // NEL - ECMA-48 8.3.86 454 linefeed(state); 455 state->pos.col = 0; 456 break; 457 458 case 0x88: // HTS - ECMA-48 8.3.62 459 set_col_tabstop(state, state->pos.col); 460 break; 461 462 case 0x8d: // RI - ECMA-48 8.3.104 463 if(state->pos.row == state->scrollregion_top) { 464 VTermRect rect = { 465 .start_row = state->scrollregion_top, 466 .end_row = SCROLLREGION_BOTTOM(state), 467 .start_col = SCROLLREGION_LEFT(state), 468 .end_col = SCROLLREGION_RIGHT(state), 469 }; 470 471 scroll(state, rect, -1, 0); 472 } 473 else if(state->pos.row > 0) 474 state->pos.row--; 475 break; 476 477 case 0x8e: // SS2 - ECMA-48 8.3.141 478 state->gsingle_set = 2; 479 break; 480 481 case 0x8f: // SS3 - ECMA-48 8.3.142 482 state->gsingle_set = 3; 483 break; 484 485 default: 486 if(state->fallbacks && state->fallbacks->control) 487 if((*state->fallbacks->control)(control, state->fbdata)) 488 return 1; 489 490 return 0; 491 } 492 493 updatecursor(state, &oldpos, 1); 494 495 #ifdef DEBUG 496 if(state->pos.row < 0 || state->pos.row >= state->rows || 497 state->pos.col < 0 || state->pos.col >= state->cols) { 498 fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n", 499 control, state->pos.row, state->pos.col); 500 abort(); 501 } 502 #endif 503 504 return 1; 505 } 506 507 static int settermprop_bool(VTermState *state, VTermProp prop, int v) 508 { 509 VTermValue val = { .boolean = v }; 510 return vterm_state_set_termprop(state, prop, &val); 511 } 512 513 static int settermprop_int(VTermState *state, VTermProp prop, int v) 514 { 515 VTermValue val = { .number = v }; 516 return vterm_state_set_termprop(state, prop, &val); 517 } 518 519 static int settermprop_string(VTermState *state, VTermProp prop, const char *str, size_t len) 520 { 521 char strvalue[len+1]; 522 strncpy(strvalue, str, len); 523 strvalue[len] = 0; 524 525 VTermValue val = { .string = strvalue }; 526 return vterm_state_set_termprop(state, prop, &val); 527 } 528 529 static void savecursor(VTermState *state, int save) 530 { 531 if(save) { 532 state->saved.pos = state->pos; 533 state->saved.mode.cursor_visible = state->mode.cursor_visible; 534 state->saved.mode.cursor_blink = state->mode.cursor_blink; 535 state->saved.mode.cursor_shape = state->mode.cursor_shape; 536 537 vterm_state_savepen(state, 1); 538 } 539 else { 540 VTermPos oldpos = state->pos; 541 542 state->pos = state->saved.pos; 543 544 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible); 545 settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink); 546 settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape); 547 548 vterm_state_savepen(state, 0); 549 550 updatecursor(state, &oldpos, 1); 551 } 552 } 553 554 static int on_escape(const char *bytes, size_t len, void *user) 555 { 556 VTermState *state = user; 557 558 /* Easier to decode this from the first byte, even though the final 559 * byte terminates it 560 */ 561 switch(bytes[0]) { 562 case ' ': 563 if(len != 2) 564 return 0; 565 566 switch(bytes[1]) { 567 case 'F': // S7C1T 568 state->vt->mode.ctrl8bit = 0; 569 break; 570 571 case 'G': // S8C1T 572 state->vt->mode.ctrl8bit = 1; 573 break; 574 575 default: 576 return 0; 577 } 578 return 2; 579 580 case '#': 581 if(len != 2) 582 return 0; 583 584 switch(bytes[1]) { 585 case '3': // DECDHL top 586 if(state->mode.leftrightmargin) 587 break; 588 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP); 589 break; 590 591 case '4': // DECDHL bottom 592 if(state->mode.leftrightmargin) 593 break; 594 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM); 595 break; 596 597 case '5': // DECSWL 598 if(state->mode.leftrightmargin) 599 break; 600 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF); 601 break; 602 603 case '6': // DECDWL 604 if(state->mode.leftrightmargin) 605 break; 606 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF); 607 break; 608 609 case '8': // DECALN 610 { 611 VTermPos pos; 612 uint32_t E[] = { 'E', 0 }; 613 for(pos.row = 0; pos.row < state->rows; pos.row++) 614 for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++) 615 putglyph(state, E, 1, pos); 616 break; 617 } 618 619 default: 620 return 0; 621 } 622 return 2; 623 624 case '(': case ')': case '*': case '+': // SCS 625 if(len != 2) 626 return 0; 627 628 { 629 int setnum = bytes[0] - 0x28; 630 VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]); 631 632 if(newenc) { 633 state->encoding[setnum].enc = newenc; 634 635 if(newenc->init) 636 (*newenc->init)(newenc, state->encoding[setnum].data); 637 } 638 } 639 640 return 2; 641 642 case '7': // DECSC 643 savecursor(state, 1); 644 return 1; 645 646 case '8': // DECRC 647 savecursor(state, 0); 648 return 1; 649 650 case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100 651 return 1; 652 653 case '=': // DECKPAM 654 state->mode.keypad = 1; 655 return 1; 656 657 case '>': // DECKPNM 658 state->mode.keypad = 0; 659 return 1; 660 661 case 'c': // RIS - ECMA-48 8.3.105 662 { 663 VTermPos oldpos = state->pos; 664 vterm_state_reset(state, 1); 665 if(state->callbacks && state->callbacks->movecursor) 666 (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata); 667 return 1; 668 } 669 670 case 'n': // LS2 - ECMA-48 8.3.78 671 state->gl_set = 2; 672 return 1; 673 674 case 'o': // LS3 - ECMA-48 8.3.80 675 state->gl_set = 3; 676 return 1; 677 678 case '~': // LS1R - ECMA-48 8.3.77 679 state->gr_set = 1; 680 return 1; 681 682 case '}': // LS2R - ECMA-48 8.3.79 683 state->gr_set = 2; 684 return 1; 685 686 case '|': // LS3R - ECMA-48 8.3.81 687 state->gr_set = 3; 688 return 1; 689 690 default: 691 return 0; 692 } 693 } 694 695 static void set_mode(VTermState *state, int num, int val) 696 { 697 switch(num) { 698 case 4: // IRM - ECMA-48 7.2.10 699 state->mode.insert = val; 700 break; 701 702 case 20: // LNM - ANSI X3.4-1977 703 state->mode.newline = val; 704 break; 705 706 default: 707 DEBUG_LOG("libvterm: Unknown mode %d\n", num); 708 return; 709 } 710 } 711 712 static void set_dec_mode(VTermState *state, int num, int val) 713 { 714 switch(num) { 715 case 1: 716 state->mode.cursor = val; 717 break; 718 719 case 5: // DECSCNM - screen mode 720 settermprop_bool(state, VTERM_PROP_REVERSE, val); 721 break; 722 723 case 6: // DECOM - origin mode 724 { 725 VTermPos oldpos = state->pos; 726 state->mode.origin = val; 727 state->pos.row = state->mode.origin ? state->scrollregion_top : 0; 728 state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0; 729 updatecursor(state, &oldpos, 1); 730 } 731 break; 732 733 case 7: 734 state->mode.autowrap = val; 735 break; 736 737 case 12: 738 settermprop_bool(state, VTERM_PROP_CURSORBLINK, val); 739 break; 740 741 case 25: 742 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val); 743 break; 744 745 case 69: // DECVSSM - vertical split screen mode 746 // DECLRMM - left/right margin mode 747 state->mode.leftrightmargin = val; 748 if(val) { 749 // Setting DECVSSM must clear doublewidth/doubleheight state of every line 750 for(int row = 0; row < state->rows; row++) 751 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); 752 } 753 754 break; 755 756 case 1000: 757 case 1002: 758 case 1003: 759 settermprop_int(state, VTERM_PROP_MOUSE, 760 !val ? VTERM_PROP_MOUSE_NONE : 761 (num == 1000) ? VTERM_PROP_MOUSE_CLICK : 762 (num == 1002) ? VTERM_PROP_MOUSE_DRAG : 763 VTERM_PROP_MOUSE_MOVE); 764 break; 765 766 case 1004: 767 state->mode.report_focus = val; 768 break; 769 770 case 1005: 771 state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10; 772 break; 773 774 case 1006: 775 state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10; 776 break; 777 778 case 1015: 779 state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10; 780 break; 781 782 case 1047: 783 settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); 784 break; 785 786 case 1048: 787 savecursor(state, val); 788 break; 789 790 case 1049: 791 settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); 792 savecursor(state, val); 793 break; 794 795 case 2004: 796 state->mode.bracketpaste = val; 797 break; 798 799 default: 800 DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num); 801 return; 802 } 803 } 804 805 static void request_dec_mode(VTermState *state, int num) 806 { 807 int reply; 808 809 switch(num) { 810 case 1: 811 reply = state->mode.cursor; 812 break; 813 814 case 5: 815 reply = state->mode.screen; 816 break; 817 818 case 6: 819 reply = state->mode.origin; 820 break; 821 822 case 7: 823 reply = state->mode.autowrap; 824 break; 825 826 case 12: 827 reply = state->mode.cursor_blink; 828 break; 829 830 case 25: 831 reply = state->mode.cursor_visible; 832 break; 833 834 case 69: 835 reply = state->mode.leftrightmargin; 836 break; 837 838 case 1000: 839 reply = state->mouse_flags == MOUSE_WANT_CLICK; 840 break; 841 842 case 1002: 843 reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG); 844 break; 845 846 case 1003: 847 reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE); 848 break; 849 850 case 1004: 851 reply = state->mode.report_focus; 852 break; 853 854 case 1005: 855 reply = state->mouse_protocol == MOUSE_UTF8; 856 break; 857 858 case 1006: 859 reply = state->mouse_protocol == MOUSE_SGR; 860 break; 861 862 case 1015: 863 reply = state->mouse_protocol == MOUSE_RXVT; 864 break; 865 866 case 1047: 867 reply = state->mode.alt_screen; 868 break; 869 870 case 2004: 871 reply = state->mode.bracketpaste; 872 break; 873 874 default: 875 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0); 876 return; 877 } 878 879 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2); 880 } 881 882 static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) 883 { 884 VTermState *state = user; 885 int leader_byte = 0; 886 int intermed_byte = 0; 887 int cancel_phantom = 1; 888 889 if(leader && leader[0]) { 890 if(leader[1]) // longer than 1 char 891 return 0; 892 893 switch(leader[0]) { 894 case '?': 895 case '>': 896 leader_byte = leader[0]; 897 break; 898 default: 899 return 0; 900 } 901 } 902 903 if(intermed && intermed[0]) { 904 if(intermed[1]) // longer than 1 char 905 return 0; 906 907 switch(intermed[0]) { 908 case ' ': 909 case '"': 910 case '$': 911 case '\'': 912 intermed_byte = intermed[0]; 913 break; 914 default: 915 return 0; 916 } 917 } 918 919 VTermPos oldpos = state->pos; 920 921 // Some temporaries for later code 922 int count, val; 923 int row, col; 924 VTermRect rect; 925 int selective; 926 927 #define LBOUND(v,min) if((v) < (min)) (v) = (min) 928 #define UBOUND(v,max) if((v) > (max)) (v) = (max) 929 930 #define LEADER(l,b) ((l << 8) | b) 931 #define INTERMED(i,b) ((i << 16) | b) 932 933 switch(intermed_byte << 16 | leader_byte << 8 | command) { 934 case 0x40: // ICH - ECMA-48 8.3.64 935 count = CSI_ARG_COUNT(args[0]); 936 937 if(!is_cursor_in_scrollregion(state)) 938 break; 939 940 rect.start_row = state->pos.row; 941 rect.end_row = state->pos.row + 1; 942 rect.start_col = state->pos.col; 943 if(state->mode.leftrightmargin) 944 rect.end_col = SCROLLREGION_RIGHT(state); 945 else 946 rect.end_col = THISROWWIDTH(state); 947 948 scroll(state, rect, 0, -count); 949 950 break; 951 952 case 0x41: // CUU - ECMA-48 8.3.22 953 count = CSI_ARG_COUNT(args[0]); 954 state->pos.row -= count; 955 state->at_phantom = 0; 956 break; 957 958 case 0x42: // CUD - ECMA-48 8.3.19 959 count = CSI_ARG_COUNT(args[0]); 960 state->pos.row += count; 961 state->at_phantom = 0; 962 break; 963 964 case 0x43: // CUF - ECMA-48 8.3.20 965 count = CSI_ARG_COUNT(args[0]); 966 state->pos.col += count; 967 state->at_phantom = 0; 968 break; 969 970 case 0x44: // CUB - ECMA-48 8.3.18 971 count = CSI_ARG_COUNT(args[0]); 972 state->pos.col -= count; 973 state->at_phantom = 0; 974 break; 975 976 case 0x45: // CNL - ECMA-48 8.3.12 977 count = CSI_ARG_COUNT(args[0]); 978 state->pos.col = 0; 979 state->pos.row += count; 980 state->at_phantom = 0; 981 break; 982 983 case 0x46: // CPL - ECMA-48 8.3.13 984 count = CSI_ARG_COUNT(args[0]); 985 state->pos.col = 0; 986 state->pos.row -= count; 987 state->at_phantom = 0; 988 break; 989 990 case 0x47: // CHA - ECMA-48 8.3.9 991 val = CSI_ARG_OR(args[0], 1); 992 state->pos.col = val-1; 993 state->at_phantom = 0; 994 break; 995 996 case 0x48: // CUP - ECMA-48 8.3.21 997 row = CSI_ARG_OR(args[0], 1); 998 col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); 999 // zero-based 1000 state->pos.row = row-1; 1001 state->pos.col = col-1; 1002 if(state->mode.origin) { 1003 state->pos.row += state->scrollregion_top; 1004 state->pos.col += SCROLLREGION_LEFT(state); 1005 } 1006 state->at_phantom = 0; 1007 break; 1008 1009 case 0x49: // CHT - ECMA-48 8.3.10 1010 count = CSI_ARG_COUNT(args[0]); 1011 tab(state, count, +1); 1012 break; 1013 1014 case 0x4a: // ED - ECMA-48 8.3.39 1015 case LEADER('?', 0x4a): // DECSED - Selective Erase in Display 1016 selective = (leader_byte == '?'); 1017 switch(CSI_ARG(args[0])) { 1018 case CSI_ARG_MISSING: 1019 case 0: 1020 rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; 1021 rect.start_col = state->pos.col; rect.end_col = state->cols; 1022 if(rect.end_col > rect.start_col) 1023 erase(state, rect, selective); 1024 1025 rect.start_row = state->pos.row + 1; rect.end_row = state->rows; 1026 rect.start_col = 0; 1027 for(int row = rect.start_row; row < rect.end_row; row++) 1028 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); 1029 if(rect.end_row > rect.start_row) 1030 erase(state, rect, selective); 1031 break; 1032 1033 case 1: 1034 rect.start_row = 0; rect.end_row = state->pos.row; 1035 rect.start_col = 0; rect.end_col = state->cols; 1036 for(int row = rect.start_row; row < rect.end_row; row++) 1037 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); 1038 if(rect.end_col > rect.start_col) 1039 erase(state, rect, selective); 1040 1041 rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; 1042 rect.end_col = state->pos.col + 1; 1043 if(rect.end_row > rect.start_row) 1044 erase(state, rect, selective); 1045 break; 1046 1047 case 2: 1048 rect.start_row = 0; rect.end_row = state->rows; 1049 rect.start_col = 0; rect.end_col = state->cols; 1050 for(int row = rect.start_row; row < rect.end_row; row++) 1051 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); 1052 erase(state, rect, selective); 1053 break; 1054 } 1055 break; 1056 1057 case 0x4b: // EL - ECMA-48 8.3.41 1058 case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line 1059 selective = (leader_byte == '?'); 1060 rect.start_row = state->pos.row; 1061 rect.end_row = state->pos.row + 1; 1062 1063 switch(CSI_ARG(args[0])) { 1064 case CSI_ARG_MISSING: 1065 case 0: 1066 rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break; 1067 case 1: 1068 rect.start_col = 0; rect.end_col = state->pos.col + 1; break; 1069 case 2: 1070 rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break; 1071 default: 1072 return 0; 1073 } 1074 1075 if(rect.end_col > rect.start_col) 1076 erase(state, rect, selective); 1077 1078 break; 1079 1080 case 0x4c: // IL - ECMA-48 8.3.67 1081 count = CSI_ARG_COUNT(args[0]); 1082 1083 if(!is_cursor_in_scrollregion(state)) 1084 break; 1085 1086 rect.start_row = state->pos.row; 1087 rect.end_row = SCROLLREGION_BOTTOM(state); 1088 rect.start_col = SCROLLREGION_LEFT(state); 1089 rect.end_col = SCROLLREGION_RIGHT(state); 1090 1091 scroll(state, rect, -count, 0); 1092 1093 break; 1094 1095 case 0x4d: // DL - ECMA-48 8.3.32 1096 count = CSI_ARG_COUNT(args[0]); 1097 1098 if(!is_cursor_in_scrollregion(state)) 1099 break; 1100 1101 rect.start_row = state->pos.row; 1102 rect.end_row = SCROLLREGION_BOTTOM(state); 1103 rect.start_col = SCROLLREGION_LEFT(state); 1104 rect.end_col = SCROLLREGION_RIGHT(state); 1105 1106 scroll(state, rect, count, 0); 1107 1108 break; 1109 1110 case 0x50: // DCH - ECMA-48 8.3.26 1111 count = CSI_ARG_COUNT(args[0]); 1112 1113 if(!is_cursor_in_scrollregion(state)) 1114 break; 1115 1116 rect.start_row = state->pos.row; 1117 rect.end_row = state->pos.row + 1; 1118 rect.start_col = state->pos.col; 1119 if(state->mode.leftrightmargin) 1120 rect.end_col = SCROLLREGION_RIGHT(state); 1121 else 1122 rect.end_col = THISROWWIDTH(state); 1123 1124 scroll(state, rect, 0, count); 1125 1126 break; 1127 1128 case 0x53: // SU - ECMA-48 8.3.147 1129 count = CSI_ARG_COUNT(args[0]); 1130 1131 rect.start_row = state->scrollregion_top; 1132 rect.end_row = SCROLLREGION_BOTTOM(state); 1133 rect.start_col = SCROLLREGION_LEFT(state); 1134 rect.end_col = SCROLLREGION_RIGHT(state); 1135 1136 scroll(state, rect, count, 0); 1137 1138 break; 1139 1140 case 0x54: // SD - ECMA-48 8.3.113 1141 count = CSI_ARG_COUNT(args[0]); 1142 1143 rect.start_row = state->scrollregion_top; 1144 rect.end_row = SCROLLREGION_BOTTOM(state); 1145 rect.start_col = SCROLLREGION_LEFT(state); 1146 rect.end_col = SCROLLREGION_RIGHT(state); 1147 1148 scroll(state, rect, -count, 0); 1149 1150 break; 1151 1152 case 0x58: // ECH - ECMA-48 8.3.38 1153 count = CSI_ARG_COUNT(args[0]); 1154 1155 rect.start_row = state->pos.row; 1156 rect.end_row = state->pos.row + 1; 1157 rect.start_col = state->pos.col; 1158 rect.end_col = state->pos.col + count; 1159 UBOUND(rect.end_col, THISROWWIDTH(state)); 1160 1161 erase(state, rect, 0); 1162 break; 1163 1164 case 0x5a: // CBT - ECMA-48 8.3.7 1165 count = CSI_ARG_COUNT(args[0]); 1166 tab(state, count, -1); 1167 break; 1168 1169 case 0x60: // HPA - ECMA-48 8.3.57 1170 col = CSI_ARG_OR(args[0], 1); 1171 state->pos.col = col-1; 1172 state->at_phantom = 0; 1173 break; 1174 1175 case 0x61: // HPR - ECMA-48 8.3.59 1176 count = CSI_ARG_COUNT(args[0]); 1177 state->pos.col += count; 1178 state->at_phantom = 0; 1179 break; 1180 1181 case 0x62: { // REP - ECMA-48 8.3.103 1182 const int row_width = THISROWWIDTH(state); 1183 count = CSI_ARG_COUNT(args[0]); 1184 col = state->pos.col + count; 1185 UBOUND(col, row_width); 1186 while (state->pos.col < col) { 1187 putglyph(state, state->combine_chars, state->combine_width, state->pos); 1188 state->pos.col += state->combine_width; 1189 } 1190 if (state->pos.col + state->combine_width >= row_width) { 1191 if (state->mode.autowrap) { 1192 state->at_phantom = 1; 1193 cancel_phantom = 0; 1194 } 1195 } 1196 break; 1197 } 1198 1199 case 0x63: // DA - ECMA-48 8.3.24 1200 val = CSI_ARG_OR(args[0], 0); 1201 if(val == 0) 1202 // DEC VT100 response 1203 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c"); 1204 break; 1205 1206 case LEADER('>', 0x63): // DEC secondary Device Attributes 1207 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0); 1208 break; 1209 1210 case 0x64: // VPA - ECMA-48 8.3.158 1211 row = CSI_ARG_OR(args[0], 1); 1212 state->pos.row = row-1; 1213 if(state->mode.origin) 1214 state->pos.row += state->scrollregion_top; 1215 state->at_phantom = 0; 1216 break; 1217 1218 case 0x65: // VPR - ECMA-48 8.3.160 1219 count = CSI_ARG_COUNT(args[0]); 1220 state->pos.row += count; 1221 state->at_phantom = 0; 1222 break; 1223 1224 case 0x66: // HVP - ECMA-48 8.3.63 1225 row = CSI_ARG_OR(args[0], 1); 1226 col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); 1227 // zero-based 1228 state->pos.row = row-1; 1229 state->pos.col = col-1; 1230 if(state->mode.origin) { 1231 state->pos.row += state->scrollregion_top; 1232 state->pos.col += SCROLLREGION_LEFT(state); 1233 } 1234 state->at_phantom = 0; 1235 break; 1236 1237 case 0x67: // TBC - ECMA-48 8.3.154 1238 val = CSI_ARG_OR(args[0], 0); 1239 1240 switch(val) { 1241 case 0: 1242 clear_col_tabstop(state, state->pos.col); 1243 break; 1244 case 3: 1245 case 5: 1246 for(col = 0; col < state->cols; col++) 1247 clear_col_tabstop(state, col); 1248 break; 1249 case 1: 1250 case 2: 1251 case 4: 1252 break; 1253 /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */ 1254 default: 1255 return 0; 1256 } 1257 break; 1258 1259 case 0x68: // SM - ECMA-48 8.3.125 1260 if(!CSI_ARG_IS_MISSING(args[0])) 1261 set_mode(state, CSI_ARG(args[0]), 1); 1262 break; 1263 1264 case LEADER('?', 0x68): // DEC private mode set 1265 if(!CSI_ARG_IS_MISSING(args[0])) 1266 set_dec_mode(state, CSI_ARG(args[0]), 1); 1267 break; 1268 1269 case 0x6a: // HPB - ECMA-48 8.3.58 1270 count = CSI_ARG_COUNT(args[0]); 1271 state->pos.col -= count; 1272 state->at_phantom = 0; 1273 break; 1274 1275 case 0x6b: // VPB - ECMA-48 8.3.159 1276 count = CSI_ARG_COUNT(args[0]); 1277 state->pos.row -= count; 1278 state->at_phantom = 0; 1279 break; 1280 1281 case 0x6c: // RM - ECMA-48 8.3.106 1282 if(!CSI_ARG_IS_MISSING(args[0])) 1283 set_mode(state, CSI_ARG(args[0]), 0); 1284 break; 1285 1286 case LEADER('?', 0x6c): // DEC private mode reset 1287 if(!CSI_ARG_IS_MISSING(args[0])) 1288 set_dec_mode(state, CSI_ARG(args[0]), 0); 1289 break; 1290 1291 case 0x6d: // SGR - ECMA-48 8.3.117 1292 vterm_state_setpen(state, args, argcount); 1293 break; 1294 1295 case 0x6e: // DSR - ECMA-48 8.3.35 1296 case LEADER('?', 0x6e): // DECDSR 1297 val = CSI_ARG_OR(args[0], 0); 1298 1299 { 1300 char *qmark = (leader_byte == '?') ? "?" : ""; 1301 1302 switch(val) { 1303 case 0: case 1: case 2: case 3: case 4: 1304 // ignore - these are replies 1305 break; 1306 case 5: 1307 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark); 1308 break; 1309 case 6: // CPR - cursor position report 1310 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1); 1311 break; 1312 } 1313 } 1314 break; 1315 1316 1317 case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset 1318 vterm_state_reset(state, 0); 1319 break; 1320 1321 case LEADER('?', INTERMED('$', 0x70)): 1322 request_dec_mode(state, CSI_ARG(args[0])); 1323 break; 1324 1325 case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape 1326 val = CSI_ARG_OR(args[0], 1); 1327 1328 switch(val) { 1329 case 0: case 1: 1330 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); 1331 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); 1332 break; 1333 case 2: 1334 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); 1335 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); 1336 break; 1337 case 3: 1338 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); 1339 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); 1340 break; 1341 case 4: 1342 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); 1343 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); 1344 break; 1345 case 5: 1346 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); 1347 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); 1348 break; 1349 case 6: 1350 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); 1351 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); 1352 break; 1353 } 1354 1355 break; 1356 1357 case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute 1358 val = CSI_ARG_OR(args[0], 0); 1359 1360 switch(val) { 1361 case 0: case 2: 1362 state->protected_cell = 0; 1363 break; 1364 case 1: 1365 state->protected_cell = 1; 1366 break; 1367 } 1368 1369 break; 1370 1371 case 0x72: // DECSTBM - DEC custom 1372 state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1; 1373 state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); 1374 LBOUND(state->scrollregion_top, 0); 1375 UBOUND(state->scrollregion_top, state->rows); 1376 LBOUND(state->scrollregion_bottom, -1); 1377 if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows) 1378 state->scrollregion_bottom = -1; 1379 else 1380 UBOUND(state->scrollregion_bottom, state->rows); 1381 1382 if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { 1383 // Invalid 1384 state->scrollregion_top = 0; 1385 state->scrollregion_bottom = -1; 1386 } 1387 1388 break; 1389 1390 case 0x73: // DECSLRM - DEC custom 1391 // Always allow setting these margins, just they won't take effect without DECVSSM 1392 state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1; 1393 state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); 1394 LBOUND(state->scrollregion_left, 0); 1395 UBOUND(state->scrollregion_left, state->cols); 1396 LBOUND(state->scrollregion_right, -1); 1397 if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols) 1398 state->scrollregion_right = -1; 1399 else 1400 UBOUND(state->scrollregion_right, state->cols); 1401 1402 if(state->scrollregion_right > -1 && 1403 state->scrollregion_right <= state->scrollregion_left) { 1404 // Invalid 1405 state->scrollregion_left = 0; 1406 state->scrollregion_right = -1; 1407 } 1408 1409 break; 1410 1411 case INTERMED('\'', 0x7D): // DECIC 1412 count = CSI_ARG_COUNT(args[0]); 1413 1414 if(!is_cursor_in_scrollregion(state)) 1415 break; 1416 1417 rect.start_row = state->scrollregion_top; 1418 rect.end_row = SCROLLREGION_BOTTOM(state); 1419 rect.start_col = state->pos.col; 1420 rect.end_col = SCROLLREGION_RIGHT(state); 1421 1422 scroll(state, rect, 0, -count); 1423 1424 break; 1425 1426 case INTERMED('\'', 0x7E): // DECDC 1427 count = CSI_ARG_COUNT(args[0]); 1428 1429 if(!is_cursor_in_scrollregion(state)) 1430 break; 1431 1432 rect.start_row = state->scrollregion_top; 1433 rect.end_row = SCROLLREGION_BOTTOM(state); 1434 rect.start_col = state->pos.col; 1435 rect.end_col = SCROLLREGION_RIGHT(state); 1436 1437 scroll(state, rect, 0, count); 1438 1439 break; 1440 1441 default: 1442 if(state->fallbacks && state->fallbacks->csi) 1443 if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) 1444 return 1; 1445 1446 return 0; 1447 } 1448 1449 if(state->mode.origin) { 1450 LBOUND(state->pos.row, state->scrollregion_top); 1451 UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state)-1); 1452 LBOUND(state->pos.col, SCROLLREGION_LEFT(state)); 1453 UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1); 1454 } 1455 else { 1456 LBOUND(state->pos.row, 0); 1457 UBOUND(state->pos.row, state->rows-1); 1458 LBOUND(state->pos.col, 0); 1459 UBOUND(state->pos.col, THISROWWIDTH(state)-1); 1460 } 1461 1462 updatecursor(state, &oldpos, cancel_phantom); 1463 1464 #ifdef DEBUG 1465 if(state->pos.row < 0 || state->pos.row >= state->rows || 1466 state->pos.col < 0 || state->pos.col >= state->cols) { 1467 fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n", 1468 command, state->pos.row, state->pos.col); 1469 abort(); 1470 } 1471 1472 if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { 1473 fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n", 1474 command, SCROLLREGION_BOTTOM(state), state->scrollregion_top); 1475 abort(); 1476 } 1477 1478 if(SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) { 1479 fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n", 1480 command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state)); 1481 abort(); 1482 } 1483 #endif 1484 1485 return 1; 1486 } 1487 1488 static int on_osc(const char *command, size_t cmdlen, void *user) 1489 { 1490 VTermState *state = user; 1491 1492 if(cmdlen < 2) 1493 return 0; 1494 1495 if(strneq(command, "0;", 2)) { 1496 settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2); 1497 settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2); 1498 return 1; 1499 } 1500 else if(strneq(command, "1;", 2)) { 1501 settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2); 1502 return 1; 1503 } 1504 else if(strneq(command, "2;", 2)) { 1505 settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2); 1506 return 1; 1507 } 1508 else if(state->fallbacks && state->fallbacks->osc) 1509 if((*state->fallbacks->osc)(command, cmdlen, state->fbdata)) 1510 return 1; 1511 1512 return 0; 1513 } 1514 1515 static void request_status_string(VTermState *state, const char *command, size_t cmdlen) 1516 { 1517 if(cmdlen == 1) 1518 switch(command[0]) { 1519 case 'm': // Query SGR 1520 { 1521 long args[20]; 1522 int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0])); 1523 vterm_push_output_sprintf_ctrl(state->vt, C1_DCS, "1$r"); 1524 for(int argi = 0; argi < argc; argi++) 1525 vterm_push_output_sprintf(state->vt, 1526 argi == argc - 1 ? "%d" : 1527 CSI_ARG_HAS_MORE(args[argi]) ? "%d:" : 1528 "%d;", 1529 CSI_ARG(args[argi])); 1530 vterm_push_output_sprintf(state->vt, "m"); 1531 vterm_push_output_sprintf_ctrl(state->vt, C1_ST, ""); 1532 } 1533 return; 1534 case 'r': // Query DECSTBM 1535 vterm_push_output_sprintf_dcs(state->vt, "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state)); 1536 return; 1537 case 's': // Query DECSLRM 1538 vterm_push_output_sprintf_dcs(state->vt, "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state)); 1539 return; 1540 } 1541 1542 if(cmdlen == 2) { 1543 if(strneq(command, " q", 2)) { 1544 int reply; 1545 switch(state->mode.cursor_shape) { 1546 case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break; 1547 case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break; 1548 case VTERM_PROP_CURSORSHAPE_BAR_LEFT: reply = 6; break; 1549 } 1550 if(state->mode.cursor_blink) 1551 reply--; 1552 vterm_push_output_sprintf_dcs(state->vt, "1$r%d q", reply); 1553 return; 1554 } 1555 else if(strneq(command, "\"q", 2)) { 1556 vterm_push_output_sprintf_dcs(state->vt, "1$r%d\"q", state->protected_cell ? 1 : 2); 1557 return; 1558 } 1559 } 1560 1561 vterm_push_output_sprintf_dcs(state->vt, "0$r%.s", (int)cmdlen, command); 1562 } 1563 1564 static int on_dcs(const char *command, size_t cmdlen, void *user) 1565 { 1566 VTermState *state = user; 1567 1568 if(cmdlen >= 2 && strneq(command, "$q", 2)) { 1569 request_status_string(state, command+2, cmdlen-2); 1570 return 1; 1571 } 1572 else if(state->fallbacks && state->fallbacks->dcs) 1573 if((*state->fallbacks->dcs)(command, cmdlen, state->fbdata)) 1574 return 1; 1575 1576 return 0; 1577 } 1578 1579 static int on_resize(int rows, int cols, void *user) 1580 { 1581 VTermState *state = user; 1582 VTermPos oldpos = state->pos; 1583 1584 if(cols != state->cols) { 1585 unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8); 1586 1587 /* TODO: This can all be done much more efficiently bytewise */ 1588 int col; 1589 for(col = 0; col < state->cols && col < cols; col++) { 1590 unsigned char mask = 1 << (col & 7); 1591 if(state->tabstops[col >> 3] & mask) 1592 newtabstops[col >> 3] |= mask; 1593 else 1594 newtabstops[col >> 3] &= ~mask; 1595 } 1596 1597 for( ; col < cols; col++) { 1598 unsigned char mask = 1 << (col & 7); 1599 if(col % 8 == 0) 1600 newtabstops[col >> 3] |= mask; 1601 else 1602 newtabstops[col >> 3] &= ~mask; 1603 } 1604 1605 vterm_allocator_free(state->vt, state->tabstops); 1606 state->tabstops = newtabstops; 1607 } 1608 1609 if(rows != state->rows) { 1610 VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo)); 1611 1612 int row; 1613 for(row = 0; row < state->rows && row < rows; row++) { 1614 newlineinfo[row] = state->lineinfo[row]; 1615 } 1616 1617 for( ; row < rows; row++) { 1618 newlineinfo[row] = (VTermLineInfo){ 1619 .doublewidth = 0, 1620 }; 1621 } 1622 1623 vterm_allocator_free(state->vt, state->lineinfo); 1624 state->lineinfo = newlineinfo; 1625 } 1626 1627 state->rows = rows; 1628 state->cols = cols; 1629 1630 if(state->scrollregion_bottom > -1) 1631 UBOUND(state->scrollregion_bottom, state->rows); 1632 if(state->scrollregion_right > -1) 1633 UBOUND(state->scrollregion_right, state->cols); 1634 1635 VTermPos delta = { 0, 0 }; 1636 1637 if(state->callbacks && state->callbacks->resize) 1638 (*state->callbacks->resize)(rows, cols, &delta, state->cbdata); 1639 1640 if(state->at_phantom && state->pos.col < cols-1) { 1641 state->at_phantom = 0; 1642 state->pos.col++; 1643 } 1644 1645 state->pos.row += delta.row; 1646 state->pos.col += delta.col; 1647 1648 if(state->pos.row >= rows) 1649 state->pos.row = rows - 1; 1650 if(state->pos.col >= cols) 1651 state->pos.col = cols - 1; 1652 1653 updatecursor(state, &oldpos, 1); 1654 1655 return 1; 1656 } 1657 1658 static const VTermParserCallbacks parser_callbacks = { 1659 .text = on_text, 1660 .control = on_control, 1661 .escape = on_escape, 1662 .csi = on_csi, 1663 .osc = on_osc, 1664 .dcs = on_dcs, 1665 .resize = on_resize, 1666 }; 1667 1668 VTermState *vterm_obtain_state(VTerm *vt) 1669 { 1670 if(vt->state) 1671 return vt->state; 1672 1673 VTermState *state = vterm_state_new(vt); 1674 vt->state = state; 1675 1676 state->combine_chars_size = 16; 1677 state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0])); 1678 1679 state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8); 1680 1681 state->lineinfo = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo)); 1682 1683 state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u'); 1684 if(*state->encoding_utf8.enc->init) 1685 (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); 1686 1687 vterm_parser_set_callbacks(vt, &parser_callbacks, state); 1688 1689 return state; 1690 } 1691 1692 void vterm_state_reset(VTermState *state, int hard) 1693 { 1694 state->scrollregion_top = 0; 1695 state->scrollregion_bottom = -1; 1696 state->scrollregion_left = 0; 1697 state->scrollregion_right = -1; 1698 1699 state->mode.keypad = 0; 1700 state->mode.cursor = 0; 1701 state->mode.autowrap = 1; 1702 state->mode.insert = 0; 1703 state->mode.newline = 0; 1704 state->mode.alt_screen = 0; 1705 state->mode.origin = 0; 1706 state->mode.leftrightmargin = 0; 1707 state->mode.bracketpaste = 0; 1708 state->mode.report_focus = 0; 1709 1710 state->vt->mode.ctrl8bit = 0; 1711 1712 for(int col = 0; col < state->cols; col++) 1713 if(col % 8 == 0) 1714 set_col_tabstop(state, col); 1715 else 1716 clear_col_tabstop(state, col); 1717 1718 for(int row = 0; row < state->rows; row++) 1719 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); 1720 1721 if(state->callbacks && state->callbacks->initpen) 1722 (*state->callbacks->initpen)(state->cbdata); 1723 1724 vterm_state_resetpen(state); 1725 1726 VTermEncoding *default_enc = state->vt->mode.utf8 ? 1727 vterm_lookup_encoding(ENC_UTF8, 'u') : 1728 vterm_lookup_encoding(ENC_SINGLE_94, 'B'); 1729 1730 for(int i = 0; i < 4; i++) { 1731 state->encoding[i].enc = default_enc; 1732 if(default_enc->init) 1733 (*default_enc->init)(default_enc, state->encoding[i].data); 1734 } 1735 1736 state->gl_set = 0; 1737 state->gr_set = 1; 1738 state->gsingle_set = 0; 1739 1740 state->protected_cell = 0; 1741 1742 // Initialise the props 1743 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1); 1744 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); 1745 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); 1746 1747 if(hard) { 1748 state->pos.row = 0; 1749 state->pos.col = 0; 1750 state->at_phantom = 0; 1751 1752 VTermRect rect = { 0, state->rows, 0, state->cols }; 1753 erase(state, rect, 0); 1754 } 1755 } 1756 1757 void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos) 1758 { 1759 *cursorpos = state->pos; 1760 } 1761 1762 void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user) 1763 { 1764 if(callbacks) { 1765 state->callbacks = callbacks; 1766 state->cbdata = user; 1767 1768 if(state->callbacks && state->callbacks->initpen) 1769 (*state->callbacks->initpen)(state->cbdata); 1770 } 1771 else { 1772 state->callbacks = NULL; 1773 state->cbdata = NULL; 1774 } 1775 } 1776 1777 void *vterm_state_get_cbdata(VTermState *state) 1778 { 1779 return state->cbdata; 1780 } 1781 1782 void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user) 1783 { 1784 if(fallbacks) { 1785 state->fallbacks = fallbacks; 1786 state->fbdata = user; 1787 } 1788 else { 1789 state->fallbacks = NULL; 1790 state->fbdata = NULL; 1791 } 1792 } 1793 1794 void *vterm_state_get_unrecognised_fbdata(VTermState *state) 1795 { 1796 return state->fbdata; 1797 } 1798 1799 int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) 1800 { 1801 /* Only store the new value of the property if usercode said it was happy. 1802 * This is especially important for altscreen switching */ 1803 if(state->callbacks && state->callbacks->settermprop) 1804 if(!(*state->callbacks->settermprop)(prop, val, state->cbdata)) 1805 return 0; 1806 1807 switch(prop) { 1808 case VTERM_PROP_TITLE: 1809 case VTERM_PROP_ICONNAME: 1810 // we don't store these, just transparently pass through 1811 return 1; 1812 case VTERM_PROP_CURSORVISIBLE: 1813 state->mode.cursor_visible = val->boolean; 1814 return 1; 1815 case VTERM_PROP_CURSORBLINK: 1816 state->mode.cursor_blink = val->boolean; 1817 return 1; 1818 case VTERM_PROP_CURSORSHAPE: 1819 state->mode.cursor_shape = val->number; 1820 return 1; 1821 case VTERM_PROP_REVERSE: 1822 state->mode.screen = val->boolean; 1823 return 1; 1824 case VTERM_PROP_ALTSCREEN: 1825 state->mode.alt_screen = val->boolean; 1826 if(state->mode.alt_screen) { 1827 VTermRect rect = { 1828 .start_row = 0, 1829 .start_col = 0, 1830 .end_row = state->rows, 1831 .end_col = state->cols, 1832 }; 1833 erase(state, rect, 0); 1834 } 1835 return 1; 1836 case VTERM_PROP_MOUSE: 1837 state->mouse_flags = 0; 1838 if(val->number) 1839 state->mouse_flags |= MOUSE_WANT_CLICK; 1840 if(val->number == VTERM_PROP_MOUSE_DRAG) 1841 state->mouse_flags |= MOUSE_WANT_DRAG; 1842 if(val->number == VTERM_PROP_MOUSE_MOVE) 1843 state->mouse_flags |= MOUSE_WANT_MOVE; 1844 return 1; 1845 1846 case VTERM_N_PROPS: 1847 return 0; 1848 } 1849 1850 return 0; 1851 } 1852 1853 void vterm_state_focus_in(VTermState *state) 1854 { 1855 if(state->mode.report_focus) 1856 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I"); 1857 } 1858 1859 void vterm_state_focus_out(VTermState *state) 1860 { 1861 if(state->mode.report_focus) 1862 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O"); 1863 } 1864 1865 const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row) 1866 { 1867 return state->lineinfo + row; 1868 } 1869