Serenity Operating System
at hosted 1048 lines 27 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <AK/StringBuilder.h> 28#include <LibVT/Terminal.h> 29#include <string.h> 30 31//#define TERMINAL_DEBUG 32 33namespace VT { 34 35Terminal::Terminal(TerminalClient& client) 36 : m_client(client) 37{ 38} 39 40Terminal::~Terminal() 41{ 42} 43 44Terminal::Line::Line(u16 length) 45{ 46 set_length(length); 47} 48 49Terminal::Line::~Line() 50{ 51 delete[] characters; 52 delete[] attributes; 53} 54 55void Terminal::Line::set_length(u16 new_length) 56{ 57 if (m_length == new_length) 58 return; 59 auto* new_characters = new u8[new_length]; 60 auto* new_attributes = new Attribute[new_length]; 61 memset(new_characters, ' ', new_length); 62 if (characters && attributes) { 63 memcpy(new_characters, characters, min(m_length, new_length)); 64 memcpy(new_attributes, attributes, min(m_length, new_length) * sizeof(Attribute)); 65 } 66 delete[] characters; 67 delete[] attributes; 68 characters = new_characters; 69 attributes = new_attributes; 70 m_length = new_length; 71} 72 73void Terminal::Line::clear(Attribute attribute) 74{ 75 if (dirty) { 76 memset(characters, ' ', m_length); 77 for (u16 i = 0; i < m_length; ++i) 78 attributes[i] = attribute; 79 return; 80 } 81 for (unsigned i = 0; i < m_length; ++i) { 82 if (characters[i] != ' ') 83 dirty = true; 84 characters[i] = ' '; 85 } 86 for (unsigned i = 0; i < m_length; ++i) { 87 if (attributes[i] != attribute) 88 dirty = true; 89 attributes[i] = attribute; 90 } 91} 92 93bool Terminal::Line::has_only_one_background_color() const 94{ 95 if (!m_length) 96 return true; 97 // FIXME: Cache this result? 98 auto color = attributes[0].background_color; 99 for (size_t i = 1; i < m_length; ++i) { 100 if (attributes[i].background_color != color) 101 return false; 102 } 103 return true; 104} 105 106void Terminal::clear() 107{ 108 for (size_t i = 0; i < rows(); ++i) 109 line(i).clear(m_current_attribute); 110 set_cursor(0, 0); 111} 112 113inline bool is_valid_parameter_character(u8 ch) 114{ 115 return ch >= 0x30 && ch <= 0x3f; 116} 117 118inline bool is_valid_intermediate_character(u8 ch) 119{ 120 return ch >= 0x20 && ch <= 0x2f; 121} 122 123inline bool is_valid_final_character(u8 ch) 124{ 125 return ch >= 0x40 && ch <= 0x7e; 126} 127 128void Terminal::alter_mode(bool should_set, bool question_param, const ParamVector& params) 129{ 130 int mode = 2; 131 if (params.size() > 0) { 132 mode = params[0]; 133 } 134 if (!question_param) { 135 switch (mode) { 136 // FIXME: implement *something* for this 137 default: 138 unimplemented_escape(); 139 break; 140 } 141 } else { 142 switch (mode) { 143 case 25: 144 // Hide cursor command, but doesn't need to be run (for now, because 145 // we don't do inverse control codes anyways) 146 if (should_set) 147 dbgprintf("Terminal: Hide Cursor escapecode recieved. Not needed: ignored.\n"); 148 else 149 dbgprintf("Terminal: Show Cursor escapecode recieved. Not needed: ignored.\n"); 150 break; 151 default: 152 break; 153 } 154 } 155} 156 157void Terminal::RM(bool question_param, const ParamVector& params) 158{ 159 // RM – Reset Mode 160 alter_mode(true, question_param, params); 161} 162 163void Terminal::SM(bool question_param, const ParamVector& params) 164{ 165 // SM – Set Mode 166 alter_mode(false, question_param, params); 167} 168 169void Terminal::SGR(const ParamVector& params) 170{ 171 // SGR – Select Graphic Rendition 172 if (params.is_empty()) { 173 m_current_attribute.reset(); 174 return; 175 } 176 if (params.size() == 3 && params[1] == 5) { 177 if (params[0] == 38) { 178 m_current_attribute.foreground_color = params[2]; 179 return; 180 } else if (params[0] == 48) { 181 m_current_attribute.background_color = params[2]; 182 return; 183 } 184 } 185 for (auto param : params) { 186 switch (param) { 187 case 0: 188 // Reset 189 m_current_attribute.reset(); 190 break; 191 case 1: 192 m_current_attribute.flags |= Attribute::Bold; 193 break; 194 case 3: 195 m_current_attribute.flags |= Attribute::Italic; 196 break; 197 case 4: 198 m_current_attribute.flags |= Attribute::Underline; 199 break; 200 case 5: 201 m_current_attribute.flags |= Attribute::Blink; 202 break; 203 case 7: 204 m_current_attribute.flags |= Attribute::Negative; 205 break; 206 case 22: 207 m_current_attribute.flags &= ~Attribute::Bold; 208 break; 209 case 23: 210 m_current_attribute.flags &= ~Attribute::Italic; 211 break; 212 case 24: 213 m_current_attribute.flags &= ~Attribute::Underline; 214 break; 215 case 25: 216 m_current_attribute.flags &= ~Attribute::Blink; 217 break; 218 case 27: 219 m_current_attribute.flags &= ~Attribute::Negative; 220 break; 221 case 30: 222 case 31: 223 case 32: 224 case 33: 225 case 34: 226 case 35: 227 case 36: 228 case 37: 229 // Foreground color 230 if (m_current_attribute.flags & Attribute::Bold) 231 param += 8; 232 m_current_attribute.foreground_color = param - 30; 233 break; 234 case 39: 235 // reset foreground 236 m_current_attribute.foreground_color = Attribute::default_foreground_color; 237 break; 238 case 40: 239 case 41: 240 case 42: 241 case 43: 242 case 44: 243 case 45: 244 case 46: 245 case 47: 246 // Background color 247 if (m_current_attribute.flags & Attribute::Bold) 248 param += 8; 249 m_current_attribute.background_color = param - 40; 250 break; 251 case 49: 252 // reset background 253 m_current_attribute.background_color = Attribute::default_background_color; 254 break; 255 default: 256 dbgprintf("FIXME: SGR: p: %u\n", param); 257 } 258 } 259} 260 261void Terminal::escape$s(const ParamVector&) 262{ 263 m_saved_cursor_row = m_cursor_row; 264 m_saved_cursor_column = m_cursor_column; 265} 266 267void Terminal::escape$u(const ParamVector&) 268{ 269 set_cursor(m_saved_cursor_row, m_saved_cursor_column); 270} 271 272void Terminal::escape$t(const ParamVector& params) 273{ 274 if (params.size() < 1) 275 return; 276 dbgprintf("FIXME: escape$t: Ps: %u (param count: %zu)\n", params[0], params.size()); 277} 278 279void Terminal::DECSTBM(const ParamVector& params) 280{ 281 // DECSTBM – Set Top and Bottom Margins ("Scrolling Region") 282 unsigned top = 1; 283 unsigned bottom = m_rows; 284 if (params.size() >= 1) 285 top = params[0]; 286 if (params.size() >= 2) 287 bottom = params[1]; 288 if ((bottom - top) < 2 || bottom > m_rows) { 289 dbgprintf("Error: DECSTBM: scrolling region invalid: %u-%u\n", top, bottom); 290 return; 291 } 292 m_scroll_region_top = top - 1; 293 m_scroll_region_bottom = bottom - 1; 294 set_cursor(0, 0); 295} 296 297void Terminal::CUP(const ParamVector& params) 298{ 299 // CUP – Cursor Position 300 unsigned row = 1; 301 unsigned col = 1; 302 if (params.size() >= 1) 303 row = params[0]; 304 if (params.size() >= 2) 305 col = params[1]; 306 set_cursor(row - 1, col - 1); 307} 308 309void Terminal::HVP(const ParamVector& params) 310{ 311 // HVP – Horizontal and Vertical Position 312 unsigned row = 1; 313 unsigned col = 1; 314 if (params.size() >= 1) 315 row = params[0]; 316 if (params.size() >= 2) 317 col = params[1]; 318 set_cursor(row - 1, col - 1); 319} 320 321void Terminal::CUU(const ParamVector& params) 322{ 323 // CUU – Cursor Up 324 int num = 1; 325 if (params.size() >= 1) 326 num = params[0]; 327 if (num == 0) 328 num = 1; 329 int new_row = (int)m_cursor_row - num; 330 if (new_row < 0) 331 new_row = 0; 332 set_cursor(new_row, m_cursor_column); 333} 334 335void Terminal::CUD(const ParamVector& params) 336{ 337 // CUD – Cursor Down 338 int num = 1; 339 if (params.size() >= 1) 340 num = params[0]; 341 if (num == 0) 342 num = 1; 343 int new_row = (int)m_cursor_row + num; 344 if (new_row >= m_rows) 345 new_row = m_rows - 1; 346 set_cursor(new_row, m_cursor_column); 347} 348 349void Terminal::CUF(const ParamVector& params) 350{ 351 // CUF – Cursor Forward 352 int num = 1; 353 if (params.size() >= 1) 354 num = params[0]; 355 if (num == 0) 356 num = 1; 357 int new_column = (int)m_cursor_column + num; 358 if (new_column >= m_columns) 359 new_column = m_columns - 1; 360 set_cursor(m_cursor_row, new_column); 361} 362 363void Terminal::CUB(const ParamVector& params) 364{ 365 // CUB – Cursor Backward 366 int num = 1; 367 if (params.size() >= 1) 368 num = params[0]; 369 if (num == 0) 370 num = 1; 371 int new_column = (int)m_cursor_column - num; 372 if (new_column < 0) 373 new_column = 0; 374 set_cursor(m_cursor_row, new_column); 375} 376 377void Terminal::escape$G(const ParamVector& params) 378{ 379 int new_column = 1; 380 if (params.size() >= 1) 381 new_column = params[0] - 1; 382 if (new_column < 0) 383 new_column = 0; 384 set_cursor(m_cursor_row, new_column); 385} 386 387void Terminal::escape$b(const ParamVector& params) 388{ 389 if (params.size() < 1) 390 return; 391 392 for (unsigned i = 0; i < params[0]; ++i) 393 put_character_at(m_cursor_row, m_cursor_column++, m_last_char); 394} 395 396void Terminal::escape$d(const ParamVector& params) 397{ 398 int new_row = 1; 399 if (params.size() >= 1) 400 new_row = params[0] - 1; 401 if (new_row < 0) 402 new_row = 0; 403 set_cursor(new_row, m_cursor_column); 404} 405 406void Terminal::escape$X(const ParamVector& params) 407{ 408 // Erase characters (without moving cursor) 409 int num = 1; 410 if (params.size() >= 1) 411 num = params[0]; 412 if (num == 0) 413 num = 1; 414 // Clear from cursor to end of line. 415 for (int i = m_cursor_column; i < num; ++i) { 416 put_character_at(m_cursor_row, i, ' '); 417 } 418} 419 420void Terminal::EL(const ParamVector& params) 421{ 422 int mode = 0; 423 if (params.size() >= 1) 424 mode = params[0]; 425 switch (mode) { 426 case 0: 427 // Clear from cursor to end of line. 428 for (int i = m_cursor_column; i < m_columns; ++i) { 429 put_character_at(m_cursor_row, i, ' '); 430 } 431 break; 432 case 1: 433 // Clear from cursor to beginning of line. 434 for (int i = 0; i <= m_cursor_column; ++i) { 435 put_character_at(m_cursor_row, i, ' '); 436 } 437 break; 438 case 2: 439 // Clear the complete line 440 for (int i = 0; i < m_columns; ++i) { 441 put_character_at(m_cursor_row, i, ' '); 442 } 443 break; 444 default: 445 unimplemented_escape(); 446 break; 447 } 448} 449 450void Terminal::ED(const ParamVector& params) 451{ 452 // ED - Erase in Display 453 int mode = 0; 454 if (params.size() >= 1) 455 mode = params[0]; 456 switch (mode) { 457 case 0: 458 // Clear from cursor to end of screen. 459 for (int i = m_cursor_column; i < m_columns; ++i) 460 put_character_at(m_cursor_row, i, ' '); 461 for (int row = m_cursor_row + 1; row < m_rows; ++row) { 462 for (int column = 0; column < m_columns; ++column) { 463 put_character_at(row, column, ' '); 464 } 465 } 466 break; 467 case 1: 468 // Clear from cursor to beginning of screen. 469 for (int i = m_cursor_column; i >= 0; --i) 470 put_character_at(m_cursor_row, i, ' '); 471 for (int row = m_cursor_row - 1; row >= 0; --row) { 472 for (int column = 0; column < m_columns; ++column) { 473 put_character_at(row, column, ' '); 474 } 475 } 476 break; 477 case 2: 478 clear(); 479 break; 480 case 3: 481 // FIXME: <esc>[3J should also clear the scrollback buffer. 482 clear(); 483 break; 484 default: 485 unimplemented_escape(); 486 break; 487 } 488} 489 490void Terminal::escape$S(const ParamVector& params) 491{ 492 int count = 1; 493 if (params.size() >= 1) 494 count = params[0]; 495 496 for (u16 i = 0; i < count; i++) 497 scroll_up(); 498} 499 500void Terminal::escape$T(const ParamVector& params) 501{ 502 int count = 1; 503 if (params.size() >= 1) 504 count = params[0]; 505 506 for (u16 i = 0; i < count; i++) 507 scroll_down(); 508} 509 510void Terminal::escape$L(const ParamVector& params) 511{ 512 int count = 1; 513 if (params.size() >= 1) 514 count = params[0]; 515 invalidate_cursor(); 516 for (; count > 0; --count) { 517 m_lines.insert(m_cursor_row + m_scroll_region_top, make<Line>(m_columns)); 518 if (m_scroll_region_bottom + 1 < m_lines.size()) 519 m_lines.remove(m_scroll_region_bottom + 1); 520 else 521 m_lines.remove(m_lines.size() - 1); 522 } 523 524 m_need_full_flush = true; 525} 526 527void Terminal::DA(const ParamVector&) 528{ 529 // DA - Device Attributes 530 emit_string("\033[?1;0c"); 531} 532 533void Terminal::escape$M(const ParamVector& params) 534{ 535 int count = 1; 536 if (params.size() >= 1) 537 count = params[0]; 538 539 if (count == 1 && m_cursor_row == 0) { 540 scroll_up(); 541 return; 542 } 543 544 int max_count = m_rows - (m_scroll_region_top + m_cursor_row); 545 count = min(count, max_count); 546 547 for (int c = count; c > 0; --c) { 548 m_lines.remove(m_cursor_row + m_scroll_region_top); 549 if (m_scroll_region_bottom < m_lines.size()) 550 m_lines.insert(m_scroll_region_bottom, make<Line>(m_columns)); 551 else 552 m_lines.append(make<Line>(m_columns)); 553 } 554} 555 556void Terminal::escape$P(const ParamVector& params) 557{ 558 int num = 1; 559 if (params.size() >= 1) 560 num = params[0]; 561 562 if (num == 0) 563 num = 1; 564 565 auto& line = this->line(m_cursor_row); 566 567 // Move n characters of line to the left 568 for (int i = m_cursor_column; i < line.m_length - num; i++) 569 line.characters[i] = line.characters[i + num]; 570 571 // Fill remainder of line with blanks 572 for (int i = line.m_length - num; i < line.m_length; i++) 573 line.characters[i] = ' '; 574 575 line.dirty = true; 576} 577 578void Terminal::execute_xterm_command() 579{ 580 m_final = '@'; 581 bool ok; 582 unsigned value = String::copy(m_xterm_param1).to_uint(ok); 583 if (ok) { 584 switch (value) { 585 case 0: 586 case 1: 587 case 2: 588 m_client.set_window_title(String::copy(m_xterm_param2)); 589 break; 590 default: 591 unimplemented_xterm_escape(); 592 break; 593 } 594 } 595 m_xterm_param1.clear_with_capacity(); 596 m_xterm_param2.clear_with_capacity(); 597} 598 599void Terminal::execute_escape_sequence(u8 final) 600{ 601 bool question_param = false; 602 m_final = final; 603 ParamVector params; 604 605 if (m_parameters.size() > 0 && m_parameters[0] == '?') { 606 question_param = true; 607 m_parameters.remove(0); 608 } 609 auto paramparts = String::copy(m_parameters).split(';'); 610 for (auto& parampart : paramparts) { 611 bool ok; 612 unsigned value = parampart.to_uint(ok); 613 if (!ok) { 614 // FIXME: Should we do something else? 615 m_parameters.clear_with_capacity(); 616 m_intermediates.clear_with_capacity(); 617 return; 618 } 619 params.append(value); 620 } 621 622#if defined(TERMINAL_DEBUG) 623 dbgprintf("Terminal::execute_escape_sequence: Handled final '%c'\n", final); 624 dbgprintf("Params: "); 625 for (auto& p : params) { 626 dbgprintf("%d ", p); 627 } 628 dbgprintf("\b\n"); 629#endif 630 631 switch (final) { 632 case 'A': 633 CUU(params); 634 break; 635 case 'B': 636 CUD(params); 637 break; 638 case 'C': 639 CUF(params); 640 break; 641 case 'D': 642 CUB(params); 643 break; 644 case 'H': 645 CUP(params); 646 break; 647 case 'J': 648 ED(params); 649 break; 650 case 'K': 651 EL(params); 652 break; 653 case 'M': 654 escape$M(params); 655 break; 656 case 'P': 657 escape$P(params); 658 break; 659 case 'S': 660 escape$S(params); 661 break; 662 case 'T': 663 escape$T(params); 664 break; 665 case 'L': 666 escape$L(params); 667 break; 668 case 'G': 669 escape$G(params); 670 break; 671 case 'X': 672 escape$X(params); 673 break; 674 case 'b': 675 escape$b(params); 676 break; 677 case 'd': 678 escape$d(params); 679 break; 680 case 'm': 681 SGR(params); 682 break; 683 case 's': 684 escape$s(params); 685 break; 686 case 'u': 687 escape$u(params); 688 break; 689 case 't': 690 escape$t(params); 691 break; 692 case 'r': 693 DECSTBM(params); 694 break; 695 case 'l': 696 RM(question_param, params); 697 break; 698 case 'h': 699 SM(question_param, params); 700 break; 701 case 'c': 702 DA(params); 703 break; 704 case 'f': 705 HVP(params); 706 break; 707 default: 708 dbgprintf("Terminal::execute_escape_sequence: Unhandled final '%c'\n", final); 709 break; 710 } 711 712#if defined(TERMINAL_DEBUG) 713 dbgprintf("\n"); 714 for (auto& line : m_lines) { 715 dbgprintf("Terminal: Line: "); 716 for (int i = 0; i < line.m_length; i++) { 717 dbgprintf("%c", line.characters[i]); 718 } 719 dbgprintf("\n"); 720 } 721#endif 722 723 m_parameters.clear_with_capacity(); 724 m_intermediates.clear_with_capacity(); 725} 726 727void Terminal::newline() 728{ 729 u16 new_row = m_cursor_row; 730 if (m_cursor_row == m_scroll_region_bottom) { 731 scroll_up(); 732 } else { 733 ++new_row; 734 } 735 set_cursor(new_row, 0); 736} 737 738void Terminal::scroll_up() 739{ 740 // NOTE: We have to invalidate the cursor first. 741 invalidate_cursor(); 742 if (m_scroll_region_top == 0) { 743 auto line = move(m_lines.ptr_at(m_scroll_region_top)); 744 m_history.append(move(line)); 745 while (m_history.size() > max_history_size()) 746 m_history.take_first(); 747 m_client.terminal_history_changed(); 748 } 749 m_lines.remove(m_scroll_region_top); 750 m_lines.insert(m_scroll_region_bottom, make<Line>(m_columns)); 751 m_need_full_flush = true; 752} 753 754void Terminal::scroll_down() 755{ 756 // NOTE: We have to invalidate the cursor first. 757 invalidate_cursor(); 758 m_lines.remove(m_scroll_region_bottom); 759 m_lines.insert(m_scroll_region_top, make<Line>(m_columns)); 760 m_need_full_flush = true; 761} 762 763void Terminal::set_cursor(unsigned a_row, unsigned a_column) 764{ 765 unsigned row = min(a_row, m_rows - 1u); 766 unsigned column = min(a_column, m_columns - 1u); 767 if (row == m_cursor_row && column == m_cursor_column) 768 return; 769 ASSERT(row < rows()); 770 ASSERT(column < columns()); 771 invalidate_cursor(); 772 m_cursor_row = row; 773 m_cursor_column = column; 774 m_stomp = false; 775 invalidate_cursor(); 776} 777 778void Terminal::put_character_at(unsigned row, unsigned column, u8 ch) 779{ 780 ASSERT(row < rows()); 781 ASSERT(column < columns()); 782 auto& line = this->line(row); 783 line.characters[column] = ch; 784 line.attributes[column] = m_current_attribute; 785 line.attributes[column].flags |= Attribute::Touched; 786 line.dirty = true; 787 788 m_last_char = ch; 789} 790 791void Terminal::NEL() 792{ 793 // NEL - Next Line 794 newline(); 795} 796 797void Terminal::IND() 798{ 799 // IND - Index (move down) 800 CUD({}); 801} 802 803void Terminal::RI() 804{ 805 // RI - Reverse Index (move up) 806 CUU({}); 807} 808 809void Terminal::on_char(u8 ch) 810{ 811#ifdef TERMINAL_DEBUG 812 dbgprintf("Terminal::on_char: %b (%c), fg=%u, bg=%u\n", ch, ch, m_current_attribute.foreground_color, m_current_attribute.background_color); 813#endif 814 switch (m_escape_state) { 815 case GotEscape: 816 if (ch == '[') { 817 m_escape_state = ExpectParameter; 818 } else if (ch == '(') { 819 m_swallow_current = true; 820 m_escape_state = ExpectParameter; 821 } else if (ch == ']') { 822 m_escape_state = ExpectXtermParameter1; 823 } else if (ch == '#') { 824 m_escape_state = ExpectHashtagDigit; 825 } else if (ch == 'D') { 826 IND(); 827 m_escape_state = Normal; 828 return; 829 } else if (ch == 'M') { 830 RI(); 831 m_escape_state = Normal; 832 return; 833 } else if (ch == 'E') { 834 NEL(); 835 m_escape_state = Normal; 836 return; 837 } else { 838 dbg() << "Unexpected character in GotEscape '" << (char)ch << "'"; 839 m_escape_state = Normal; 840 } 841 return; 842 case ExpectHashtagDigit: 843 if (ch >= '0' && ch <= '9') { 844 execute_hashtag(ch); 845 m_escape_state = Normal; 846 } 847 break; 848 case ExpectXtermParameter1: 849 if (ch != ';') { 850 m_xterm_param1.append(ch); 851 return; 852 } 853 m_escape_state = ExpectXtermParameter2; 854 return; 855 case ExpectXtermParameter2: 856 if (ch != '\007') { 857 m_xterm_param2.append(ch); 858 return; 859 } 860 m_escape_state = ExpectXtermFinal; 861 [[fallthrough]]; 862 case ExpectXtermFinal: 863 m_escape_state = Normal; 864 if (ch == '\007') 865 execute_xterm_command(); 866 return; 867 case ExpectParameter: 868 if (is_valid_parameter_character(ch)) { 869 m_parameters.append(ch); 870 return; 871 } 872 m_escape_state = ExpectIntermediate; 873 [[fallthrough]]; 874 case ExpectIntermediate: 875 if (is_valid_intermediate_character(ch)) { 876 m_intermediates.append(ch); 877 return; 878 } 879 m_escape_state = ExpectFinal; 880 [[fallthrough]]; 881 case ExpectFinal: 882 if (is_valid_final_character(ch)) { 883 m_escape_state = Normal; 884 if (!m_swallow_current) 885 execute_escape_sequence(ch); 886 m_swallow_current = false; 887 return; 888 } 889 m_escape_state = Normal; 890 m_swallow_current = false; 891 return; 892 case Normal: 893 break; 894 } 895 896 switch (ch) { 897 case '\0': 898 return; 899 case '\033': 900 m_escape_state = GotEscape; 901 m_swallow_current = false; 902 return; 903 case 8: // Backspace 904 if (m_cursor_column) { 905 set_cursor(m_cursor_row, m_cursor_column - 1); 906 return; 907 } 908 return; 909 case '\a': 910 m_client.beep(); 911 return; 912 case '\t': { 913 for (unsigned i = m_cursor_column + 1; i < columns(); ++i) { 914 if (m_horizontal_tabs[i]) { 915 set_cursor(m_cursor_row, i); 916 return; 917 } 918 } 919 return; 920 } 921 case '\r': 922 set_cursor(m_cursor_row, 0); 923 return; 924 case '\n': 925 newline(); 926 return; 927 } 928 929 auto new_column = m_cursor_column + 1; 930 if (new_column < columns()) { 931 put_character_at(m_cursor_row, m_cursor_column, ch); 932 set_cursor(m_cursor_row, new_column); 933 } else { 934 if (m_stomp) { 935 m_stomp = false; 936 newline(); 937 put_character_at(m_cursor_row, m_cursor_column, ch); 938 set_cursor(m_cursor_row, 1); 939 } else { 940 // Curious: We wait once on the right-hand side 941 m_stomp = true; 942 put_character_at(m_cursor_row, m_cursor_column, ch); 943 } 944 } 945} 946 947void Terminal::inject_string(const StringView& str) 948{ 949 for (size_t i = 0; i < str.length(); ++i) 950 on_char(str[i]); 951} 952 953void Terminal::emit_string(const StringView& str) 954{ 955 for (size_t i = 0; i < str.length(); ++i) 956 m_client.emit_char(str[i]); 957} 958 959void Terminal::unimplemented_escape() 960{ 961 StringBuilder builder; 962 builder.appendf("((Unimplemented escape: %c", m_final); 963 if (!m_parameters.is_empty()) { 964 builder.append(" parameters:"); 965 for (size_t i = 0; i < m_parameters.size(); ++i) 966 builder.append((char)m_parameters[i]); 967 } 968 if (!m_intermediates.is_empty()) { 969 builder.append(" intermediates:"); 970 for (size_t i = 0; i < m_intermediates.size(); ++i) 971 builder.append((char)m_intermediates[i]); 972 } 973 builder.append("))"); 974 inject_string(builder.to_string()); 975} 976 977void Terminal::unimplemented_xterm_escape() 978{ 979 auto message = String::format("((Unimplemented xterm escape: %c))\n", m_final); 980 inject_string(message); 981} 982 983void Terminal::set_size(u16 columns, u16 rows) 984{ 985 if (!columns) 986 columns = 1; 987 if (!rows) 988 rows = 1; 989 990 if (columns == m_columns && rows == m_rows) 991 return; 992 993#if defined(TERMINAL_DEBUG) 994 dbgprintf("Terminal: RESIZE to: %d rows\n", rows); 995#endif 996 997 if (rows > m_rows) { 998 while (m_lines.size() < rows) 999 m_lines.append(make<Line>(columns)); 1000 } else { 1001 m_lines.shrink(rows); 1002 } 1003 1004 for (int i = 0; i < rows; ++i) 1005 m_lines[i].set_length(columns); 1006 1007 m_columns = columns; 1008 m_rows = rows; 1009 1010 m_scroll_region_top = 0; 1011 m_scroll_region_bottom = rows - 1; 1012 1013 m_cursor_row = min((int)m_cursor_row, m_rows - 1); 1014 m_cursor_column = min((int)m_cursor_column, m_columns - 1); 1015 m_saved_cursor_row = min((int)m_saved_cursor_row, m_rows - 1); 1016 m_saved_cursor_column = min((int)m_saved_cursor_column, m_columns - 1); 1017 1018 m_horizontal_tabs.resize(columns); 1019 for (unsigned i = 0; i < columns; ++i) 1020 m_horizontal_tabs[i] = (i % 8) == 0; 1021 // Rightmost column is always last tab on line. 1022 m_horizontal_tabs[columns - 1] = 1; 1023 1024 m_client.terminal_did_resize(m_columns, m_rows); 1025} 1026 1027void Terminal::invalidate_cursor() 1028{ 1029 line(m_cursor_row).dirty = true; 1030} 1031 1032void Terminal::execute_hashtag(u8 hashtag) 1033{ 1034 switch (hashtag) { 1035 case '8': 1036 // Confidence Test - Fill screen with E's 1037 for (size_t row = 0; row < m_rows; ++row) { 1038 for (size_t column = 0; column < m_columns; ++column) { 1039 put_character_at(row, column, 'E'); 1040 } 1041 } 1042 break; 1043 default: 1044 dbg() << "Unknown hashtag: '" << hashtag << "'"; 1045 } 1046} 1047 1048}