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