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