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