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#include "utf8.h" 9 10#ifdef DEBUG 11# define DEBUG_GLYPH_COMBINE 12#endif 13 14#define MOUSE_WANT_DRAG 0x01 15#define MOUSE_WANT_MOVE 0x02 16 17/* Some convenient wrappers to make callback functions easier */ 18 19static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos) 20{ 21 if(state->callbacks && state->callbacks->putglyph) 22 if((*state->callbacks->putglyph)(chars, width, pos, state->cbdata)) 23 return; 24 25 fprintf(stderr, "libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); 26} 27 28static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom) 29{ 30 if(state->pos.col == oldpos->col && state->pos.row == oldpos->row) 31 return; 32 33 if(cancel_phantom) 34 state->at_phantom = 0; 35 36 if(state->callbacks && state->callbacks->movecursor) 37 if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata)) 38 return; 39} 40 41static void erase(VTermState *state, VTermRect rect) 42{ 43 if(state->callbacks && state->callbacks->erase) 44 if((*state->callbacks->erase)(rect, state->cbdata)) 45 return; 46} 47 48static VTermState *vterm_state_new(VTerm *vt) 49{ 50 VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState)); 51 52 state->vt = vt; 53 54 state->rows = vt->rows; 55 state->cols = vt->cols; 56 57 // 90% grey so that pure white is brighter 58 state->default_fg.red = state->default_fg.green = state->default_fg.blue = 240; 59 state->default_bg.red = state->default_bg.green = state->default_bg.blue = 0; 60 61 state->bold_is_highbright = 0; 62 63 return state; 64} 65 66void vterm_state_free(VTermState *state) 67{ 68 vterm_allocator_free(state->vt, state->combine_chars); 69 vterm_allocator_free(state->vt, state); 70} 71 72static void scroll(VTermState *state, VTermRect rect, int downward, int rightward) 73{ 74 if(!downward && !rightward) 75 return; 76 77 if(state->callbacks && state->callbacks->scrollrect) 78 if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) 79 return; 80 81 if(state->callbacks) 82 vterm_scroll_rect(rect, downward, rightward, 83 state->callbacks->moverect, state->callbacks->erase, state->cbdata); 84} 85 86static void linefeed(VTermState *state) 87{ 88 if(state->pos.row == SCROLLREGION_END(state) - 1) { 89 VTermRect rect = { 90 .start_row = state->scrollregion_start, 91 .end_row = SCROLLREGION_END(state), 92 .start_col = 0, 93 .end_col = state->cols, 94 }; 95 96 scroll(state, rect, 1, 0); 97 } 98 else if(state->pos.row < state->rows-1) 99 state->pos.row++; 100} 101 102static void grow_combine_buffer(VTermState *state) 103{ 104 size_t new_size = state->combine_chars_size * 2; 105 uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0])); 106 107 memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0])); 108 109 vterm_allocator_free(state->vt, state->combine_chars); 110 state->combine_chars = new_chars; 111} 112 113static void set_col_tabstop(VTermState *state, int col) 114{ 115 unsigned char mask = 1 << (col & 7); 116 state->tabstops[col >> 3] |= mask; 117} 118 119static void clear_col_tabstop(VTermState *state, int col) 120{ 121 unsigned char mask = 1 << (col & 7); 122 state->tabstops[col >> 3] &= ~mask; 123} 124 125static int is_col_tabstop(VTermState *state, int col) 126{ 127 unsigned char mask = 1 << (col & 7); 128 return state->tabstops[col >> 3] & mask; 129} 130 131static void tab(VTermState *state, int count, int direction) 132{ 133 while(count--) 134 while(state->pos.col >= 0 && state->pos.col < state->cols-1) { 135 state->pos.col += direction; 136 137 if(is_col_tabstop(state, state->pos.col)) 138 break; 139 } 140} 141 142static int on_text(const char bytes[], size_t len, void *user) 143{ 144 VTermState *state = user; 145 uint32_t* chars; 146 147 VTermPos oldpos = state->pos; 148 149 // We'll have at most len codepoints 150 uint32_t codepoints[len]; 151 int npoints = 0; 152 size_t eaten = 0; 153 int i = 0; 154 155 VTermEncodingInstance *encoding = 156 !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] : 157 state->vt->is_utf8 ? &state->encoding_utf8 : 158 &state->encoding[state->gr_set]; 159 160 (*encoding->enc->decode)(encoding->enc, encoding->data, 161 codepoints, &npoints, len, bytes, &eaten, len); 162 163 /* This is a combining char. that needs to be merged with the previous 164 * glyph output */ 165 if(vterm_unicode_is_combining(codepoints[i])) { 166 /* See if the cursor has moved since */ 167 if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) { 168 size_t saved_i = 0; 169#ifdef DEBUG_GLYPH_COMBINE 170 int printpos; 171 printf("DEBUG: COMBINING SPLIT GLYPH of chars {"); 172 for(printpos = 0; state->combine_chars[printpos]; printpos++) 173 printf("U+%04x ", state->combine_chars[printpos]); 174 printf("} + {"); 175#endif 176 177 /* Find where we need to append these combining chars */ 178 while(state->combine_chars[saved_i]) 179 saved_i++; 180 181 /* Add extra ones */ 182 while(i < npoints && vterm_unicode_is_combining(codepoints[i])) { 183 if(saved_i >= state->combine_chars_size) 184 grow_combine_buffer(state); 185 state->combine_chars[saved_i++] = codepoints[i++]; 186 } 187 if(saved_i >= state->combine_chars_size) 188 grow_combine_buffer(state); 189 state->combine_chars[saved_i] = 0; 190 191#ifdef DEBUG_GLYPH_COMBINE 192 for(; state->combine_chars[printpos]; printpos++) 193 printf("U+%04x ", state->combine_chars[printpos]); 194 printf("}\n"); 195#endif 196 197 /* Now render it */ 198 putglyph(state, state->combine_chars, state->combine_width, state->combine_pos); 199 } 200 else { 201 fprintf(stderr, "libvterm: TODO: Skip over split char+combining\n"); 202 } 203 } 204 205 for(; i < npoints; i++) { 206 // Try to find combining characters following this 207 int glyph_starts = i; 208 int glyph_ends; 209 int width = 0; 210 211 for(glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++) 212 if(!vterm_unicode_is_combining(codepoints[glyph_ends])) 213 break; 214 215 chars = alloca(sizeof(uint32_t) * (glyph_ends - glyph_starts + 1)); 216 217 for( ; i < glyph_ends; i++) { 218 chars[i - glyph_starts] = codepoints[i]; 219 width += vterm_unicode_width(codepoints[i]); 220 } 221 222 chars[glyph_ends - glyph_starts] = 0; 223 i--; 224 225#ifdef DEBUG_GLYPH_COMBINE 226 { 227 int printpos; 228 printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts); 229 for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++) 230 printf("U+%04x ", chars[printpos]); 231 printf("}, onscreen width %d\n", width); 232 } 233#endif 234 235 if(state->at_phantom) { 236 linefeed(state); 237 state->pos.col = 0; 238 state->at_phantom = 0; 239 } 240 241 if(state->mode.insert) { 242 /* TODO: This will be a little inefficient for large bodies of text, as 243 * it'll have to 'ICH' effectively before every glyph. We should scan 244 * ahead and ICH as many times as required 245 */ 246 VTermRect rect = { 247 .start_row = state->pos.row, 248 .end_row = state->pos.row + 1, 249 .start_col = state->pos.col, 250 .end_col = state->cols, 251 }; 252 scroll(state, rect, 0, -1); 253 } 254 putglyph(state, chars, width, state->pos); 255 256 if(i == npoints - 1) { 257 /* End of the buffer. Save the chars in case we have to combine with 258 * more on the next call */ 259 unsigned int save_i; 260 for(save_i = 0; chars[save_i]; save_i++) { 261 if(save_i >= state->combine_chars_size) 262 grow_combine_buffer(state); 263 state->combine_chars[save_i] = chars[save_i]; 264 } 265 if(save_i >= state->combine_chars_size) 266 grow_combine_buffer(state); 267 state->combine_chars[save_i] = 0; 268 state->combine_width = width; 269 state->combine_pos = state->pos; 270 } 271 272 if(state->pos.col + width >= state->cols) { 273 if(state->mode.autowrap) 274 state->at_phantom = 1; 275 } 276 else { 277 state->pos.col += width; 278 } 279 } 280 281 updatecursor(state, &oldpos, 0); 282 283 return eaten; 284} 285 286static int on_control(unsigned char control, void *user) 287{ 288 VTermState *state = user; 289 290 VTermPos oldpos = state->pos; 291 292 switch(control) { 293 case 0x07: // BEL - ECMA-48 8.3.3 294 if(state->callbacks && state->callbacks->bell) 295 (*state->callbacks->bell)(state->cbdata); 296 break; 297 298 case 0x08: // BS - ECMA-48 8.3.5 299 if(state->pos.col > 0) 300 state->pos.col--; 301 break; 302 303 case 0x09: // HT - ECMA-48 8.3.60 304 tab(state, 1, +1); 305 break; 306 307 case 0x0a: // LF - ECMA-48 8.3.74 308 case 0x0b: // VT 309 case 0x0c: // FF 310 linefeed(state); 311 if(state->mode.newline) 312 state->pos.col = 0; 313 break; 314 315 case 0x0d: // CR - ECMA-48 8.3.15 316 state->pos.col = 0; 317 break; 318 319 case 0x0e: // LS1 - ECMA-48 8.3.76 320 state->gl_set = 1; 321 break; 322 323 case 0x0f: // LS0 - ECMA-48 8.3.75 324 state->gl_set = 0; 325 break; 326 327 case 0x84: // IND - DEPRECATED but implemented for completeness 328 linefeed(state); 329 break; 330 331 case 0x85: // NEL - ECMA-48 8.3.86 332 linefeed(state); 333 state->pos.col = 0; 334 break; 335 336 case 0x88: // HTS - ECMA-48 8.3.62 337 set_col_tabstop(state, state->pos.col); 338 break; 339 340 case 0x8d: // RI - ECMA-48 8.3.104 341 if(state->pos.row == state->scrollregion_start) { 342 VTermRect rect = { 343 .start_row = state->scrollregion_start, 344 .end_row = SCROLLREGION_END(state), 345 .start_col = 0, 346 .end_col = state->cols, 347 }; 348 349 scroll(state, rect, -1, 0); 350 } 351 else if(state->pos.row > 0) 352 state->pos.row--; 353 break; 354 355 default: 356 return 0; 357 } 358 359 updatecursor(state, &oldpos, 1); 360 361 return 1; 362} 363 364static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row) 365{ 366 modifiers <<= 2; 367 368 switch(state->mouse_protocol) { 369 case MOUSE_X10: 370 if(col + 0x21 > 0xff) 371 col = 0xff - 0x21; 372 if(row + 0x21 > 0xff) 373 row = 0xff - 0x21; 374 375 if(!pressed) 376 code = 3; 377 378 vterm_push_output_sprintf(state->vt, "\e[M%c%c%c", 379 (code | modifiers) + 0x20, col + 0x21, row + 0x21); 380 break; 381 382 case MOUSE_UTF8: 383 { 384 char utf8[18]; size_t len = 0; 385 386 if(!pressed) 387 code = 3; 388 389 len += fill_utf8((code | modifiers) + 0x20, utf8 + len); 390 len += fill_utf8(col + 0x21, utf8 + len); 391 len += fill_utf8(row + 0x21, utf8 + len); 392 393 vterm_push_output_sprintf(state->vt, "\e[M%s", utf8); 394 } 395 break; 396 397 case MOUSE_SGR: 398 vterm_push_output_sprintf(state->vt, "\e[<%d;%d;%d%c", 399 code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm'); 400 break; 401 402 case MOUSE_RXVT: 403 if(!pressed) 404 code = 3; 405 406 vterm_push_output_sprintf(state->vt, "\e[%d;%d;%dM", 407 code | modifiers, col + 1, row + 1); 408 break; 409 } 410} 411 412static void mousefunc(int col, int row, int button, int pressed, int modifiers, void *data) 413{ 414 VTermState *state = data; 415 416 int old_col = state->mouse_col; 417 int old_row = state->mouse_row; 418 int old_buttons = state->mouse_buttons; 419 420 state->mouse_col = col; 421 state->mouse_row = row; 422 423 if(button > 0 && button <= 3) { 424 if(pressed) 425 state->mouse_buttons |= (1 << (button-1)); 426 else 427 state->mouse_buttons &= ~(1 << (button-1)); 428 } 429 430 modifiers &= 0x7; 431 432 433 /* Most of the time we don't get button releases from 4/5 */ 434 if(state->mouse_buttons != old_buttons || button >= 4) { 435 if(button < 4) { 436 output_mouse(state, button-1, pressed, modifiers, col, row); 437 } 438 else if(button < 6) { 439 output_mouse(state, button-4 + 0x40, pressed, modifiers, col, row); 440 } 441 } 442 else if(col != old_col || row != old_row) { 443 if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) || 444 (state->mouse_flags & MOUSE_WANT_MOVE)) { 445 int button = state->mouse_buttons & 0x01 ? 1 : 446 state->mouse_buttons & 0x02 ? 2 : 447 state->mouse_buttons & 0x04 ? 3 : 4; 448 output_mouse(state, button-1 + 0x20, 1, modifiers, col, row); 449 } 450 } 451} 452 453static int settermprop_bool(VTermState *state, VTermProp prop, int v) 454{ 455 VTermValue val; 456 val.boolean = v; 457 458#ifdef DEBUG 459 if(VTERM_VALUETYPE_BOOL != vterm_get_prop_type(prop)) { 460 fprintf(stderr, "Cannot set prop %d as it has type %d, not type BOOL\n", 461 prop, vterm_get_prop_type(prop)); 462 return -1; 463 } 464#endif 465 466 if(state->callbacks && state->callbacks->settermprop) 467 if((*state->callbacks->settermprop)(prop, &val, state->cbdata)) 468 return 1; 469 470 return 0; 471} 472 473static int settermprop_int(VTermState *state, VTermProp prop, int v) 474{ 475 VTermValue val; 476 val.number = v; 477 478#ifdef DEBUG 479 if(VTERM_VALUETYPE_INT != vterm_get_prop_type(prop)) { 480 fprintf(stderr, "Cannot set prop %d as it has type %d, not type int\n", 481 prop, vterm_get_prop_type(prop)); 482 return -1; 483 } 484#endif 485 486 if(state->callbacks && state->callbacks->settermprop) 487 if((*state->callbacks->settermprop)(prop, &val, state->cbdata)) 488 return 1; 489 490 return 0; 491} 492 493static int settermprop_string(VTermState *state, VTermProp prop, const char *str, size_t len) 494{ 495 char strvalue[len+1]; 496 VTermValue val; 497 498 strncpy(strvalue, str, len); 499 strvalue[len] = 0; 500 501 val.string = strvalue; 502 503#ifdef DEBUG 504 if(VTERM_VALUETYPE_STRING != vterm_get_prop_type(prop)) { 505 fprintf(stderr, "Cannot set prop %d as it has type %d, not type STRING\n", 506 prop, vterm_get_prop_type(prop)); 507 return -1; 508 } 509#endif 510 511 if(state->callbacks && state->callbacks->settermprop) 512 if((*state->callbacks->settermprop)(prop, &val, state->cbdata)) 513 return 1; 514 515 return 0; 516} 517 518static void savecursor(VTermState *state, int save) 519{ 520 if(save) { 521 state->saved.pos = state->pos; 522 state->saved.mode.cursor_visible = state->mode.cursor_visible; 523 state->saved.mode.cursor_blink = state->mode.cursor_blink; 524 state->saved.mode.cursor_shape = state->mode.cursor_shape; 525 526 vterm_state_savepen(state, 1); 527 } 528 else { 529 VTermPos oldpos = state->pos; 530 531 state->pos = state->saved.pos; 532 state->mode.cursor_visible = state->saved.mode.cursor_visible; 533 state->mode.cursor_blink = state->saved.mode.cursor_blink; 534 state->mode.cursor_shape = state->saved.mode.cursor_shape; 535 536 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->mode.cursor_visible); 537 settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->mode.cursor_blink); 538 settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->mode.cursor_shape); 539 540 vterm_state_savepen(state, 0); 541 542 updatecursor(state, &oldpos, 1); 543 } 544} 545 546static void altscreen(VTermState *state, int alt) 547{ 548 /* Only store that we're on the alternate screen if the usercode said it 549 * switched */ 550 if(!settermprop_bool(state, VTERM_PROP_ALTSCREEN, alt)) 551 return; 552 553 state->mode.alt_screen = alt; 554 if(alt) { 555 VTermRect rect = { 556 .start_row = 0, 557 .start_col = 0, 558 .end_row = state->rows, 559 .end_col = state->cols, 560 }; 561 erase(state, rect); 562 } 563} 564 565static int on_escape(const char *bytes, size_t len, void *user) 566{ 567 VTermState *state = user; 568 569 /* Easier to decode this from the first byte, even though the final 570 * byte terminates it 571 */ 572 switch(bytes[0]) { 573 case '#': 574 if(len != 2) 575 return 0; 576 577 switch(bytes[1]) { 578 case '8': // DECALN 579 { 580 VTermPos pos; 581 uint32_t E[] = { 'E', 0 }; 582 for(pos.row = 0; pos.row < state->rows; pos.row++) 583 for(pos.col = 0; pos.col < state->cols; pos.col++) 584 putglyph(state, E, 1, pos); 585 break; 586 } 587 588 default: 589 return 0; 590 } 591 return 2; 592 593 case '(': case ')': case '*': case '+': // SCS 594 if(len != 2) 595 return 0; 596 597 { 598 int setnum = bytes[0] - 0x28; 599 VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]); 600 601 if(newenc) { 602 state->encoding[setnum].enc = newenc; 603 604 if(newenc->init) 605 (*newenc->init)(newenc, state->encoding[setnum].data); 606 } 607 } 608 609 return 2; 610 611 case '7': // DECSC 612 savecursor(state, 1); 613 return 1; 614 615 case '8': // DECRC 616 savecursor(state, 0); 617 return 1; 618 619 case '=': // DECKPAM 620 state->mode.keypad = 1; 621 return 1; 622 623 case '>': // DECKPNM 624 state->mode.keypad = 0; 625 return 1; 626 627 case 'c': // RIS - ECMA-48 8.3.105 628 { 629 VTermPos oldpos = state->pos; 630 vterm_state_reset(state, 1); 631 if(state->callbacks && state->callbacks->movecursor) 632 (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata); 633 return 1; 634 } 635 636 case 'n': // LS2 - ECMA-48 8.3.78 637 state->gl_set = 2; 638 return 1; 639 640 case 'o': // LS3 - ECMA-48 8.3.80 641 state->gl_set = 3; 642 return 1; 643 644 default: 645 return 0; 646 } 647} 648 649static void set_mode(VTermState *state, int num, int val) 650{ 651 switch(num) { 652 case 4: // IRM - ECMA-48 7.2.10 653 state->mode.insert = val; 654 break; 655 656 case 20: // LNM - ANSI X3.4-1977 657 state->mode.newline = val; 658 break; 659 660 default: 661 fprintf(stderr, "libvterm: Unknown mode %d\n", num); 662 return; 663 } 664} 665 666static void set_dec_mode(VTermState *state, int num, int val) 667{ 668 switch(num) { 669 case 1: 670 state->mode.cursor = val; 671 break; 672 673 case 5: 674 settermprop_bool(state, VTERM_PROP_REVERSE, val); 675 break; 676 677 case 6: // DECOM - origin mode 678 { 679 VTermPos oldpos = state->pos; 680 state->mode.origin = val; 681 state->pos.row = state->mode.origin ? state->scrollregion_start : 0; 682 state->pos.col = 0; 683 updatecursor(state, &oldpos, 1); 684 } 685 break; 686 687 case 7: 688 state->mode.autowrap = val; 689 break; 690 691 case 12: 692 state->mode.cursor_blink = val; 693 settermprop_bool(state, VTERM_PROP_CURSORBLINK, val); 694 break; 695 696 case 25: 697 state->mode.cursor_visible = val; 698 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val); 699 break; 700 701 case 1000: 702 case 1002: 703 case 1003: 704 if(val) { 705 state->mouse_col = 0; 706 state->mouse_row = 0; 707 state->mouse_buttons = 0; 708 709 state->mouse_flags = 0; 710 state->mouse_protocol = MOUSE_X10; 711 712 if(num == 1002) 713 state->mouse_flags |= MOUSE_WANT_DRAG; 714 if(num == 1003) 715 state->mouse_flags |= MOUSE_WANT_MOVE; 716 } 717 718 if(state->callbacks && state->callbacks->setmousefunc) 719 (*state->callbacks->setmousefunc)(val ? mousefunc : NULL, state, state->cbdata); 720 721 break; 722 723 case 1005: 724 state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10; 725 break; 726 727 case 1006: 728 state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10; 729 break; 730 731 case 1015: 732 state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10; 733 break; 734 735 case 1047: 736 altscreen(state, val); 737 break; 738 739 case 1048: 740 savecursor(state, val); 741 break; 742 743 case 1049: 744 altscreen(state, val); 745 savecursor(state, val); 746 break; 747 748 default: 749 fprintf(stderr, "libvterm: Unknown DEC mode %d\n", num); 750 return; 751 } 752} 753 754static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) 755{ 756 VTermState *state = user; 757 int leader_byte = 0; 758 int intermed_byte = 0; 759 VTermPos oldpos; 760 761 // Some temporaries for later code 762 int count, val; 763 int row, col; 764 VTermRect rect; 765 766 if(leader && leader[0]) { 767 if(leader[1]) // longer than 1 char 768 return 0; 769 770 switch(leader[0]) { 771 case '?': 772 case '>': 773 leader_byte = leader[0]; 774 break; 775 default: 776 return 0; 777 } 778 } 779 780 if(intermed && intermed[0]) { 781 if(intermed[1]) // longer than 1 char 782 return 0; 783 784 switch(intermed[0]) { 785 case ' ': 786 intermed_byte = intermed[0]; 787 break; 788 default: 789 return 0; 790 } 791 } 792 793 oldpos = state->pos; 794 795#define LEADER(l,b) ((l << 8) | b) 796#define INTERMED(i,b) ((i << 16) | b) 797 798 switch(intermed_byte << 16 | leader_byte << 8 | command) { 799 case 0x40: // ICH - ECMA-48 8.3.64 800 count = CSI_ARG_COUNT(args[0]); 801 802 rect.start_row = state->pos.row; 803 rect.end_row = state->pos.row + 1; 804 rect.start_col = state->pos.col; 805 rect.end_col = state->cols; 806 807 scroll(state, rect, 0, -count); 808 809 break; 810 811 case 0x41: // CUU - ECMA-48 8.3.22 812 count = CSI_ARG_COUNT(args[0]); 813 state->pos.row -= count; 814 state->at_phantom = 0; 815 break; 816 817 case 0x42: // CUD - ECMA-48 8.3.19 818 count = CSI_ARG_COUNT(args[0]); 819 state->pos.row += count; 820 state->at_phantom = 0; 821 break; 822 823 case 0x43: // CUF - ECMA-48 8.3.20 824 count = CSI_ARG_COUNT(args[0]); 825 state->pos.col += count; 826 state->at_phantom = 0; 827 break; 828 829 case 0x44: // CUB - ECMA-48 8.3.18 830 count = CSI_ARG_COUNT(args[0]); 831 state->pos.col -= count; 832 state->at_phantom = 0; 833 break; 834 835 case 0x45: // CNL - ECMA-48 8.3.12 836 count = CSI_ARG_COUNT(args[0]); 837 state->pos.col = 0; 838 state->pos.row += count; 839 state->at_phantom = 0; 840 break; 841 842 case 0x46: // CPL - ECMA-48 8.3.13 843 count = CSI_ARG_COUNT(args[0]); 844 state->pos.col = 0; 845 state->pos.row -= count; 846 state->at_phantom = 0; 847 break; 848 849 case 0x47: // CHA - ECMA-48 8.3.9 850 val = CSI_ARG_OR(args[0], 1); 851 state->pos.col = val-1; 852 state->at_phantom = 0; 853 break; 854 855 case 0x48: // CUP - ECMA-48 8.3.21 856 row = CSI_ARG_OR(args[0], 1); 857 col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); 858 // zero-based 859 state->pos.row = row-1; 860 state->pos.col = col-1; 861 if(state->mode.origin) 862 state->pos.row += state->scrollregion_start; 863 state->at_phantom = 0; 864 break; 865 866 case 0x49: // CHT - ECMA-48 8.3.10 867 count = CSI_ARG_COUNT(args[0]); 868 tab(state, count, +1); 869 break; 870 871 case 0x4a: // ED - ECMA-48 8.3.39 872 switch(CSI_ARG(args[0])) { 873 case CSI_ARG_MISSING: 874 case 0: 875 rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; 876 rect.start_col = state->pos.col; rect.end_col = state->cols; 877 if(rect.end_col > rect.start_col) 878 erase(state, rect); 879 880 rect.start_row = state->pos.row + 1; rect.end_row = state->rows; 881 rect.start_col = 0; 882 if(rect.end_row > rect.start_row) 883 erase(state, rect); 884 break; 885 886 case 1: 887 rect.start_row = 0; rect.end_row = state->pos.row; 888 rect.start_col = 0; rect.end_col = state->cols; 889 if(rect.end_col > rect.start_col) 890 erase(state, rect); 891 892 rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; 893 rect.end_col = state->pos.col + 1; 894 if(rect.end_row > rect.start_row) 895 erase(state, rect); 896 break; 897 898 case 2: 899 rect.start_row = 0; rect.end_row = state->rows; 900 rect.start_col = 0; rect.end_col = state->cols; 901 erase(state, rect); 902 break; 903 } 904 break; 905 906 case 0x4b: // EL - ECMA-48 8.3.41 907 rect.start_row = state->pos.row; 908 rect.end_row = state->pos.row + 1; 909 910 switch(CSI_ARG(args[0])) { 911 case CSI_ARG_MISSING: 912 case 0: 913 rect.start_col = state->pos.col; rect.end_col = state->cols; break; 914 case 1: 915 rect.start_col = 0; rect.end_col = state->pos.col + 1; break; 916 case 2: 917 rect.start_col = 0; rect.end_col = state->cols; break; 918 default: 919 return 0; 920 } 921 922 if(rect.end_col > rect.start_col) 923 erase(state, rect); 924 925 break; 926 927 case 0x4c: // IL - ECMA-48 8.3.67 928 count = CSI_ARG_COUNT(args[0]); 929 930 rect.start_row = state->pos.row; 931 rect.end_row = SCROLLREGION_END(state); 932 rect.start_col = 0; 933 rect.end_col = state->cols; 934 935 scroll(state, rect, -count, 0); 936 937 break; 938 939 case 0x4d: // DL - ECMA-48 8.3.32 940 count = CSI_ARG_COUNT(args[0]); 941 942 rect.start_row = state->pos.row; 943 rect.end_row = SCROLLREGION_END(state); 944 rect.start_col = 0; 945 rect.end_col = state->cols; 946 947 scroll(state, rect, count, 0); 948 949 break; 950 951 case 0x50: // DCH - ECMA-48 8.3.26 952 count = CSI_ARG_COUNT(args[0]); 953 954 rect.start_row = state->pos.row; 955 rect.end_row = state->pos.row + 1; 956 rect.start_col = state->pos.col; 957 rect.end_col = state->cols; 958 959 scroll(state, rect, 0, count); 960 961 break; 962 963 case 0x53: // SU - ECMA-48 8.3.147 964 count = CSI_ARG_COUNT(args[0]); 965 966 rect.start_row = state->scrollregion_start; 967 rect.end_row = SCROLLREGION_END(state); 968 rect.start_col = 0; 969 rect.end_col = state->cols; 970 971 scroll(state, rect, count, 0); 972 973 break; 974 975 case 0x54: // SD - ECMA-48 8.3.113 976 count = CSI_ARG_COUNT(args[0]); 977 978 rect.start_row = state->scrollregion_start; 979 rect.end_row = SCROLLREGION_END(state); 980 rect.start_col = 0; 981 rect.end_col = state->cols; 982 983 scroll(state, rect, -count, 0); 984 985 break; 986 987 case 0x58: // ECH - ECMA-48 8.3.38 988 count = CSI_ARG_COUNT(args[0]); 989 990 rect.start_row = state->pos.row; 991 rect.end_row = state->pos.row + 1; 992 rect.start_col = state->pos.col; 993 rect.end_col = state->pos.col + count; 994 995 erase(state, rect); 996 break; 997 998 case 0x5a: // CBT - ECMA-48 8.3.7 999 count = CSI_ARG_COUNT(args[0]); 1000 tab(state, count, -1); 1001 break; 1002 1003 case 0x60: // HPA - ECMA-48 8.3.57 1004 col = CSI_ARG_OR(args[0], 1); 1005 state->pos.col = col-1; 1006 state->at_phantom = 0; 1007 break; 1008 1009 case 0x61: // HPR - ECMA-48 8.3.59 1010 count = CSI_ARG_COUNT(args[0]); 1011 state->pos.col += count; 1012 state->at_phantom = 0; 1013 break; 1014 1015 case 0x63: // DA - ECMA-48 8.3.24 1016 val = CSI_ARG_OR(args[0], 0); 1017 if(val == 0) 1018 // DEC VT100 response 1019 vterm_push_output_sprintf(state->vt, "\e[?1;2c"); 1020 break; 1021 1022 case LEADER('>', 0x63): // DEC secondary Device Attributes 1023 vterm_push_output_sprintf(state->vt, "\e[>%d;%d;%dc", 0, 100, 0); 1024 break; 1025 1026 case 0x64: // VPA - ECMA-48 8.3.158 1027 row = CSI_ARG_OR(args[0], 1); 1028 state->pos.row = row-1; 1029 if(state->mode.origin) 1030 state->pos.row += state->scrollregion_start; 1031 state->at_phantom = 0; 1032 break; 1033 1034 case 0x65: // VPR - ECMA-48 8.3.160 1035 count = CSI_ARG_COUNT(args[0]); 1036 state->pos.row += count; 1037 state->at_phantom = 0; 1038 break; 1039 1040 case 0x66: // HVP - ECMA-48 8.3.63 1041 row = CSI_ARG_OR(args[0], 1); 1042 col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); 1043 // zero-based 1044 state->pos.row = row-1; 1045 state->pos.col = col-1; 1046 if(state->mode.origin) 1047 state->pos.row += state->scrollregion_start; 1048 state->at_phantom = 0; 1049 break; 1050 1051 case 0x67: // TBC - ECMA-48 8.3.154 1052 val = CSI_ARG_OR(args[0], 0); 1053 1054 switch(val) { 1055 case 0: 1056 clear_col_tabstop(state, state->pos.col); 1057 break; 1058 case 3: 1059 case 5: 1060 for(col = 0; col < state->cols; col++) 1061 clear_col_tabstop(state, col); 1062 break; 1063 case 1: 1064 case 2: 1065 case 4: 1066 break; 1067 /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */ 1068 default: 1069 return 0; 1070 } 1071 break; 1072 1073 case 0x68: // SM - ECMA-48 8.3.125 1074 if(!CSI_ARG_IS_MISSING(args[0])) 1075 set_mode(state, CSI_ARG(args[0]), 1); 1076 break; 1077 1078 case LEADER('?', 0x68): // DEC private mode set 1079 if(!CSI_ARG_IS_MISSING(args[0])) 1080 set_dec_mode(state, CSI_ARG(args[0]), 1); 1081 break; 1082 1083 case 0x6a: // HPB - ECMA-48 8.3.58 1084 count = CSI_ARG_COUNT(args[0]); 1085 state->pos.col -= count; 1086 state->at_phantom = 0; 1087 break; 1088 1089 case 0x6b: // VPB - ECMA-48 8.3.159 1090 count = CSI_ARG_COUNT(args[0]); 1091 state->pos.row -= count; 1092 state->at_phantom = 0; 1093 break; 1094 1095 case 0x6c: // RM - ECMA-48 8.3.106 1096 if(!CSI_ARG_IS_MISSING(args[0])) 1097 set_mode(state, CSI_ARG(args[0]), 0); 1098 break; 1099 1100 case LEADER('?', 0x6c): // DEC private mode reset 1101 if(!CSI_ARG_IS_MISSING(args[0])) 1102 set_dec_mode(state, CSI_ARG(args[0]), 0); 1103 break; 1104 1105 case 0x6d: // SGR - ECMA-48 8.3.117 1106 vterm_state_setpen(state, args, argcount); 1107 break; 1108 1109 case 0x6e: // DSR - ECMA-48 8.3.35 1110 val = CSI_ARG_OR(args[0], 0); 1111 1112 switch(val) { 1113 case 0: case 1: case 2: case 3: case 4: 1114 // ignore - these are replies 1115 break; 1116 case 5: 1117 vterm_push_output_sprintf(state->vt, "\e[0n"); 1118 break; 1119 case 6: 1120 vterm_push_output_sprintf(state->vt, "\e[%d;%dR", state->pos.row + 1, state->pos.col + 1); 1121 break; 1122 } 1123 break; 1124 1125 case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset 1126 vterm_state_reset(state, 0); 1127 break; 1128 1129 case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape 1130 val = CSI_ARG_OR(args[0], 1); 1131 1132 switch(val) { 1133 case 0: case 1: 1134 state->mode.cursor_blink = 1; 1135 state->mode.cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK; 1136 break; 1137 case 2: 1138 state->mode.cursor_blink = 0; 1139 state->mode.cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK; 1140 break; 1141 case 3: 1142 state->mode.cursor_blink = 1; 1143 state->mode.cursor_shape = VTERM_PROP_CURSORSHAPE_UNDERLINE; 1144 break; 1145 case 4: 1146 state->mode.cursor_blink = 0; 1147 state->mode.cursor_shape = VTERM_PROP_CURSORSHAPE_UNDERLINE; 1148 break; 1149 } 1150 1151 settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->mode.cursor_blink); 1152 settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->mode.cursor_shape); 1153 break; 1154 1155 case 0x72: // DECSTBM - DEC custom 1156 state->scrollregion_start = CSI_ARG_OR(args[0], 1) - 1; 1157 state->scrollregion_end = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); 1158 if(state->scrollregion_start == 0 && state->scrollregion_end == state->rows) 1159 state->scrollregion_end = -1; 1160 break; 1161 1162 case 0x73: // ANSI SAVE 1163 savecursor(state, 1); 1164 break; 1165 1166 case 0x75: // ANSI RESTORE 1167 savecursor(state, 0); 1168 break; 1169 1170 default: 1171 return 0; 1172 } 1173 1174#define LBOUND(v,min) if((v) < (min)) (v) = (min) 1175#define UBOUND(v,max) if((v) > (max)) (v) = (max) 1176 1177 LBOUND(state->pos.col, 0); 1178 UBOUND(state->pos.col, state->cols-1); 1179 1180 if(state->mode.origin) { 1181 LBOUND(state->pos.row, state->scrollregion_start); 1182 UBOUND(state->pos.row, state->scrollregion_end-1); 1183 } 1184 else { 1185 LBOUND(state->pos.row, 0); 1186 UBOUND(state->pos.row, state->rows-1); 1187 } 1188 1189 updatecursor(state, &oldpos, 1); 1190 1191 return 1; 1192} 1193 1194static int on_osc(const char *command, size_t cmdlen, void *user) 1195{ 1196 VTermState *state = user; 1197 1198 if(cmdlen < 2) 1199 return 0; 1200 1201 if(strneq(command, "0;", 2)) { 1202 settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2); 1203 settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2); 1204 return 1; 1205 } 1206 else if(strneq(command, "1;", 2)) { 1207 settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2); 1208 return 1; 1209 } 1210 else if(strneq(command, "2;", 2)) { 1211 settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2); 1212 return 1; 1213 } 1214 1215 return 0; 1216} 1217 1218static void request_status_string(VTermState *state, const char *command, size_t cmdlen) 1219{ 1220 if(cmdlen == 1) 1221 switch(command[0]) { 1222 case 'r': // Query DECSTBM 1223 vterm_push_output_sprintf(state->vt, "\eP1$r%d;%dr\e\\", state->scrollregion_start+1, SCROLLREGION_END(state)); 1224 return; 1225 } 1226 1227 if(cmdlen == 2) 1228 if(strneq(command, " q", 2)) { 1229 int reply = 0; 1230 switch(state->mode.cursor_shape) { 1231 case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break; 1232 case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break; 1233 } 1234 if(state->mode.cursor_blink) 1235 reply--; 1236 vterm_push_output_sprintf(state->vt, "\eP1$r%d q\e\\", reply); 1237 return; 1238 } 1239 1240 vterm_push_output_sprintf(state->vt, "\eP0$r%.s\e\\", (int)cmdlen, command); 1241} 1242 1243static int on_dcs(const char *command, size_t cmdlen, void *user) 1244{ 1245 VTermState *state = user; 1246 1247 if(cmdlen >= 2 && strneq(command, "$q", 2)) { 1248 request_status_string(state, command+2, cmdlen-2); 1249 return 1; 1250 } 1251 1252 return 0; 1253} 1254 1255static int on_resize(int rows, int cols, void *user) 1256{ 1257 VTermState *state = user; 1258 VTermPos oldpos = state->pos; 1259 1260 if(cols != state->cols) { 1261 unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8); 1262 1263 /* TODO: This can all be done much more efficiently bytewise */ 1264 int col; 1265 for(col = 0; col < state->cols && col < cols; col++) { 1266 unsigned char mask = 1 << (col & 7); 1267 if(state->tabstops[col >> 3] & mask) 1268 newtabstops[col >> 3] |= mask; 1269 else 1270 newtabstops[col >> 3] &= ~mask; 1271 } 1272 1273 for( ; col < cols; col++) { 1274 unsigned char mask = 1 << (col & 7); 1275 if(col % 8 == 0) 1276 newtabstops[col >> 3] |= mask; 1277 else 1278 newtabstops[col >> 3] &= ~mask; 1279 } 1280 1281 vterm_allocator_free(state->vt, state->tabstops); 1282 state->tabstops = newtabstops; 1283 } 1284 1285 state->rows = rows; 1286 state->cols = cols; 1287 1288 if(state->pos.row >= rows) 1289 state->pos.row = rows - 1; 1290 if(state->pos.col >= cols) 1291 state->pos.col = cols - 1; 1292 1293 if(state->at_phantom && state->pos.col < cols-1) { 1294 state->at_phantom = 0; 1295 state->pos.col++; 1296 } 1297 1298 if(state->callbacks && state->callbacks->resize) 1299 (*state->callbacks->resize)(rows, cols, state->cbdata); 1300 1301 updatecursor(state, &oldpos, 1); 1302 1303 return 1; 1304} 1305 1306static const VTermParserCallbacks parser_callbacks = { 1307 .text = on_text, 1308 .control = on_control, 1309 .escape = on_escape, 1310 .csi = on_csi, 1311 .osc = on_osc, 1312 .dcs = on_dcs, 1313 .resize = on_resize, 1314}; 1315 1316VTermState *vterm_obtain_state(VTerm *vt) 1317{ 1318 VTermState *state; 1319 if(vt->state) 1320 return vt->state; 1321 1322 state = vterm_state_new(vt); 1323 vt->state = state; 1324 1325 state->combine_chars_size = 16; 1326 state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0])); 1327 1328 state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8); 1329 1330 state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u'); 1331 if(*state->encoding_utf8.enc->init) 1332 (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); 1333 1334 vterm_set_parser_callbacks(vt, &parser_callbacks, state); 1335 1336 return state; 1337} 1338 1339void vterm_state_reset(VTermState *state, int hard) 1340{ 1341 int col, i; 1342 VTermEncoding *default_enc; 1343 1344 state->scrollregion_start = 0; 1345 state->scrollregion_end = -1; 1346 1347 state->mode.keypad = 0; 1348 state->mode.cursor = 0; 1349 state->mode.autowrap = 1; 1350 state->mode.insert = 0; 1351 state->mode.newline = 0; 1352 state->mode.cursor_visible = 1; 1353 state->mode.cursor_blink = 1; 1354 state->mode.cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK; 1355 state->mode.alt_screen = 0; 1356 state->mode.origin = 0; 1357 1358 for(col = 0; col < state->cols; col++) 1359 if(col % 8 == 0) 1360 set_col_tabstop(state, col); 1361 else 1362 clear_col_tabstop(state, col); 1363 1364 if(state->callbacks && state->callbacks->initpen) 1365 (*state->callbacks->initpen)(state->cbdata); 1366 1367 vterm_state_resetpen(state); 1368 1369 default_enc = state->vt->is_utf8 ? 1370 vterm_lookup_encoding(ENC_UTF8, 'u') : 1371 vterm_lookup_encoding(ENC_SINGLE_94, 'B'); 1372 1373 for(i = 0; i < 4; i++) { 1374 state->encoding[i].enc = default_enc; 1375 if(default_enc->init) 1376 (*default_enc->init)(default_enc, state->encoding[i].data); 1377 } 1378 1379 state->gl_set = 0; 1380 state->gr_set = 0; 1381 1382 // Initialise the props 1383 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->mode.cursor_visible); 1384 settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->mode.cursor_blink); 1385 settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->mode.cursor_shape); 1386 1387 if(hard) { 1388 VTermRect rect = { 0, state->rows, 0, state->cols }; 1389 1390 state->pos.row = 0; 1391 state->pos.col = 0; 1392 state->at_phantom = 0; 1393 1394 erase(state, rect); 1395 } 1396} 1397 1398void vterm_state_get_cursorpos(VTermState *state, VTermPos *cursorpos) 1399{ 1400 *cursorpos = state->pos; 1401} 1402 1403void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user) 1404{ 1405 if(callbacks) { 1406 state->callbacks = callbacks; 1407 state->cbdata = user; 1408 1409 if(state->callbacks && state->callbacks->initpen) 1410 (*state->callbacks->initpen)(state->cbdata); 1411 } 1412 else { 1413 state->callbacks = NULL; 1414 state->cbdata = NULL; 1415 } 1416} 1417