state.c (64961B)
1 #include <assert.h> 2 #include <stdio.h> 3 #include <string.h> 4 5 #include "nvim/grid.h" 6 #include "nvim/mbyte.h" 7 #include "nvim/vterm/encoding.h" 8 #include "nvim/vterm/parser.h" 9 #include "nvim/vterm/pen.h" 10 #include "nvim/vterm/state.h" 11 #include "nvim/vterm/vterm.h" 12 #include "nvim/vterm/vterm_internal_defs.h" 13 14 #include "vterm/state.c.generated.h" 15 16 #define strneq(a, b, n) (strncmp(a, b, n) == 0) 17 18 // Primary Device Attributes (DA1) response. 19 // We make this a global (extern) variable so that we can override it with FFI 20 // in tests. 21 DLLEXPORT char vterm_primary_device_attr[] = "61;22;52"; 22 23 // Some convenient wrappers to make callback functions easier 24 25 static void putglyph(VTermState *state, const schar_T schar, int width, VTermPos pos) 26 { 27 VTermGlyphInfo info = { 28 .schar = schar, 29 .width = width, 30 .protected_cell = state->protected_cell, 31 .dwl = state->lineinfo[pos.row].doublewidth, 32 .dhl = state->lineinfo[pos.row].doubleheight, 33 }; 34 35 if (state->callbacks && state->callbacks->putglyph) { 36 if ((*state->callbacks->putglyph)(&info, pos, state->cbdata)) { 37 return; 38 } 39 } 40 41 DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); 42 } 43 44 static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom) 45 { 46 if (state->pos.col == oldpos->col && state->pos.row == oldpos->row) { 47 return; 48 } 49 50 if (cancel_phantom) { 51 state->at_phantom = 0; 52 } 53 54 if (state->callbacks && state->callbacks->movecursor) { 55 if ((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, 56 state->cbdata)) { 57 return; 58 } 59 } 60 } 61 62 static void erase(VTermState *state, VTermRect rect, int selective) 63 { 64 if (rect.end_col == state->cols) { 65 // If we're erasing the final cells of any lines, cancel the continuation marker on the 66 // subsequent line 67 for (int row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++) { 68 state->lineinfo[row].continuation = 0; 69 } 70 } 71 72 if (state->callbacks && state->callbacks->erase) { 73 if ((*state->callbacks->erase)(rect, selective, state->cbdata)) { 74 return; 75 } 76 } 77 } 78 79 static VTermState *vterm_state_new(VTerm *vt) 80 { 81 VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState)); 82 83 state->vt = vt; 84 85 state->rows = vt->rows; 86 state->cols = vt->cols; 87 88 state->mouse_col = 0; 89 state->mouse_row = 0; 90 state->mouse_buttons = 0; 91 92 state->mouse_protocol = MOUSE_X10; 93 94 state->callbacks = NULL; 95 state->cbdata = NULL; 96 97 state->selection.callbacks = NULL; 98 state->selection.user = NULL; 99 state->selection.buffer = NULL; 100 101 vterm_state_newpen(state); 102 103 state->bold_is_highbright = 0; 104 105 state->combine_pos.row = -1; 106 107 state->tabstops = vterm_allocator_malloc(state->vt, ((size_t)state->cols + 7) / 8); 108 109 state->lineinfos[BUFIDX_PRIMARY] = vterm_allocator_malloc(state->vt, 110 (size_t)state->rows * 111 sizeof(VTermLineInfo)); 112 // TODO(vterm): Make an 'enable' function 113 state->lineinfos[BUFIDX_ALTSCREEN] = vterm_allocator_malloc(state->vt, 114 (size_t)state->rows * 115 sizeof(VTermLineInfo)); 116 state->lineinfo = state->lineinfos[BUFIDX_PRIMARY]; 117 118 state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u'); 119 if (*state->encoding_utf8.enc->init) { 120 (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); 121 } 122 123 for (size_t i = 0; i < ARRAY_SIZE(state->key_encoding_stacks); i++) { 124 struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[i]; 125 for (size_t j = 0; j < ARRAY_SIZE(stack->items); j++) { 126 memset(&stack->items[j], 0, sizeof(stack->items[j])); 127 } 128 129 stack->size = 1; 130 } 131 132 return state; 133 } 134 135 void vterm_state_free(VTermState *state) 136 { 137 vterm_allocator_free(state->vt, state->tabstops); 138 vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]); 139 if (state->lineinfos[BUFIDX_ALTSCREEN]) { 140 vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]); 141 } 142 vterm_allocator_free(state->vt, state); 143 } 144 145 static void scroll(VTermState *state, VTermRect rect, int downward, int rightward) 146 { 147 if (!downward && !rightward) { 148 return; 149 } 150 151 int rows = rect.end_row - rect.start_row; 152 if (downward > rows) { 153 downward = rows; 154 } else if (downward < -rows) { 155 downward = -rows; 156 } 157 158 int cols = rect.end_col - rect.start_col; 159 if (rightward > cols) { 160 rightward = cols; 161 } else if (rightward < -cols) { 162 rightward = -cols; 163 } 164 165 // Update lineinfo if full line 166 if (rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) { 167 int height = rect.end_row - rect.start_row - abs(downward); 168 169 if (downward > 0) { 170 memmove(state->lineinfo + rect.start_row, 171 state->lineinfo + rect.start_row + downward, 172 (size_t)height * sizeof(state->lineinfo[0])); 173 for (int row = rect.end_row - downward; row < rect.end_row; row++) { 174 state->lineinfo[row] = (VTermLineInfo){ 0 }; 175 } 176 } else { 177 memmove(state->lineinfo + rect.start_row - downward, 178 state->lineinfo + rect.start_row, 179 (size_t)height * sizeof(state->lineinfo[0])); 180 for (int row = rect.start_row; row < rect.start_row - downward; row++) { 181 state->lineinfo[row] = (VTermLineInfo){ 0 }; 182 } 183 } 184 } 185 186 if (state->callbacks && state->callbacks->scrollrect) { 187 if ((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) { 188 return; 189 } 190 } 191 192 if (state->callbacks) { 193 vterm_scroll_rect(rect, downward, rightward, 194 state->callbacks->moverect, state->callbacks->erase, state->cbdata); 195 } 196 } 197 198 static void linefeed(VTermState *state) 199 { 200 if (state->pos.row == SCROLLREGION_BOTTOM(state) - 1) { 201 VTermRect rect = { 202 .start_row = state->scrollregion_top, 203 .end_row = SCROLLREGION_BOTTOM(state), 204 .start_col = SCROLLREGION_LEFT(state), 205 .end_col = SCROLLREGION_RIGHT(state), 206 }; 207 208 scroll(state, rect, 1, 0); 209 } else if (state->pos.row < state->rows - 1) { 210 state->pos.row++; 211 } 212 } 213 214 static void set_col_tabstop(VTermState *state, int col) 215 { 216 uint8_t mask = (uint8_t)(1 << (col & 7)); 217 state->tabstops[col >> 3] |= mask; 218 } 219 220 static void clear_col_tabstop(VTermState *state, int col) 221 { 222 uint8_t mask = (uint8_t)(1 << (col & 7)); 223 state->tabstops[col >> 3] &= ~mask; 224 } 225 226 static int is_col_tabstop(VTermState *state, int col) 227 { 228 uint8_t mask = (uint8_t)(1 << (col & 7)); 229 return state->tabstops[col >> 3] & mask; 230 } 231 232 static int is_cursor_in_scrollregion(const VTermState *state) 233 { 234 if (state->pos.row < state->scrollregion_top 235 || state->pos.row >= SCROLLREGION_BOTTOM(state)) { 236 return 0; 237 } 238 if (state->pos.col < SCROLLREGION_LEFT(state) 239 || state->pos.col >= SCROLLREGION_RIGHT(state)) { 240 return 0; 241 } 242 243 return 1; 244 } 245 246 static void tab(VTermState *state, int count, int direction) 247 { 248 while (count > 0) { 249 if (direction > 0) { 250 if (state->pos.col >= THISROWWIDTH(state) - 1) { 251 return; 252 } 253 254 state->pos.col++; 255 } else if (direction < 0) { 256 if (state->pos.col < 1) { 257 return; 258 } 259 260 state->pos.col--; 261 } 262 263 if (is_col_tabstop(state, state->pos.col)) { 264 count--; 265 } 266 } 267 } 268 269 #define NO_FORCE 0 270 #define FORCE 1 271 272 #define DWL_OFF 0 273 #define DWL_ON 1 274 275 #define DHL_OFF 0 276 #define DHL_TOP 1 277 #define DHL_BOTTOM 2 278 279 static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl) 280 { 281 VTermLineInfo info = state->lineinfo[row]; 282 283 if (dwl == DWL_OFF) { 284 info.doublewidth = DWL_OFF; 285 } else if (dwl == DWL_ON) { 286 info.doublewidth = DWL_ON; 287 } 288 // else -1 to ignore 289 290 if (dhl == DHL_OFF) { 291 info.doubleheight = DHL_OFF; 292 } else if (dhl == DHL_TOP) { 293 info.doubleheight = DHL_TOP; 294 } else if (dhl == DHL_BOTTOM) { 295 info.doubleheight = DHL_BOTTOM; 296 } 297 298 if ((state->callbacks 299 && state->callbacks->setlineinfo 300 && (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata)) 301 || force) { 302 state->lineinfo[row] = info; 303 } 304 } 305 306 static int on_text(const char bytes[], size_t len, void *user) 307 { 308 VTermState *state = user; 309 310 VTermPos oldpos = state->pos; 311 312 uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer); 313 size_t maxpoints = (state->vt->tmpbuffer_len) / sizeof(uint32_t); 314 315 int npoints = 0; 316 size_t eaten = 0; 317 318 VTermEncodingInstance *encoding = 319 state->gsingle_set ? &state->encoding[state->gsingle_set] 320 : !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] 321 : state->vt->mode.utf8 ? &state->encoding_utf8 322 : &state->encoding[state-> 323 gr_set]; 324 if (encoding->enc == state->encoding_utf8.enc) { 325 encoding = &state->encoding_utf8; // Only use one UTF-8 encoding state. 326 } 327 328 (*encoding->enc->decode)(encoding->enc, encoding->data, 329 codepoints, &npoints, state->gsingle_set ? 1 : (int)maxpoints, 330 bytes, &eaten, len); 331 332 // There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet for even a single codepoint 333 if (!npoints) { 334 return (int)eaten; 335 } 336 337 if (state->gsingle_set && npoints) { 338 state->gsingle_set = 0; 339 } 340 341 int i = 0; 342 GraphemeState grapheme_state = GRAPHEME_STATE_INIT; 343 size_t grapheme_len = 0; 344 bool recombine = false; 345 346 // See if the cursor has moved since 347 if (state->pos.row == state->combine_pos.row 348 && state->pos.col >= state->combine_pos.col 349 && state->pos.col <= state->combine_pos.col + state->combine_width) { 350 // This is a combining char. that needs to be merged with the previous glyph output 351 if (utf_iscomposing((int)state->grapheme_last, (int)codepoints[i], &state->grapheme_state)) { 352 // Find where we need to append these combining chars 353 grapheme_len = state->grapheme_len; 354 grapheme_state = state->grapheme_state; 355 state->pos.col = state->combine_pos.col; 356 state->at_phantom = 0; 357 recombine = true; 358 } else { 359 DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n"); 360 } 361 } 362 363 while (i < npoints) { 364 // Try to find combining characters following this 365 do { 366 if (grapheme_len < sizeof(state->grapheme_buf) - 4) { 367 grapheme_len += (size_t)utf_char2bytes((int)codepoints[i], 368 state->grapheme_buf + grapheme_len); 369 } 370 i++; 371 } while (i < npoints && utf_iscomposing((int)codepoints[i - 1], (int)codepoints[i], 372 &grapheme_state)); 373 374 int width = utf_ptr2cells_len(state->grapheme_buf, (int)grapheme_len); 375 376 if (state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) { 377 linefeed(state); 378 state->pos.col = 0; 379 state->at_phantom = 0; 380 state->lineinfo[state->pos.row].continuation = 1; 381 } 382 383 if (state->mode.insert && !recombine) { 384 // TODO(vterm): This will be a little inefficient for large bodies of text, as it'll have to 385 // 'ICH' effectively before every glyph. We should scan ahead and ICH as many times as 386 // required 387 VTermRect rect = { 388 .start_row = state->pos.row, 389 .end_row = state->pos.row + 1, 390 .start_col = state->pos.col, 391 .end_col = THISROWWIDTH(state), 392 }; 393 scroll(state, rect, 0, -1); 394 } 395 396 schar_T sc = schar_from_buf(state->grapheme_buf, grapheme_len); 397 putglyph(state, sc, width, state->pos); 398 399 if (i == npoints) { 400 // End of the buffer. Save the chars in case we have to combine with more on the next call 401 state->grapheme_len = grapheme_len; 402 state->grapheme_last = codepoints[i - 1]; 403 state->grapheme_state = grapheme_state; 404 state->combine_width = width; 405 state->combine_pos = state->pos; 406 } else { 407 grapheme_len = 0; 408 recombine = false; 409 } 410 411 if (state->pos.col + width >= THISROWWIDTH(state)) { 412 if (state->mode.autowrap) { 413 state->at_phantom = 1; 414 } 415 } else { 416 state->pos.col += width; 417 } 418 } 419 420 updatecursor(state, &oldpos, 0); 421 422 #ifdef DEBUG 423 if (state->pos.row < 0 || state->pos.row >= state->rows 424 || state->pos.col < 0 || state->pos.col >= state->cols) { 425 fprintf(stderr, "Position out of bounds after text: (%d,%d)\n", 426 state->pos.row, state->pos.col); 427 abort(); 428 } 429 #endif 430 431 return (int)eaten; 432 } 433 434 static int on_control(uint8_t control, void *user) 435 { 436 VTermState *state = user; 437 438 VTermPos oldpos = state->pos; 439 440 switch (control) { 441 case 0x07: // BEL - ECMA-48 8.3.3 442 if (state->callbacks && state->callbacks->bell) { 443 (*state->callbacks->bell)(state->cbdata); 444 } 445 break; 446 447 case 0x08: // BS - ECMA-48 8.3.5 448 if (state->pos.col > 0) { 449 state->pos.col--; 450 } 451 break; 452 453 case 0x09: // HT - ECMA-48 8.3.60 454 tab(state, 1, +1); 455 break; 456 457 case 0x0a: // LF - ECMA-48 8.3.74 458 case 0x0b: // VT 459 case 0x0c: // FF 460 linefeed(state); 461 if (state->mode.newline) { 462 state->pos.col = 0; 463 } 464 break; 465 466 case 0x0d: // CR - ECMA-48 8.3.15 467 state->pos.col = 0; 468 break; 469 470 case 0x0e: // LS1 - ECMA-48 8.3.76 471 state->gl_set = 1; 472 break; 473 474 case 0x0f: // LS0 - ECMA-48 8.3.75 475 state->gl_set = 0; 476 break; 477 478 case 0x84: // IND - DEPRECATED but implemented for completeness 479 linefeed(state); 480 break; 481 482 case 0x85: // NEL - ECMA-48 8.3.86 483 linefeed(state); 484 state->pos.col = 0; 485 break; 486 487 case 0x88: // HTS - ECMA-48 8.3.62 488 set_col_tabstop(state, state->pos.col); 489 break; 490 491 case 0x8d: // RI - ECMA-48 8.3.104 492 if (state->pos.row == state->scrollregion_top) { 493 VTermRect rect = { 494 .start_row = state->scrollregion_top, 495 .end_row = SCROLLREGION_BOTTOM(state), 496 .start_col = SCROLLREGION_LEFT(state), 497 .end_col = SCROLLREGION_RIGHT(state), 498 }; 499 500 scroll(state, rect, -1, 0); 501 } else if (state->pos.row > 0) { 502 state->pos.row--; 503 } 504 break; 505 506 case 0x8e: // SS2 - ECMA-48 8.3.141 507 state->gsingle_set = 2; 508 break; 509 510 case 0x8f: // SS3 - ECMA-48 8.3.142 511 state->gsingle_set = 3; 512 break; 513 514 default: 515 if (state->fallbacks && state->fallbacks->control) { 516 if ((*state->fallbacks->control)(control, state->fbdata)) { 517 return 1; 518 } 519 } 520 521 return 0; 522 } 523 524 updatecursor(state, &oldpos, 1); 525 526 #ifdef DEBUG 527 if (state->pos.row < 0 || state->pos.row >= state->rows 528 || state->pos.col < 0 || state->pos.col >= state->cols) { 529 fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n", 530 control, state->pos.row, state->pos.col); 531 abort(); 532 } 533 #endif 534 535 return 1; 536 } 537 538 static int settermprop_bool(VTermState *state, VTermProp prop, int v) 539 { 540 VTermValue val = { .boolean = v }; 541 return vterm_state_set_termprop(state, prop, &val); 542 } 543 544 static int settermprop_int(VTermState *state, VTermProp prop, int v) 545 { 546 VTermValue val = { .number = v }; 547 return vterm_state_set_termprop(state, prop, &val); 548 } 549 550 static int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag) 551 { 552 VTermValue val = { .string = frag }; 553 return vterm_state_set_termprop(state, prop, &val); 554 } 555 556 static void savecursor(VTermState *state, int save) 557 { 558 if (save) { 559 state->saved.pos = state->pos; 560 state->saved.mode.cursor_visible = state->mode.cursor_visible; 561 state->saved.mode.cursor_blink = state->mode.cursor_blink; 562 state->saved.mode.cursor_shape = state->mode.cursor_shape; 563 564 vterm_state_savepen(state, 1); 565 } else { 566 VTermPos oldpos = state->pos; 567 568 state->pos = state->saved.pos; 569 570 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible); 571 settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink); 572 settermprop_int(state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape); 573 574 vterm_state_savepen(state, 0); 575 576 updatecursor(state, &oldpos, 1); 577 } 578 } 579 580 static int on_escape(const char *bytes, size_t len, void *user) 581 { 582 VTermState *state = user; 583 584 // Easier to decode this from the first byte, even though the final byte terminates it 585 switch (bytes[0]) { 586 case ' ': 587 if (len != 2) { 588 return 0; 589 } 590 591 switch (bytes[1]) { 592 case 'F': // S7C1T 593 state->vt->mode.ctrl8bit = 0; 594 break; 595 596 case 'G': // S8C1T 597 state->vt->mode.ctrl8bit = 1; 598 break; 599 600 default: 601 return 0; 602 } 603 return 2; 604 605 case '#': 606 if (len != 2) { 607 return 0; 608 } 609 610 switch (bytes[1]) { 611 case '3': // DECDHL top 612 if (state->mode.leftrightmargin) { 613 break; 614 } 615 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP); 616 break; 617 618 case '4': // DECDHL bottom 619 if (state->mode.leftrightmargin) { 620 break; 621 } 622 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM); 623 break; 624 625 case '5': // DECSWL 626 if (state->mode.leftrightmargin) { 627 break; 628 } 629 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF); 630 break; 631 632 case '6': // DECDWL 633 if (state->mode.leftrightmargin) { 634 break; 635 } 636 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF); 637 break; 638 639 case '8': // DECALN 640 { 641 VTermPos pos; 642 schar_T E = schar_from_ascii('E'); // E 643 for (pos.row = 0; pos.row < state->rows; pos.row++) { 644 for (pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++) { 645 putglyph(state, E, 1, pos); 646 } 647 } 648 break; 649 } 650 651 default: 652 return 0; 653 } 654 return 2; 655 656 case '(': 657 case ')': 658 case '*': 659 case '+': // SCS 660 if (len != 2) { 661 return 0; 662 } 663 664 { 665 int setnum = bytes[0] - 0x28; 666 VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]); 667 668 if (newenc) { 669 state->encoding[setnum].enc = newenc; 670 671 if (newenc->init) { 672 (*newenc->init)(newenc, state->encoding[setnum].data); 673 } 674 } 675 } 676 677 return 2; 678 679 case '7': // DECSC 680 savecursor(state, 1); 681 return 1; 682 683 case '8': // DECRC 684 savecursor(state, 0); 685 return 1; 686 687 case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100 688 return 1; 689 690 case '=': // DECKPAM 691 state->mode.keypad = 1; 692 return 1; 693 694 case '>': // DECKPNM 695 state->mode.keypad = 0; 696 return 1; 697 698 case 'c': // RIS - ECMA-48 8.3.105 699 { 700 VTermPos oldpos = state->pos; 701 vterm_state_reset(state, 1); 702 if (state->callbacks && state->callbacks->movecursor) { 703 (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, 704 state->cbdata); 705 } 706 return 1; 707 } 708 709 case 'n': // LS2 - ECMA-48 8.3.78 710 state->gl_set = 2; 711 return 1; 712 713 case 'o': // LS3 - ECMA-48 8.3.80 714 state->gl_set = 3; 715 return 1; 716 717 case '~': // LS1R - ECMA-48 8.3.77 718 state->gr_set = 1; 719 return 1; 720 721 case '}': // LS2R - ECMA-48 8.3.79 722 state->gr_set = 2; 723 return 1; 724 725 case '|': // LS3R - ECMA-48 8.3.81 726 state->gr_set = 3; 727 return 1; 728 729 default: 730 return 0; 731 } 732 } 733 734 static void set_mode(VTermState *state, int num, int val) 735 { 736 switch (num) { 737 case 4: // IRM - ECMA-48 7.2.10 738 state->mode.insert = (unsigned)val; 739 break; 740 741 case 20: // LNM - ANSI X3.4-1977 742 state->mode.newline = (unsigned)val; 743 break; 744 745 default: 746 DEBUG_LOG("libvterm: Unknown mode %d\n", num); 747 return; 748 } 749 } 750 751 static void set_dec_mode(VTermState *state, int num, int val) 752 { 753 switch (num) { 754 case 1: 755 state->mode.cursor = (unsigned)val; 756 break; 757 758 case 5: // DECSCNM - screen mode 759 settermprop_bool(state, VTERM_PROP_REVERSE, val); 760 break; 761 762 case 6: // DECOM - origin mode 763 { 764 VTermPos oldpos = state->pos; 765 state->mode.origin = (unsigned)val; 766 state->pos.row = state->mode.origin ? state->scrollregion_top : 0; 767 state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0; 768 updatecursor(state, &oldpos, 1); 769 } 770 break; 771 772 case 7: 773 state->mode.autowrap = (unsigned)val; 774 break; 775 776 case 12: 777 settermprop_bool(state, VTERM_PROP_CURSORBLINK, val); 778 break; 779 780 case 25: 781 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val); 782 break; 783 784 case 69: // DECVSSM - vertical split screen mode 785 // DECLRMM - left/right margin mode 786 state->mode.leftrightmargin = (unsigned)val; 787 if (val) { 788 // Setting DECVSSM must clear doublewidth/doubleheight state of every line 789 for (int row = 0; row < state->rows; row++) { 790 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); 791 } 792 } 793 794 break; 795 796 case 1000: 797 case 1002: 798 case 1003: 799 settermprop_int(state, VTERM_PROP_MOUSE, 800 !val ? VTERM_PROP_MOUSE_NONE 801 : (num == 1000) ? VTERM_PROP_MOUSE_CLICK 802 : (num == 1002) ? VTERM_PROP_MOUSE_DRAG 803 : VTERM_PROP_MOUSE_MOVE); 804 break; 805 806 case 1004: 807 settermprop_bool(state, VTERM_PROP_FOCUSREPORT, val); 808 state->mode.report_focus = (unsigned)val; 809 break; 810 811 case 1005: 812 state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10; 813 break; 814 815 case 1006: 816 state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10; 817 break; 818 819 case 1015: 820 state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10; 821 break; 822 823 case 1047: 824 settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); 825 break; 826 827 case 1048: 828 savecursor(state, val); 829 break; 830 831 case 1049: 832 settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); 833 savecursor(state, val); 834 break; 835 836 case 2004: 837 state->mode.bracketpaste = (unsigned)val; 838 break; 839 840 case 2031: 841 settermprop_bool(state, VTERM_PROP_THEMEUPDATES, val); 842 break; 843 844 default: 845 DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num); 846 return; 847 } 848 } 849 850 static void request_dec_mode(VTermState *state, int num) 851 { 852 int reply; 853 854 switch (num) { 855 case 1: 856 reply = state->mode.cursor; 857 break; 858 859 case 5: 860 reply = state->mode.screen; 861 break; 862 863 case 6: 864 reply = state->mode.origin; 865 break; 866 867 case 7: 868 reply = state->mode.autowrap; 869 break; 870 871 case 12: 872 reply = state->mode.cursor_blink; 873 break; 874 875 case 25: 876 reply = state->mode.cursor_visible; 877 break; 878 879 case 69: 880 reply = state->mode.leftrightmargin; 881 break; 882 883 case 1000: 884 reply = state->mouse_flags == MOUSE_WANT_CLICK; 885 break; 886 887 case 1002: 888 reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG); 889 break; 890 891 case 1003: 892 reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE); 893 break; 894 895 case 1004: 896 reply = state->mode.report_focus; 897 break; 898 899 case 1005: 900 reply = state->mouse_protocol == MOUSE_UTF8; 901 break; 902 903 case 1006: 904 reply = state->mouse_protocol == MOUSE_SGR; 905 break; 906 907 case 1015: 908 reply = state->mouse_protocol == MOUSE_RXVT; 909 break; 910 911 case 1047: 912 reply = state->mode.alt_screen; 913 break; 914 915 case 2004: 916 reply = state->mode.bracketpaste; 917 break; 918 919 case 2031: 920 reply = state->mode.theme_updates; 921 break; 922 923 default: 924 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0); 925 return; 926 } 927 928 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2); 929 } 930 931 static void request_version_string(VTermState *state) 932 { 933 vterm_push_output_sprintf_str(state->vt, C1_DCS, true, ">|libvterm(%d.%d)", 934 VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR); 935 } 936 937 static void request_key_encoding_flags(VTermState *state) 938 { 939 int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY; 940 struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen]; 941 942 int reply = 0; 943 944 assert(stack->size > 0); 945 VTermKeyEncodingFlags flags = stack->items[stack->size - 1]; 946 947 if (flags.disambiguate) { 948 reply |= KEY_ENCODING_DISAMBIGUATE; 949 } 950 951 if (flags.report_events) { 952 reply |= KEY_ENCODING_REPORT_EVENTS; 953 } 954 955 if (flags.report_alternate) { 956 reply |= KEY_ENCODING_REPORT_ALTERNATE; 957 } 958 959 if (flags.report_all_keys) { 960 reply |= KEY_ENCODING_REPORT_ALL_KEYS; 961 } 962 963 if (flags.report_associated) { 964 reply |= KEY_ENCODING_REPORT_ASSOCIATED; 965 } 966 967 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%du", reply); 968 } 969 970 static void set_key_encoding_flags(VTermState *state, int arg, int mode) 971 { 972 // When mode is 3, bits set in arg reset the corresponding mode 973 bool set = mode != 3; 974 975 // When mode is 1, unset bits are reset 976 bool reset_unset = mode == 1; 977 978 struct VTermKeyEncodingFlags flags = { 0 }; 979 if (arg & KEY_ENCODING_DISAMBIGUATE) { 980 flags.disambiguate = set; 981 } else if (reset_unset) { 982 flags.disambiguate = false; 983 } 984 985 if (arg & KEY_ENCODING_REPORT_EVENTS) { 986 flags.report_events = set; 987 } else if (reset_unset) { 988 flags.report_events = false; 989 } 990 991 if (arg & KEY_ENCODING_REPORT_ALTERNATE) { 992 flags.report_alternate = set; 993 } else if (reset_unset) { 994 flags.report_alternate = false; 995 } 996 if (arg & KEY_ENCODING_REPORT_ALL_KEYS) { 997 flags.report_all_keys = set; 998 } else if (reset_unset) { 999 flags.report_all_keys = false; 1000 } 1001 1002 if (arg & KEY_ENCODING_REPORT_ASSOCIATED) { 1003 flags.report_associated = set; 1004 } else if (reset_unset) { 1005 flags.report_associated = false; 1006 } 1007 1008 int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY; 1009 struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen]; 1010 assert(stack->size > 0); 1011 stack->items[stack->size - 1] = flags; 1012 } 1013 1014 static void push_key_encoding_flags(VTermState *state, int arg) 1015 { 1016 int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY; 1017 struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen]; 1018 assert(stack->size <= ARRAY_SIZE(stack->items)); 1019 1020 if (stack->size == ARRAY_SIZE(stack->items)) { 1021 // Evict oldest entry when stack is full 1022 for (size_t i = 0; i < ARRAY_SIZE(stack->items) - 1; i++) { 1023 stack->items[i] = stack->items[i + 1]; 1024 } 1025 } else { 1026 stack->size++; 1027 } 1028 1029 set_key_encoding_flags(state, arg, 1); 1030 } 1031 1032 static void pop_key_encoding_flags(VTermState *state, int arg) 1033 { 1034 int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY; 1035 struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen]; 1036 if (arg >= stack->size) { 1037 stack->size = 1; 1038 1039 // If a pop request is received that empties the stack, all flags are reset. 1040 memset(&stack->items[0], 0, sizeof(stack->items[0])); 1041 } else if (arg > 0) { 1042 stack->size -= arg; 1043 } 1044 } 1045 1046 static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, 1047 char command, void *user) 1048 { 1049 VTermState *state = user; 1050 int leader_byte = 0; 1051 int intermed_byte = 0; 1052 int cancel_phantom = 1; 1053 1054 if (leader && leader[0]) { 1055 if (leader[1]) { // longer than 1 char 1056 return 0; 1057 } 1058 1059 switch (leader[0]) { 1060 case '?': 1061 case '>': 1062 case '<': 1063 case '=': 1064 leader_byte = (int)leader[0]; 1065 break; 1066 default: 1067 return 0; 1068 } 1069 } 1070 1071 if (intermed && intermed[0]) { 1072 if (intermed[1]) { // longer than 1 char 1073 return 0; 1074 } 1075 1076 switch (intermed[0]) { 1077 case ' ': 1078 case '!': 1079 case '"': 1080 case '$': 1081 case '\'': 1082 intermed_byte = (int)intermed[0]; 1083 break; 1084 default: 1085 return 0; 1086 } 1087 } 1088 1089 VTermPos oldpos = state->pos; 1090 1091 // Some temporaries for later code 1092 int count, val; 1093 int row, col; 1094 VTermRect rect; 1095 int selective; 1096 1097 #define LBOUND(v, min) if ((v) < (min))(v) = (min) 1098 #define UBOUND(v, max) if ((v) > (max))(v) = (max) 1099 1100 #define LEADER(l, b) ((l << 8) | b) 1101 #define INTERMED(i, b) ((i << 16) | b) 1102 1103 switch (intermed_byte << 16 | leader_byte << 8 | command) { 1104 case 0x40: // ICH - ECMA-48 8.3.64 1105 count = CSI_ARG_COUNT(args[0]); 1106 1107 if (!is_cursor_in_scrollregion(state)) { 1108 break; 1109 } 1110 1111 rect.start_row = state->pos.row; 1112 rect.end_row = state->pos.row + 1; 1113 rect.start_col = state->pos.col; 1114 if (state->mode.leftrightmargin) { 1115 rect.end_col = SCROLLREGION_RIGHT(state); 1116 } else { 1117 rect.end_col = THISROWWIDTH(state); 1118 } 1119 1120 scroll(state, rect, 0, -count); 1121 1122 break; 1123 1124 case 0x41: // CUU - ECMA-48 8.3.22 1125 count = CSI_ARG_COUNT(args[0]); 1126 state->pos.row -= count; 1127 state->at_phantom = 0; 1128 break; 1129 1130 case 0x42: // CUD - ECMA-48 8.3.19 1131 count = CSI_ARG_COUNT(args[0]); 1132 state->pos.row += count; 1133 state->at_phantom = 0; 1134 break; 1135 1136 case 0x43: // CUF - ECMA-48 8.3.20 1137 count = CSI_ARG_COUNT(args[0]); 1138 state->pos.col += count; 1139 state->at_phantom = 0; 1140 break; 1141 1142 case 0x44: // CUB - ECMA-48 8.3.18 1143 count = CSI_ARG_COUNT(args[0]); 1144 state->pos.col -= count; 1145 state->at_phantom = 0; 1146 break; 1147 1148 case 0x45: // CNL - ECMA-48 8.3.12 1149 count = CSI_ARG_COUNT(args[0]); 1150 state->pos.col = 0; 1151 state->pos.row += count; 1152 state->at_phantom = 0; 1153 break; 1154 1155 case 0x46: // CPL - ECMA-48 8.3.13 1156 count = CSI_ARG_COUNT(args[0]); 1157 state->pos.col = 0; 1158 state->pos.row -= count; 1159 state->at_phantom = 0; 1160 break; 1161 1162 case 0x47: // CHA - ECMA-48 8.3.9 1163 val = CSI_ARG_OR(args[0], 1); 1164 state->pos.col = val - 1; 1165 state->at_phantom = 0; 1166 break; 1167 1168 case 0x48: // CUP - ECMA-48 8.3.21 1169 row = CSI_ARG_OR(args[0], 1); 1170 col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); 1171 // zero-based 1172 state->pos.row = row - 1; 1173 state->pos.col = col - 1; 1174 if (state->mode.origin) { 1175 state->pos.row += state->scrollregion_top; 1176 state->pos.col += SCROLLREGION_LEFT(state); 1177 } 1178 state->at_phantom = 0; 1179 break; 1180 1181 case 0x49: // CHT - ECMA-48 8.3.10 1182 count = CSI_ARG_COUNT(args[0]); 1183 tab(state, count, +1); 1184 break; 1185 1186 case 0x4a: // ED - ECMA-48 8.3.39 1187 case LEADER('?', 0x4a): // DECSED - Selective Erase in Display 1188 selective = (leader_byte == '?'); 1189 switch (CSI_ARG(args[0])) { 1190 case CSI_ARG_MISSING: 1191 case 0: 1192 rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; 1193 rect.start_col = state->pos.col; rect.end_col = state->cols; 1194 if (rect.end_col > rect.start_col) { 1195 erase(state, rect, selective); 1196 } 1197 1198 rect.start_row = state->pos.row + 1; rect.end_row = state->rows; 1199 rect.start_col = 0; 1200 for (int row_ = rect.start_row; row_ < rect.end_row; row_++) { 1201 set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF); 1202 } 1203 if (rect.end_row > rect.start_row) { 1204 erase(state, rect, selective); 1205 } 1206 break; 1207 1208 case 1: 1209 rect.start_row = 0; rect.end_row = state->pos.row; 1210 rect.start_col = 0; rect.end_col = state->cols; 1211 for (int row_ = rect.start_row; row_ < rect.end_row; row_++) { 1212 set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF); 1213 } 1214 if (rect.end_col > rect.start_col) { 1215 erase(state, rect, selective); 1216 } 1217 1218 rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; 1219 rect.end_col = state->pos.col + 1; 1220 if (rect.end_row > rect.start_row) { 1221 erase(state, rect, selective); 1222 } 1223 break; 1224 1225 case 2: 1226 rect.start_row = 0; rect.end_row = state->rows; 1227 rect.start_col = 0; rect.end_col = state->cols; 1228 for (int row_ = rect.start_row; row_ < rect.end_row; row_++) { 1229 set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF); 1230 } 1231 erase(state, rect, selective); 1232 break; 1233 1234 case 3: 1235 if (state->callbacks && state->callbacks->sb_clear) { 1236 if ((*state->callbacks->sb_clear)(state->cbdata)) { 1237 return 1; 1238 } 1239 } 1240 break; 1241 } 1242 break; 1243 1244 case 0x4b: // EL - ECMA-48 8.3.41 1245 case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line 1246 selective = (leader_byte == '?'); 1247 rect.start_row = state->pos.row; 1248 rect.end_row = state->pos.row + 1; 1249 1250 switch (CSI_ARG(args[0])) { 1251 case CSI_ARG_MISSING: 1252 case 0: 1253 rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break; 1254 case 1: 1255 rect.start_col = 0; rect.end_col = state->pos.col + 1; break; 1256 case 2: 1257 rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break; 1258 default: 1259 return 0; 1260 } 1261 1262 if (rect.end_col > rect.start_col) { 1263 erase(state, rect, selective); 1264 } 1265 1266 break; 1267 1268 case 0x4c: // IL - ECMA-48 8.3.67 1269 count = CSI_ARG_COUNT(args[0]); 1270 1271 if (!is_cursor_in_scrollregion(state)) { 1272 break; 1273 } 1274 1275 rect.start_row = state->pos.row; 1276 rect.end_row = SCROLLREGION_BOTTOM(state); 1277 rect.start_col = SCROLLREGION_LEFT(state); 1278 rect.end_col = SCROLLREGION_RIGHT(state); 1279 1280 scroll(state, rect, -count, 0); 1281 1282 break; 1283 1284 case 0x4d: // DL - ECMA-48 8.3.32 1285 count = CSI_ARG_COUNT(args[0]); 1286 1287 if (!is_cursor_in_scrollregion(state)) { 1288 break; 1289 } 1290 1291 rect.start_row = state->pos.row; 1292 rect.end_row = SCROLLREGION_BOTTOM(state); 1293 rect.start_col = SCROLLREGION_LEFT(state); 1294 rect.end_col = SCROLLREGION_RIGHT(state); 1295 1296 scroll(state, rect, count, 0); 1297 1298 break; 1299 1300 case 0x50: // DCH - ECMA-48 8.3.26 1301 count = CSI_ARG_COUNT(args[0]); 1302 1303 if (!is_cursor_in_scrollregion(state)) { 1304 break; 1305 } 1306 1307 rect.start_row = state->pos.row; 1308 rect.end_row = state->pos.row + 1; 1309 rect.start_col = state->pos.col; 1310 if (state->mode.leftrightmargin) { 1311 rect.end_col = SCROLLREGION_RIGHT(state); 1312 } else { 1313 rect.end_col = THISROWWIDTH(state); 1314 } 1315 1316 scroll(state, rect, 0, count); 1317 1318 break; 1319 1320 case 0x53: // SU - ECMA-48 8.3.147 1321 count = CSI_ARG_COUNT(args[0]); 1322 1323 rect.start_row = state->scrollregion_top; 1324 rect.end_row = SCROLLREGION_BOTTOM(state); 1325 rect.start_col = SCROLLREGION_LEFT(state); 1326 rect.end_col = SCROLLREGION_RIGHT(state); 1327 1328 scroll(state, rect, count, 0); 1329 1330 break; 1331 1332 case 0x54: // SD - ECMA-48 8.3.113 1333 count = CSI_ARG_COUNT(args[0]); 1334 1335 rect.start_row = state->scrollregion_top; 1336 rect.end_row = SCROLLREGION_BOTTOM(state); 1337 rect.start_col = SCROLLREGION_LEFT(state); 1338 rect.end_col = SCROLLREGION_RIGHT(state); 1339 1340 scroll(state, rect, -count, 0); 1341 1342 break; 1343 1344 case 0x58: // ECH - ECMA-48 8.3.38 1345 count = CSI_ARG_COUNT(args[0]); 1346 1347 rect.start_row = state->pos.row; 1348 rect.end_row = state->pos.row + 1; 1349 rect.start_col = state->pos.col; 1350 rect.end_col = state->pos.col + count; 1351 UBOUND(rect.end_col, THISROWWIDTH(state)); 1352 1353 erase(state, rect, 0); 1354 break; 1355 1356 case 0x5a: // CBT - ECMA-48 8.3.7 1357 count = CSI_ARG_COUNT(args[0]); 1358 tab(state, count, -1); 1359 break; 1360 1361 case 0x60: // HPA - ECMA-48 8.3.57 1362 col = CSI_ARG_OR(args[0], 1); 1363 state->pos.col = col - 1; 1364 state->at_phantom = 0; 1365 break; 1366 1367 case 0x61: // HPR - ECMA-48 8.3.59 1368 count = CSI_ARG_COUNT(args[0]); 1369 state->pos.col += count; 1370 state->at_phantom = 0; 1371 break; 1372 1373 case 0x62: { // REP - ECMA-48 8.3.103 1374 const int row_width = THISROWWIDTH(state); 1375 count = CSI_ARG_COUNT(args[0]); 1376 col = state->pos.col + count; 1377 UBOUND(col, row_width); 1378 schar_T sc = schar_from_buf(state->grapheme_buf, state->grapheme_len); 1379 while (state->pos.col < col) { 1380 putglyph(state, sc, state->combine_width, state->pos); 1381 state->pos.col += state->combine_width; 1382 } 1383 if (state->pos.col + state->combine_width >= row_width) { 1384 if (state->mode.autowrap) { 1385 state->at_phantom = 1; 1386 cancel_phantom = 0; 1387 } 1388 } 1389 break; 1390 } 1391 1392 case 0x63: // DA - ECMA-48 8.3.24 1393 val = CSI_ARG_OR(args[0], 0); 1394 if (val == 0) { 1395 // DEC VT100 response 1396 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%sc", vterm_primary_device_attr); 1397 } 1398 break; 1399 1400 case LEADER('>', 0x63): // DEC secondary Device Attributes 1401 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0); 1402 break; 1403 1404 case 0x64: // VPA - ECMA-48 8.3.158 1405 row = CSI_ARG_OR(args[0], 1); 1406 state->pos.row = row - 1; 1407 if (state->mode.origin) { 1408 state->pos.row += state->scrollregion_top; 1409 } 1410 state->at_phantom = 0; 1411 break; 1412 1413 case 0x65: // VPR - ECMA-48 8.3.160 1414 count = CSI_ARG_COUNT(args[0]); 1415 state->pos.row += count; 1416 state->at_phantom = 0; 1417 break; 1418 1419 case 0x66: // HVP - ECMA-48 8.3.63 1420 row = CSI_ARG_OR(args[0], 1); 1421 col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); 1422 // zero-based 1423 state->pos.row = row - 1; 1424 state->pos.col = col - 1; 1425 if (state->mode.origin) { 1426 state->pos.row += state->scrollregion_top; 1427 state->pos.col += SCROLLREGION_LEFT(state); 1428 } 1429 state->at_phantom = 0; 1430 break; 1431 1432 case 0x67: // TBC - ECMA-48 8.3.154 1433 val = CSI_ARG_OR(args[0], 0); 1434 1435 switch (val) { 1436 case 0: 1437 clear_col_tabstop(state, state->pos.col); 1438 break; 1439 case 3: 1440 case 5: 1441 for (col = 0; col < state->cols; col++) { 1442 clear_col_tabstop(state, col); 1443 } 1444 break; 1445 case 1: 1446 case 2: 1447 case 4: 1448 break; 1449 // TODO(vterm): 1, 2 and 4 aren't meaningful yet without line tab stops 1450 default: 1451 return 0; 1452 } 1453 break; 1454 1455 case 0x68: // SM - ECMA-48 8.3.125 1456 if (!CSI_ARG_IS_MISSING(args[0])) { 1457 set_mode(state, CSI_ARG(args[0]), 1); 1458 } 1459 break; 1460 1461 case LEADER('?', 0x68): // DEC private mode set 1462 for (int i = 0; i < argcount; i++) { 1463 if (!CSI_ARG_IS_MISSING(args[i])) { 1464 set_dec_mode(state, CSI_ARG(args[i]), 1); 1465 } 1466 } 1467 break; 1468 1469 case 0x6a: // HPB - ECMA-48 8.3.58 1470 count = CSI_ARG_COUNT(args[0]); 1471 state->pos.col -= count; 1472 state->at_phantom = 0; 1473 break; 1474 1475 case 0x6b: // VPB - ECMA-48 8.3.159 1476 count = CSI_ARG_COUNT(args[0]); 1477 state->pos.row -= count; 1478 state->at_phantom = 0; 1479 break; 1480 1481 case 0x6c: // RM - ECMA-48 8.3.106 1482 if (!CSI_ARG_IS_MISSING(args[0])) { 1483 set_mode(state, CSI_ARG(args[0]), 0); 1484 } 1485 break; 1486 1487 case LEADER('?', 0x6c): // DEC private mode reset 1488 for (int i = 0; i < argcount; i++) { 1489 if (!CSI_ARG_IS_MISSING(args[i])) { 1490 set_dec_mode(state, CSI_ARG(args[i]), 0); 1491 } 1492 } 1493 break; 1494 1495 case 0x6d: // SGR - ECMA-48 8.3.117 1496 vterm_state_setpen(state, args, argcount); 1497 break; 1498 1499 case LEADER('?', 0x6d): // DECSGR 1500 // No actual DEC terminal recognised these, but some printers did. These are alternative ways to 1501 // request subscript/superscript/off 1502 for (int argi = 0; argi < argcount; argi++) { 1503 long arg; 1504 switch (arg = CSI_ARG(args[argi])) { 1505 case 4: // Superscript on 1506 arg = 73; 1507 vterm_state_setpen(state, &arg, 1); 1508 break; 1509 case 5: // Subscript on 1510 arg = 74; 1511 vterm_state_setpen(state, &arg, 1); 1512 break; 1513 case 24: // Super+subscript off 1514 arg = 75; 1515 vterm_state_setpen(state, &arg, 1); 1516 break; 1517 } 1518 } 1519 break; 1520 1521 case 0x6e: // DSR - ECMA-48 8.3.35 1522 case LEADER('?', 0x6e): // DECDSR 1523 val = CSI_ARG_OR(args[0], 0); 1524 1525 { 1526 char *qmark = (leader_byte == '?') ? "?" : ""; 1527 bool dark = false; 1528 1529 switch (val) { 1530 case 0: 1531 case 1: 1532 case 2: 1533 case 3: 1534 case 4: 1535 // ignore - these are replies 1536 break; 1537 case 5: 1538 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark); 1539 break; 1540 case 6: // CPR - cursor position report 1541 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, 1542 state->pos.col + 1); 1543 break; 1544 case 996: 1545 if (state->callbacks && state->callbacks->theme) { 1546 if (state->callbacks->theme(&dark, state->cbdata)) { 1547 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?997;%cn", dark ? '1' : '2'); 1548 } 1549 } 1550 break; 1551 } 1552 } 1553 break; 1554 1555 case INTERMED('!', 0x70): // DECSTR - DEC soft terminal reset 1556 vterm_state_reset(state, 0); 1557 break; 1558 1559 case LEADER('?', INTERMED('$', 0x70)): 1560 request_dec_mode(state, CSI_ARG(args[0])); 1561 break; 1562 1563 case LEADER('>', 0x71): // XTVERSION - xterm query version string 1564 request_version_string(state); 1565 break; 1566 1567 case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape 1568 val = CSI_ARG_OR(args[0], 1); 1569 1570 switch (val) { 1571 case 0: 1572 case 1: 1573 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); 1574 settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); 1575 break; 1576 case 2: 1577 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); 1578 settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); 1579 break; 1580 case 3: 1581 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); 1582 settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); 1583 break; 1584 case 4: 1585 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); 1586 settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); 1587 break; 1588 case 5: 1589 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); 1590 settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); 1591 break; 1592 case 6: 1593 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); 1594 settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); 1595 break; 1596 } 1597 1598 break; 1599 1600 case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute 1601 val = CSI_ARG_OR(args[0], 0); 1602 1603 switch (val) { 1604 case 0: 1605 case 2: 1606 state->protected_cell = 0; 1607 break; 1608 case 1: 1609 state->protected_cell = 1; 1610 break; 1611 } 1612 1613 break; 1614 1615 case 0x72: // DECSTBM - DEC custom 1616 state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1; 1617 state->scrollregion_bottom = argcount < 2 1618 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); 1619 LBOUND(state->scrollregion_top, 0); 1620 UBOUND(state->scrollregion_top, state->rows); 1621 LBOUND(state->scrollregion_bottom, -1); 1622 if (state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows) { 1623 state->scrollregion_bottom = -1; 1624 } else { 1625 UBOUND(state->scrollregion_bottom, state->rows); 1626 } 1627 1628 if (SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { 1629 // Invalid 1630 state->scrollregion_top = 0; 1631 state->scrollregion_bottom = -1; 1632 } 1633 1634 // Setting the scrolling region restores the cursor to the home position 1635 state->pos.row = 0; 1636 state->pos.col = 0; 1637 if (state->mode.origin) { 1638 state->pos.row += state->scrollregion_top; 1639 state->pos.col += SCROLLREGION_LEFT(state); 1640 } 1641 1642 break; 1643 1644 case 0x73: // DECSLRM - DEC custom 1645 // Always allow setting these margins, just they won't take effect without DECVSSM 1646 state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1; 1647 state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); 1648 LBOUND(state->scrollregion_left, 0); 1649 UBOUND(state->scrollregion_left, state->cols); 1650 LBOUND(state->scrollregion_right, -1); 1651 if (state->scrollregion_left == 0 && state->scrollregion_right == state->cols) { 1652 state->scrollregion_right = -1; 1653 } else { 1654 UBOUND(state->scrollregion_right, state->cols); 1655 } 1656 1657 if (state->scrollregion_right > -1 1658 && state->scrollregion_right <= state->scrollregion_left) { 1659 // Invalid 1660 state->scrollregion_left = 0; 1661 state->scrollregion_right = -1; 1662 } 1663 1664 // Setting the scrolling region restores the cursor to the home position 1665 state->pos.row = 0; 1666 state->pos.col = 0; 1667 if (state->mode.origin) { 1668 state->pos.row += state->scrollregion_top; 1669 state->pos.col += SCROLLREGION_LEFT(state); 1670 } 1671 1672 break; 1673 1674 case LEADER('?', 0x75): // Kitty query 1675 request_key_encoding_flags(state); 1676 break; 1677 1678 case LEADER('>', 0x75): // Kitty push flags 1679 push_key_encoding_flags(state, CSI_ARG_OR(args[0], 0)); 1680 break; 1681 1682 case LEADER('<', 0x75): // Kitty pop flags 1683 pop_key_encoding_flags(state, CSI_ARG_OR(args[0], 1)); 1684 break; 1685 1686 case LEADER('=', 0x75): // Kitty set flags 1687 val = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); 1688 set_key_encoding_flags(state, CSI_ARG_OR(args[0], 0), val); 1689 break; 1690 1691 case INTERMED('\'', 0x7D): // DECIC 1692 count = CSI_ARG_COUNT(args[0]); 1693 1694 if (!is_cursor_in_scrollregion(state)) { 1695 break; 1696 } 1697 1698 rect.start_row = state->scrollregion_top; 1699 rect.end_row = SCROLLREGION_BOTTOM(state); 1700 rect.start_col = state->pos.col; 1701 rect.end_col = SCROLLREGION_RIGHT(state); 1702 1703 scroll(state, rect, 0, -count); 1704 1705 break; 1706 1707 case INTERMED('\'', 0x7E): // DECDC 1708 count = CSI_ARG_COUNT(args[0]); 1709 1710 if (!is_cursor_in_scrollregion(state)) { 1711 break; 1712 } 1713 1714 rect.start_row = state->scrollregion_top; 1715 rect.end_row = SCROLLREGION_BOTTOM(state); 1716 rect.start_col = state->pos.col; 1717 rect.end_col = SCROLLREGION_RIGHT(state); 1718 1719 scroll(state, rect, 0, count); 1720 1721 break; 1722 1723 default: 1724 if (state->fallbacks && state->fallbacks->csi) { 1725 if ((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) { 1726 return 1; 1727 } 1728 } 1729 1730 return 0; 1731 } 1732 1733 if (state->mode.origin) { 1734 LBOUND(state->pos.row, state->scrollregion_top); 1735 UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state) - 1); 1736 LBOUND(state->pos.col, SCROLLREGION_LEFT(state)); 1737 UBOUND(state->pos.col, SCROLLREGION_RIGHT(state) - 1); 1738 } else { 1739 LBOUND(state->pos.row, 0); 1740 UBOUND(state->pos.row, state->rows - 1); 1741 LBOUND(state->pos.col, 0); 1742 UBOUND(state->pos.col, THISROWWIDTH(state) - 1); 1743 } 1744 1745 updatecursor(state, &oldpos, cancel_phantom); 1746 1747 #ifdef DEBUG 1748 if (state->pos.row < 0 || state->pos.row >= state->rows 1749 || state->pos.col < 0 || state->pos.col >= state->cols) { 1750 fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n", 1751 command, state->pos.row, state->pos.col); 1752 abort(); 1753 } 1754 1755 if (SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { 1756 fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n", 1757 command, SCROLLREGION_BOTTOM(state), state->scrollregion_top); 1758 abort(); 1759 } 1760 1761 if (SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) { 1762 fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n", 1763 command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state)); 1764 abort(); 1765 } 1766 #endif 1767 1768 return 1; 1769 } 1770 1771 static uint8_t unbase64one(char c) 1772 { 1773 if (c >= 'A' && c <= 'Z') { 1774 return (uint8_t)c - 'A'; 1775 } else if (c >= 'a' && c <= 'z') { 1776 return (uint8_t)c - 'a' + 26; 1777 } else if (c >= '0' && c <= '9') { 1778 return (uint8_t)c - '0' + 52; 1779 } else if (c == '+') { 1780 return 62; 1781 } else if (c == '/') { 1782 return 63; 1783 } 1784 1785 return 0xFF; 1786 } 1787 1788 static void osc_selection(VTermState *state, VTermStringFragment frag) 1789 { 1790 if (frag.initial) { 1791 state->tmp.selection.mask = 0; 1792 state->tmp.selection.state = SELECTION_INITIAL; 1793 } 1794 1795 while (!state->tmp.selection.state && frag.len) { 1796 // Parse selection parameter 1797 switch (frag.str[0]) { 1798 case 'c': 1799 state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD; 1800 break; 1801 case 'p': 1802 state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY; 1803 break; 1804 case 'q': 1805 state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY; 1806 break; 1807 case 's': 1808 state->tmp.selection.mask |= VTERM_SELECTION_SELECT; 1809 break; 1810 case '0': 1811 case '1': 1812 case '2': 1813 case '3': 1814 case '4': 1815 case '5': 1816 case '6': 1817 case '7': 1818 state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0')); 1819 break; 1820 1821 case ';': 1822 state->tmp.selection.state = SELECTION_SELECTED; 1823 if (!state->tmp.selection.mask) { 1824 state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0; 1825 } 1826 break; 1827 } 1828 1829 frag.str++; 1830 frag.len--; 1831 } 1832 1833 if (!frag.len) { 1834 // Clear selection if we're already finished but didn't do anything 1835 if (frag.final && state->selection.callbacks->set) { 1836 (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ 1837 .str = NULL, 1838 .len = 0, 1839 .initial = state->tmp.selection.state != SELECTION_SET, 1840 .final = true, 1841 }, state->selection.user); 1842 } 1843 return; 1844 } 1845 1846 if (state->tmp.selection.state == SELECTION_SELECTED) { 1847 if (frag.str[0] == '?') { 1848 state->tmp.selection.state = SELECTION_QUERY; 1849 } else { 1850 state->tmp.selection.state = SELECTION_SET_INITIAL; 1851 state->tmp.selection.recvpartial = 0; 1852 } 1853 } 1854 1855 if (state->tmp.selection.state == SELECTION_QUERY) { 1856 if (state->selection.callbacks->query) { 1857 (*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user); 1858 } 1859 return; 1860 } 1861 1862 if (state->tmp.selection.state == SELECTION_INVALID) { 1863 return; 1864 } 1865 1866 if (state->selection.callbacks->set) { 1867 size_t bufcur = 0; 1868 char *buffer = state->selection.buffer; 1869 1870 uint32_t x = 0; // Current decoding value 1871 int n = 0; // Number of sextets consumed 1872 1873 if (state->tmp.selection.recvpartial) { 1874 n = state->tmp.selection.recvpartial >> 24; 1875 x = state->tmp.selection.recvpartial & 0x03FFFF; // could be up to 18 bits of state in here 1876 1877 state->tmp.selection.recvpartial = 0; 1878 } 1879 1880 while ((state->selection.buflen - bufcur) >= 3 && frag.len) { 1881 if (frag.str[0] == '=') { 1882 if (n == 2) { 1883 buffer[0] = (char)(x >> 4 & 0xFF); 1884 buffer += 1, bufcur += 1; 1885 } 1886 if (n == 3) { 1887 buffer[0] = (char)(x >> 10 & 0xFF); 1888 buffer[1] = (char)(x >> 2 & 0xFF); 1889 buffer += 2, bufcur += 2; 1890 } 1891 1892 while (frag.len && frag.str[0] == '=') { 1893 frag.str++, frag.len--; 1894 } 1895 1896 n = 0; 1897 } else { 1898 uint8_t b = unbase64one(frag.str[0]); 1899 if (b == 0xFF) { 1900 DEBUG_LOG("base64decode bad input %02X\n", (uint8_t)frag.str[0]); 1901 1902 state->tmp.selection.state = SELECTION_INVALID; 1903 if (state->selection.callbacks->set) { 1904 (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ 1905 .str = NULL, 1906 .len = 0, 1907 .initial = true, 1908 .final = true, 1909 }, state->selection.user); 1910 } 1911 break; 1912 } 1913 1914 x = (x << 6) | b; 1915 n++; 1916 frag.str++, frag.len--; 1917 1918 if (n == 4) { 1919 buffer[0] = (char)(x >> 16 & 0xFF); 1920 buffer[1] = (char)(x >> 8 & 0xFF); 1921 buffer[2] = (char)(x >> 0 & 0xFF); 1922 1923 buffer += 3, bufcur += 3; 1924 x = 0; 1925 n = 0; 1926 } 1927 } 1928 1929 if (!frag.len || (state->selection.buflen - bufcur) < 3) { 1930 if (bufcur) { 1931 (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ 1932 .str = state->selection.buffer, 1933 .len = bufcur, 1934 .initial = state->tmp.selection.state == SELECTION_SET_INITIAL, 1935 .final = frag.final && !frag.len, 1936 }, state->selection.user); 1937 state->tmp.selection.state = SELECTION_SET; 1938 } 1939 1940 buffer = state->selection.buffer; 1941 bufcur = 0; 1942 } 1943 } 1944 1945 if (n) { 1946 state->tmp.selection.recvpartial = (uint32_t)(n << 24) | x; 1947 } 1948 } 1949 } 1950 1951 static int on_osc(int command, VTermStringFragment frag, void *user) 1952 { 1953 VTermState *state = user; 1954 1955 switch (command) { 1956 case 0: 1957 settermprop_string(state, VTERM_PROP_ICONNAME, frag); 1958 settermprop_string(state, VTERM_PROP_TITLE, frag); 1959 break; 1960 1961 case 1: 1962 settermprop_string(state, VTERM_PROP_ICONNAME, frag); 1963 break; 1964 1965 case 2: 1966 settermprop_string(state, VTERM_PROP_TITLE, frag); 1967 break; 1968 1969 case 52: 1970 if (state->selection.callbacks) { 1971 osc_selection(state, frag); 1972 } 1973 break; 1974 } 1975 1976 if (state->fallbacks && state->fallbacks->osc) { 1977 if ((*state->fallbacks->osc)(command, frag, state->fbdata)) { 1978 return 1; 1979 } 1980 } 1981 1982 return 0; 1983 } 1984 1985 static void request_status_string(VTermState *state, VTermStringFragment frag) 1986 { 1987 VTerm *vt = state->vt; 1988 1989 char *tmp = state->tmp.decrqss; 1990 1991 if (frag.initial) { 1992 tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0; 1993 } 1994 1995 size_t i = 0; 1996 while (i < sizeof(state->tmp.decrqss) - 1 && tmp[i]) { 1997 i++; 1998 } 1999 while (i < sizeof(state->tmp.decrqss) - 1 && frag.len--) { 2000 tmp[i++] = (frag.str++)[0]; 2001 } 2002 tmp[i] = 0; 2003 2004 if (!frag.final) { 2005 return; 2006 } 2007 2008 switch (tmp[0] | tmp[1] << 8 | tmp[2] << 16) { 2009 case 'm': { 2010 // Query SGR 2011 long args[20]; 2012 int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0])); 2013 size_t cur = 0; 2014 2015 cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, 2016 vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ... 2017 if (cur >= vt->tmpbuffer_len) { 2018 return; 2019 } 2020 2021 for (int argi = 0; argi < argc; argi++) { 2022 cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, 2023 argi == argc - 1 ? "%ld" 2024 : CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" 2025 : "%ld;", 2026 CSI_ARG(args[argi])); 2027 if (cur >= vt->tmpbuffer_len) { 2028 return; 2029 } 2030 } 2031 2032 cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, 2033 vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST 2034 if (cur >= vt->tmpbuffer_len) { 2035 return; 2036 } 2037 2038 vterm_push_output_bytes(vt, vt->tmpbuffer, cur); 2039 return; 2040 } 2041 2042 case 'r': 2043 // Query DECSTBM 2044 vterm_push_output_sprintf_str(vt, C1_DCS, true, 2045 "1$r%d;%dr", state->scrollregion_top + 1, 2046 SCROLLREGION_BOTTOM(state)); 2047 return; 2048 2049 case 's': 2050 // Query DECSLRM 2051 vterm_push_output_sprintf_str(vt, C1_DCS, true, 2052 "1$r%d;%ds", SCROLLREGION_LEFT(state) + 1, 2053 SCROLLREGION_RIGHT(state)); 2054 return; 2055 2056 case ' '|('q' << 8): { 2057 // Query DECSCUSR 2058 int reply = 0; 2059 switch (state->mode.cursor_shape) { 2060 case VTERM_PROP_CURSORSHAPE_BLOCK: 2061 reply = 2; break; 2062 case VTERM_PROP_CURSORSHAPE_UNDERLINE: 2063 reply = 4; break; 2064 case VTERM_PROP_CURSORSHAPE_BAR_LEFT: 2065 reply = 6; break; 2066 } 2067 if (state->mode.cursor_blink) { 2068 reply--; 2069 } 2070 vterm_push_output_sprintf_str(vt, C1_DCS, true, 2071 "1$r%d q", reply); 2072 return; 2073 } 2074 2075 case '\"'|('q' << 8): 2076 // Query DECSCA 2077 vterm_push_output_sprintf_str(vt, C1_DCS, true, 2078 "1$r%d\"q", state->protected_cell ? 1 : 2); 2079 return; 2080 } 2081 2082 vterm_push_output_sprintf_str(state->vt, C1_DCS, true, "0$r"); 2083 } 2084 2085 static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user) 2086 { 2087 VTermState *state = user; 2088 2089 if (commandlen == 2 && strneq(command, "$q", 2)) { 2090 request_status_string(state, frag); 2091 return 1; 2092 } else if (state->fallbacks && state->fallbacks->dcs) { 2093 if ((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata)) { 2094 return 1; 2095 } 2096 } 2097 2098 DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)commandlen, command); 2099 return 0; 2100 } 2101 2102 static int on_apc(VTermStringFragment frag, void *user) 2103 { 2104 VTermState *state = user; 2105 2106 if (state->fallbacks && state->fallbacks->apc) { 2107 if ((*state->fallbacks->apc)(frag, state->fbdata)) { 2108 return 1; 2109 } 2110 } 2111 2112 // No DEBUG_LOG because all APCs are unhandled 2113 return 0; 2114 } 2115 2116 static int on_pm(VTermStringFragment frag, void *user) 2117 { 2118 VTermState *state = user; 2119 2120 if (state->fallbacks && state->fallbacks->pm) { 2121 if ((*state->fallbacks->pm)(frag, state->fbdata)) { 2122 return 1; 2123 } 2124 } 2125 2126 // No DEBUG_LOG because all PMs are unhandled 2127 return 0; 2128 } 2129 2130 static int on_sos(VTermStringFragment frag, void *user) 2131 { 2132 VTermState *state = user; 2133 2134 if (state->fallbacks && state->fallbacks->sos) { 2135 if ((*state->fallbacks->sos)(frag, state->fbdata)) { 2136 return 1; 2137 } 2138 } 2139 2140 // No DEBUG_LOG because all SOSs are unhandled 2141 return 0; 2142 } 2143 2144 static int on_resize(int rows, int cols, void *user) 2145 { 2146 VTermState *state = user; 2147 VTermPos oldpos = state->pos; 2148 2149 if (cols != state->cols) { 2150 uint8_t *newtabstops = vterm_allocator_malloc(state->vt, ((size_t)cols + 7) / 8); 2151 2152 // TODO(vterm): This can all be done much more efficiently bytewise 2153 int col; 2154 for (col = 0; col < state->cols && col < cols; col++) { 2155 uint8_t mask = (uint8_t)(1 << (col & 7)); 2156 if (state->tabstops[col >> 3] & mask) { 2157 newtabstops[col >> 3] |= mask; 2158 } else { 2159 newtabstops[col >> 3] &= ~mask; 2160 } 2161 } 2162 2163 for (; col < cols; col++) { 2164 uint8_t mask = (uint8_t)(1 << (col & 7)); 2165 if (col % 8 == 0) { 2166 newtabstops[col >> 3] |= mask; 2167 } else { 2168 newtabstops[col >> 3] &= ~mask; 2169 } 2170 } 2171 2172 vterm_allocator_free(state->vt, state->tabstops); 2173 state->tabstops = newtabstops; 2174 } 2175 2176 state->rows = rows; 2177 state->cols = cols; 2178 2179 if (state->scrollregion_bottom > -1) { 2180 UBOUND(state->scrollregion_bottom, state->rows); 2181 } 2182 if (state->scrollregion_right > -1) { 2183 UBOUND(state->scrollregion_right, state->cols); 2184 } 2185 2186 VTermStateFields fields = { 2187 .pos = state->pos, 2188 .lineinfos = {[0] = state->lineinfos[0], [1] = state->lineinfos[1] }, 2189 }; 2190 2191 if (state->callbacks && state->callbacks->resize) { 2192 (*state->callbacks->resize)(rows, cols, &fields, state->cbdata); 2193 state->pos = fields.pos; 2194 2195 state->lineinfos[0] = fields.lineinfos[0]; 2196 state->lineinfos[1] = fields.lineinfos[1]; 2197 } else { 2198 if (rows != state->rows) { 2199 for (int bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) { 2200 VTermLineInfo *oldlineinfo = state->lineinfos[bufidx]; 2201 if (!oldlineinfo) { 2202 continue; 2203 } 2204 2205 VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, 2206 (size_t)rows * sizeof(VTermLineInfo)); 2207 2208 int row; 2209 for (row = 0; row < state->rows && row < rows; row++) { 2210 newlineinfo[row] = oldlineinfo[row]; 2211 } 2212 2213 for (; row < rows; row++) { 2214 newlineinfo[row] = (VTermLineInfo){ 2215 .doublewidth = 0, 2216 }; 2217 } 2218 2219 vterm_allocator_free(state->vt, state->lineinfos[bufidx]); 2220 state->lineinfos[bufidx] = newlineinfo; 2221 } 2222 } 2223 } 2224 2225 state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; 2226 2227 if (state->at_phantom && state->pos.col < cols - 1) { 2228 state->at_phantom = 0; 2229 state->pos.col++; 2230 } 2231 2232 if (state->pos.row < 0) { 2233 state->pos.row = 0; 2234 } 2235 if (state->pos.row >= rows) { 2236 state->pos.row = rows - 1; 2237 } 2238 if (state->pos.col < 0) { 2239 state->pos.col = 0; 2240 } 2241 if (state->pos.col >= cols) { 2242 state->pos.col = cols - 1; 2243 } 2244 2245 updatecursor(state, &oldpos, 1); 2246 2247 return 1; 2248 } 2249 2250 static const VTermParserCallbacks parser_callbacks = { 2251 .text = on_text, 2252 .control = on_control, 2253 .escape = on_escape, 2254 .csi = on_csi, 2255 .osc = on_osc, 2256 .dcs = on_dcs, 2257 .apc = on_apc, 2258 .pm = on_pm, 2259 .sos = on_sos, 2260 .resize = on_resize, 2261 }; 2262 2263 VTermState *vterm_obtain_state(VTerm *vt) 2264 { 2265 if (vt->state) { 2266 return vt->state; 2267 } 2268 2269 VTermState *state = vterm_state_new(vt); 2270 vt->state = state; 2271 2272 vterm_parser_set_callbacks(vt, &parser_callbacks, state); 2273 2274 return state; 2275 } 2276 2277 void vterm_state_reset(VTermState *state, int hard) 2278 { 2279 state->scrollregion_top = 0; 2280 state->scrollregion_bottom = -1; 2281 state->scrollregion_left = 0; 2282 state->scrollregion_right = -1; 2283 2284 state->mode.keypad = 0; 2285 state->mode.cursor = 0; 2286 state->mode.autowrap = 1; 2287 state->mode.insert = 0; 2288 state->mode.newline = 0; 2289 state->mode.alt_screen = 0; 2290 state->mode.origin = 0; 2291 state->mode.leftrightmargin = 0; 2292 state->mode.bracketpaste = 0; 2293 state->mode.report_focus = 0; 2294 2295 state->mouse_flags = 0; 2296 2297 state->vt->mode.ctrl8bit = 0; 2298 2299 for (int col = 0; col < state->cols; col++) { 2300 if (col % 8 == 0) { 2301 set_col_tabstop(state, col); 2302 } else { 2303 clear_col_tabstop(state, col); 2304 } 2305 } 2306 2307 for (int row = 0; row < state->rows; row++) { 2308 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); 2309 } 2310 2311 if (state->callbacks && state->callbacks->initpen) { 2312 (*state->callbacks->initpen)(state->cbdata); 2313 } 2314 2315 vterm_state_resetpen(state); 2316 2317 VTermEncoding *default_enc = state->vt->mode.utf8 2318 ? vterm_lookup_encoding(ENC_UTF8, 'u') 2319 : vterm_lookup_encoding(ENC_SINGLE_94, 'B'); 2320 2321 for (int i = 0; i < 4; i++) { 2322 state->encoding[i].enc = default_enc; 2323 if (default_enc->init) { 2324 (*default_enc->init)(default_enc, state->encoding[i].data); 2325 } 2326 } 2327 2328 state->gl_set = 0; 2329 state->gr_set = 1; 2330 state->gsingle_set = 0; 2331 2332 state->protected_cell = 0; 2333 2334 // Initialise the props 2335 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1); 2336 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); 2337 settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); 2338 2339 if (hard) { 2340 state->pos.row = 0; 2341 state->pos.col = 0; 2342 state->at_phantom = 0; 2343 2344 VTermRect rect = { 0, state->rows, 0, state->cols }; 2345 erase(state, rect, 0); 2346 } 2347 } 2348 2349 void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user) 2350 { 2351 if (callbacks) { 2352 state->callbacks = callbacks; 2353 state->cbdata = user; 2354 2355 if (state->callbacks && state->callbacks->initpen) { 2356 (*state->callbacks->initpen)(state->cbdata); 2357 } 2358 } else { 2359 state->callbacks = NULL; 2360 state->cbdata = NULL; 2361 } 2362 } 2363 2364 void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, 2365 void *user) 2366 { 2367 if (fallbacks) { 2368 state->fallbacks = fallbacks; 2369 state->fbdata = user; 2370 } else { 2371 state->fallbacks = NULL; 2372 state->fbdata = NULL; 2373 } 2374 } 2375 2376 int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) 2377 { 2378 // Only store the new value of the property if usercode said it was happy. This is especially 2379 // important for altscreen switching 2380 if (state->callbacks && state->callbacks->settermprop) { 2381 if (!(*state->callbacks->settermprop)(prop, val, state->cbdata)) { 2382 return 0; 2383 } 2384 } 2385 2386 switch (prop) { 2387 case VTERM_PROP_TITLE: 2388 case VTERM_PROP_ICONNAME: 2389 // we don't store these, just transparently pass through 2390 return 1; 2391 case VTERM_PROP_CURSORVISIBLE: 2392 state->mode.cursor_visible = (unsigned)val->boolean; 2393 return 1; 2394 case VTERM_PROP_CURSORBLINK: 2395 state->mode.cursor_blink = (unsigned)val->boolean; 2396 return 1; 2397 case VTERM_PROP_CURSORSHAPE: 2398 state->mode.cursor_shape = (unsigned)val->number; 2399 return 1; 2400 case VTERM_PROP_REVERSE: 2401 state->mode.screen = (unsigned)val->boolean; 2402 return 1; 2403 case VTERM_PROP_ALTSCREEN: 2404 state->mode.alt_screen = (unsigned)val->boolean; 2405 state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; 2406 if (state->mode.alt_screen) { 2407 VTermRect rect = { 2408 .start_row = 0, 2409 .start_col = 0, 2410 .end_row = state->rows, 2411 .end_col = state->cols, 2412 }; 2413 erase(state, rect, 0); 2414 } 2415 return 1; 2416 case VTERM_PROP_MOUSE: 2417 state->mouse_flags = 0; 2418 if (val->number) { 2419 state->mouse_flags |= MOUSE_WANT_CLICK; 2420 } 2421 if (val->number == VTERM_PROP_MOUSE_DRAG) { 2422 state->mouse_flags |= MOUSE_WANT_DRAG; 2423 } 2424 if (val->number == VTERM_PROP_MOUSE_MOVE) { 2425 state->mouse_flags |= MOUSE_WANT_MOVE; 2426 } 2427 return 1; 2428 case VTERM_PROP_FOCUSREPORT: 2429 state->mode.report_focus = (unsigned)val->boolean; 2430 return 1; 2431 case VTERM_PROP_THEMEUPDATES: 2432 state->mode.theme_updates = (unsigned)val->boolean; 2433 return 1; 2434 2435 case VTERM_N_PROPS: 2436 return 0; 2437 } 2438 2439 return 0; 2440 } 2441 2442 void vterm_state_focus_in(VTermState *state) 2443 { 2444 if (state->mode.report_focus) { 2445 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I"); 2446 } 2447 } 2448 2449 void vterm_state_focus_out(VTermState *state) 2450 { 2451 if (state->mode.report_focus) { 2452 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O"); 2453 } 2454 } 2455 2456 const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row) 2457 { 2458 return state->lineinfo + row; 2459 } 2460 2461 void vterm_state_set_selection_callbacks(VTermState *state, 2462 const VTermSelectionCallbacks *callbacks, void *user, 2463 char *buffer, size_t buflen) 2464 { 2465 if (buflen && !buffer) { 2466 buffer = vterm_allocator_malloc(state->vt, buflen); 2467 } 2468 2469 state->selection.callbacks = callbacks; 2470 state->selection.user = user; 2471 state->selection.buffer = buffer; 2472 state->selection.buflen = buflen; 2473 }