1 /* 2 * Copyright 2001-2024, Haiku, Inc. All rights reserved. 3 * Copyright (c) 2003-4 Kian Duffy <myob@users.sourceforge.net> 4 * Parts Copyright (C) 1998,99 Kazuho Okui and Takashi Murai. 5 * Distributed under the terms of the MIT license. 6 * 7 * Authors: 8 * Kian Duffy, myob@users.sourceforge.net 9 * Simon South, simon@simonsouth.net 10 * Siarzhuk Zharski, zharik@gmx.li 11 */ 12 13 14 //! Escape sequence parse and character encoding. 15 16 17 #include "TermParse.h" 18 19 #include <ctype.h> 20 #include <errno.h> 21 #include <stdio.h> 22 #include <stdlib.h> 23 #include <signal.h> 24 #include <string.h> 25 #include <unistd.h> 26 27 #include <Autolock.h> 28 #include <Beep.h> 29 #include <Catalog.h> 30 #include <Locale.h> 31 #include <Message.h> 32 #include <UTF8.h> 33 34 #include "Colors.h" 35 #include "TermConst.h" 36 #include "TerminalBuffer.h" 37 #include "VTparse.h" 38 39 40 extern int gUTF8GroundTable[]; /* UTF8 Ground table */ 41 extern int gISO8859GroundTable[]; /* ISO8859 & EUC Ground table */ 42 extern int gWinCPGroundTable[]; /* Windows cp1252, cp1251, koi-8r */ 43 extern int gSJISGroundTable[]; /* Shift-JIS Ground table */ 44 45 extern int gEscTable[]; /* ESC */ 46 extern int gCsiTable[]; /* ESC [ */ 47 extern int gDecTable[]; /* ESC [ ? */ 48 extern int gScrTable[]; /* ESC # */ 49 extern int gIgnoreTable[]; /* ignore table */ 50 extern int gIesTable[]; /* ignore ESC table */ 51 extern int gEscIgnoreTable[]; /* ESC ignore table */ 52 53 extern const char* gLineDrawGraphSet[]; /* may be used for G0, G1, G2, G3 */ 54 55 #define DEFAULT -1 56 #define NPARAM 10 // Max parameters 57 58 59 //! Get char from pty reader buffer. 60 inline uchar 61 TermParse::_NextParseChar() 62 { 63 if (fParserBufferOffset >= fParserBufferSize) { 64 // parser buffer empty 65 status_t error = _ReadParserBuffer(); 66 if (error != B_OK) 67 throw error; 68 } 69 70 #ifdef USE_DEBUG_SNAPSHOTS 71 fBuffer->CaptureChar(fParserBuffer[fParserBufferOffset]); 72 #endif 73 74 return fParserBuffer[fParserBufferOffset++]; 75 } 76 77 78 TermParse::TermParse(int fd) 79 : 80 fFd(fd), 81 fParseThread(-1), 82 fReaderThread(-1), 83 fReaderSem(-1), 84 fReaderLocker(-1), 85 fBufferPosition(0), 86 fReadBufferSize(0), 87 fParserBufferSize(0), 88 fParserBufferOffset(0), 89 fBuffer(NULL), 90 fQuitting(true) 91 { 92 memset(fReadBuffer, 0, READ_BUF_SIZE); 93 memset(fParserBuffer, 0, ESC_PARSER_BUFFER_SIZE); 94 } 95 96 97 TermParse::~TermParse() 98 { 99 StopThreads(); 100 } 101 102 103 status_t 104 TermParse::StartThreads(TerminalBuffer *buffer) 105 { 106 if (fBuffer != NULL) 107 return B_ERROR; 108 109 fQuitting = false; 110 fBuffer = buffer; 111 112 status_t status = _InitPtyReader(); 113 if (status < B_OK) { 114 fBuffer = NULL; 115 return status; 116 } 117 118 status = _InitTermParse(); 119 if (status < B_OK) { 120 _StopPtyReader(); 121 fBuffer = NULL; 122 return status; 123 } 124 125 return B_OK; 126 } 127 128 129 status_t 130 TermParse::StopThreads() 131 { 132 if (fBuffer == NULL) 133 return B_ERROR; 134 135 fQuitting = true; 136 137 _StopPtyReader(); 138 _StopTermParse(); 139 140 fBuffer = NULL; 141 142 return B_OK; 143 } 144 145 146 //! Initialize and spawn EscParse thread. 147 status_t 148 TermParse::_InitTermParse() 149 { 150 if (fParseThread >= 0) 151 return B_ERROR; // we might want to return B_OK instead ? 152 153 fParseThread = spawn_thread(_escparse_thread, "EscParse", 154 B_DISPLAY_PRIORITY, this); 155 156 if (fParseThread < 0) 157 return fParseThread; 158 159 resume_thread(fParseThread); 160 161 return B_OK; 162 } 163 164 165 //! Initialize and spawn PtyReader thread. 166 status_t 167 TermParse::_InitPtyReader() 168 { 169 if (fReaderThread >= 0) 170 return B_ERROR; // same as above 171 172 fReaderSem = create_sem(0, "pty_reader_sem"); 173 if (fReaderSem < 0) 174 return fReaderSem; 175 176 fReaderLocker = create_sem(0, "pty_locker_sem"); 177 if (fReaderLocker < 0) { 178 delete_sem(fReaderSem); 179 fReaderSem = -1; 180 return fReaderLocker; 181 } 182 183 fReaderThread = spawn_thread(_ptyreader_thread, "PtyReader", 184 B_NORMAL_PRIORITY, this); 185 if (fReaderThread < 0) { 186 delete_sem(fReaderSem); 187 fReaderSem = -1; 188 delete_sem(fReaderLocker); 189 fReaderLocker = -1; 190 return fReaderThread; 191 } 192 193 resume_thread(fReaderThread); 194 195 return B_OK; 196 } 197 198 199 void 200 TermParse::_StopTermParse() 201 { 202 if (fParseThread >= 0) { 203 status_t dummy; 204 wait_for_thread(fParseThread, &dummy); 205 fParseThread = -1; 206 } 207 } 208 209 210 void 211 TermParse::_StopPtyReader() 212 { 213 if (fReaderSem >= 0) { 214 delete_sem(fReaderSem); 215 fReaderSem = -1; 216 } 217 if (fReaderLocker >= 0) { 218 delete_sem(fReaderLocker); 219 fReaderLocker = -1; 220 } 221 222 if (fReaderThread >= 0) { 223 suspend_thread(fReaderThread); 224 225 status_t status; 226 wait_for_thread(fReaderThread, &status); 227 228 fReaderThread = -1; 229 } 230 } 231 232 233 //! Get data from pty device. 234 int32 235 TermParse::PtyReader() 236 { 237 int32 bufferSize = 0; 238 int32 readPos = 0; 239 while (!fQuitting) { 240 // If Pty Buffer nearly full, snooze this thread, and continue. 241 while (READ_BUF_SIZE - bufferSize < MIN_PTY_BUFFER_SPACE) { 242 status_t status; 243 do { 244 status = acquire_sem(fReaderLocker); 245 } while (status == B_INTERRUPTED); 246 if (status < B_OK) 247 return status; 248 249 bufferSize = fReadBufferSize; 250 } 251 252 // Read PTY 253 uchar buf[READ_BUF_SIZE]; 254 ssize_t nread = read(fFd, buf, READ_BUF_SIZE - bufferSize); 255 if (nread <= 0) { 256 fBuffer->NotifyQuit(errno); 257 return B_OK; 258 } 259 260 // Copy read string to PtyBuffer. 261 262 int32 left = READ_BUF_SIZE - readPos; 263 264 if (nread >= left) { 265 memcpy(fReadBuffer + readPos, buf, left); 266 memcpy(fReadBuffer, buf + left, nread - left); 267 } else 268 memcpy(fReadBuffer + readPos, buf, nread); 269 270 bufferSize = atomic_add(&fReadBufferSize, nread); 271 if (bufferSize == 0) 272 release_sem(fReaderSem); 273 274 bufferSize += nread; 275 readPos = (readPos + nread) % READ_BUF_SIZE; 276 } 277 278 return B_OK; 279 } 280 281 282 void 283 TermParse::DumpState(int *groundtable, int *parsestate, uchar c) 284 { 285 static const struct { 286 int *p; 287 const char *name; 288 } tables[] = { 289 #define T(t) \ 290 { t, #t } 291 T(gUTF8GroundTable), 292 T(gISO8859GroundTable), 293 T(gWinCPGroundTable), 294 T(gSJISGroundTable), 295 T(gEscTable), 296 T(gCsiTable), 297 T(gDecTable), 298 T(gScrTable), 299 T(gIgnoreTable), 300 T(gIesTable), 301 T(gEscIgnoreTable), 302 { NULL, NULL } 303 }; 304 int i; 305 fprintf(stderr, "groundtable: "); 306 for (i = 0; tables[i].p; i++) { 307 if (tables[i].p == groundtable) 308 fprintf(stderr, "%s\t", tables[i].name); 309 } 310 fprintf(stderr, "parsestate: "); 311 for (i = 0; tables[i].p; i++) { 312 if (tables[i].p == parsestate) 313 fprintf(stderr, "%s\t", tables[i].name); 314 } 315 fprintf(stderr, "char: 0x%02x (%d)\n", c, c); 316 } 317 318 319 int * 320 TermParse::_GuessGroundTable(int encoding) 321 { 322 switch (encoding) { 323 case B_ISO1_CONVERSION: 324 case B_ISO2_CONVERSION: 325 case B_ISO3_CONVERSION: 326 case B_ISO4_CONVERSION: 327 case B_ISO5_CONVERSION: 328 case B_ISO6_CONVERSION: 329 case B_ISO7_CONVERSION: 330 case B_ISO8_CONVERSION: 331 case B_ISO9_CONVERSION: 332 case B_ISO10_CONVERSION: 333 case B_ISO13_CONVERSION: 334 case B_ISO14_CONVERSION: 335 case B_ISO15_CONVERSION: 336 case B_EUC_CONVERSION: 337 case B_EUC_KR_CONVERSION: 338 case B_JIS_CONVERSION: 339 case B_BIG5_CONVERSION: 340 return gISO8859GroundTable; 341 342 case B_KOI8R_CONVERSION: 343 case B_MS_WINDOWS_1251_CONVERSION: 344 case B_MS_WINDOWS_CONVERSION: 345 case B_MAC_ROMAN_CONVERSION: 346 case B_MS_DOS_866_CONVERSION: 347 case B_GBK_CONVERSION: 348 case B_MS_DOS_CONVERSION: 349 return gWinCPGroundTable; 350 351 case B_SJIS_CONVERSION: 352 return gSJISGroundTable; 353 354 case M_UTF8: 355 default: 356 break; 357 } 358 359 return gUTF8GroundTable; 360 } 361 362 363 int32 364 TermParse::EscParse() 365 { 366 int top = 0; 367 int bottom = 0; 368 369 char cbuf[4] = { 0 }; 370 char dstbuf[4] = { 0 }; 371 372 int currentEncoding = -1; 373 374 int param[NPARAM]; 375 int nparam = 1; 376 for (int i = 0; i < NPARAM; i++) 377 param[i] = DEFAULT; 378 379 int row = 0; 380 int column = 0; 381 382 // default encoding system is UTF8 383 int *groundtable = gUTF8GroundTable; 384 int *parsestate = gUTF8GroundTable; 385 386 // handle alternative character sets G0 - G4 387 const char** graphSets[4] = { NULL, NULL, NULL, NULL }; 388 int curGL = 0; 389 int curGR = 0; 390 391 BAutolock locker(fBuffer); 392 393 while (!fQuitting) { 394 try { 395 uchar c = _NextParseChar(); 396 397 //DumpState(groundtable, parsestate, c); 398 399 if (currentEncoding != fBuffer->Encoding()) { 400 // Change coding, change parse table. 401 groundtable = _GuessGroundTable(fBuffer->Encoding()); 402 parsestate = groundtable; 403 currentEncoding = fBuffer->Encoding(); 404 } 405 406 //debug_printf("TermParse: char: '%c' (%d), parse state: %d\n", c, c, parsestate[c]); 407 int32 srcLen = 0; 408 int32 dstLen = sizeof(dstbuf); 409 int32 dummyState = 0; 410 411 switch (parsestate[c]) { 412 case CASE_PRINT: 413 { 414 int curGS = c < 128 ? curGL : curGR; 415 const char** curGraphSet = graphSets[curGS]; 416 if (curGraphSet != NULL) { 417 int offset = c - (c < 128 ? 0x20 : 0xA0); 418 if (offset >= 0 && offset < 96 419 && curGraphSet[offset] != 0) { 420 fBuffer->InsertChar(curGraphSet[offset]); 421 break; 422 } 423 } 424 fBuffer->InsertChar((char)c); 425 break; 426 } 427 case CASE_PRINT_GR: 428 { 429 /* case iso8859 gr character, or euc */ 430 switch (currentEncoding) { 431 case B_EUC_CONVERSION: 432 case B_EUC_KR_CONVERSION: 433 case B_JIS_CONVERSION: 434 case B_BIG5_CONVERSION: 435 cbuf[srcLen++] = c; 436 c = _NextParseChar(); 437 cbuf[srcLen++] = c; 438 break; 439 440 case B_GBK_CONVERSION: 441 cbuf[srcLen++] = c; 442 do { 443 // GBK-compatible codepoints are 2-bytes long 444 c = _NextParseChar(); 445 cbuf[srcLen++] = c; 446 447 // GB18030 extends GBK with 4-byte codepoints 448 // using 2nd byte from range 0x30...0x39 449 if (srcLen == 2 && (c < 0x30 || c > 0x39)) 450 break; 451 } while (srcLen < 4); 452 break; 453 454 default: // ISO-8859-1...10 and MacRoman 455 cbuf[srcLen++] = c; 456 break; 457 } 458 459 if (srcLen > 0) { 460 int encoding = currentEncoding == B_JIS_CONVERSION 461 ? B_EUC_CONVERSION : currentEncoding; 462 463 convert_to_utf8(encoding, cbuf, &srcLen, 464 dstbuf, &dstLen, &dummyState, '?'); 465 466 fBuffer->InsertChar(UTF8Char(dstbuf, dstLen)); 467 } 468 break; 469 } 470 471 case CASE_LF: 472 fBuffer->InsertLF(); 473 break; 474 475 case CASE_CR: 476 fBuffer->InsertCR(); 477 break; 478 479 case CASE_INDEX: 480 fBuffer->InsertLF(); 481 parsestate = groundtable; 482 break; 483 484 case CASE_NEXT_LINE: 485 fBuffer->NextLine(); 486 parsestate = groundtable; 487 break; 488 489 case CASE_SJIS_KANA: 490 cbuf[srcLen++] = c; 491 convert_to_utf8(currentEncoding, cbuf, &srcLen, 492 dstbuf, &dstLen, &dummyState, '?'); 493 fBuffer->InsertChar(UTF8Char(dstbuf, dstLen)); 494 break; 495 496 case CASE_SJIS_INSTRING: 497 cbuf[srcLen++] = c; 498 c = _NextParseChar(); 499 cbuf[srcLen++] = c; 500 501 convert_to_utf8(currentEncoding, cbuf, &srcLen, 502 dstbuf, &dstLen, &dummyState, '?'); 503 fBuffer->InsertChar(UTF8Char(dstbuf, dstLen)); 504 break; 505 506 case CASE_UTF8_2BYTE: 507 cbuf[srcLen++] = c; 508 c = _NextParseChar(); 509 if (groundtable[c] != CASE_UTF8_INSTRING) 510 break; 511 cbuf[srcLen++] = c; 512 513 fBuffer->InsertChar(UTF8Char(cbuf, srcLen)); 514 break; 515 516 case CASE_UTF8_3BYTE: 517 cbuf[srcLen++] = c; 518 519 do { 520 c = _NextParseChar(); 521 if (groundtable[c] != CASE_UTF8_INSTRING) { 522 srcLen = 0; 523 break; 524 } 525 cbuf[srcLen++] = c; 526 527 } while (srcLen != 3); 528 529 if (srcLen > 0) 530 fBuffer->InsertChar(UTF8Char(cbuf, srcLen)); 531 break; 532 533 case CASE_SCS_STATE: 534 { 535 int set = -1; 536 switch (c) { 537 case '(': 538 set = 0; 539 break; 540 case ')': 541 case '-': 542 set = 1; 543 break; 544 case '*': 545 case '.': 546 set = 2; 547 break; 548 case '+': 549 case '/': 550 set = 3; 551 break; 552 default: 553 break; 554 } 555 556 if (set > -1) { 557 char page = _NextParseChar(); 558 switch (page) { 559 case '0': 560 graphSets[set] = gLineDrawGraphSet; 561 break; 562 default: 563 graphSets[set] = NULL; 564 break; 565 } 566 } 567 568 parsestate = groundtable; 569 break; 570 } 571 572 case CASE_GROUND_STATE: 573 /* exit ignore mode */ 574 parsestate = groundtable; 575 break; 576 577 case CASE_BELL: 578 beep(); 579 break; 580 581 case CASE_BS: 582 fBuffer->MoveCursorLeft(1); 583 break; 584 585 case CASE_TAB: 586 fBuffer->InsertTab(); 587 break; 588 589 case CASE_ESC: 590 /* escape */ 591 parsestate = gEscTable; 592 break; 593 594 case CASE_IGNORE_STATE: 595 /* Ies: ignore anything else */ 596 parsestate = gIgnoreTable; 597 break; 598 599 case CASE_IGNORE_ESC: 600 /* Ign: escape */ 601 parsestate = gIesTable; 602 break; 603 604 case CASE_IGNORE: 605 /* Ignore character */ 606 break; 607 608 case CASE_LS1: 609 /* select G1 into GL */ 610 curGL = 1; 611 parsestate = groundtable; 612 break; 613 614 case CASE_LS0: 615 /* select G0 into GL */ 616 curGL = 0; 617 parsestate = groundtable; 618 break; 619 620 case CASE_SCR_STATE: // ESC # 621 /* enter scr state */ 622 parsestate = gScrTable; 623 break; 624 625 case CASE_ESC_IGNORE: 626 /* unknown escape sequence */ 627 parsestate = gEscIgnoreTable; 628 break; 629 630 case CASE_ESC_DIGIT: // ESC [ number 631 /* digit in csi or dec mode */ 632 if ((row = param[nparam - 1]) == DEFAULT) 633 row = 0; 634 param[nparam - 1] = 10 * row + (c - '0'); 635 break; 636 637 case CASE_ESC_SEMI: // ESC ; 638 /* semicolon in csi or dec mode */ 639 if (nparam < NPARAM) 640 param[nparam++] = DEFAULT; 641 break; 642 643 case CASE_CSI_SP: // ESC [N q 644 // part of change cursor style DECSCUSR 645 if (nparam < NPARAM) 646 param[nparam++] = ' '; 647 break; 648 649 case CASE_DEC_STATE: 650 /* enter dec mode */ 651 parsestate = gDecTable; 652 break; 653 654 case CASE_ICH: // ESC [@ insert charactor 655 /* ICH */ 656 if ((row = param[0]) < 1) 657 row = 1; 658 fBuffer->InsertSpace(row); 659 parsestate = groundtable; 660 break; 661 662 case CASE_CUU: // ESC [A cursor up, up arrow key. 663 /* CUU */ 664 if ((row = param[0]) < 1) 665 row = 1; 666 fBuffer->MoveCursorUp(row); 667 parsestate = groundtable; 668 break; 669 670 case CASE_CUD: // ESC [B cursor down, down arrow key. 671 /* CUD */ 672 if ((row = param[0]) < 1) 673 row = 1; 674 fBuffer->MoveCursorDown(row); 675 parsestate = groundtable; 676 break; 677 678 case CASE_CUF: // ESC [C cursor forword 679 /* CUF */ 680 if ((row = param[0]) < 1) 681 row = 1; 682 fBuffer->MoveCursorRight(row); 683 parsestate = groundtable; 684 break; 685 686 case CASE_CUB: // ESC [D cursor backword 687 /* CUB */ 688 if ((row = param[0]) < 1) 689 row = 1; 690 fBuffer->MoveCursorLeft(row); 691 parsestate = groundtable; 692 break; 693 694 case CASE_CUP: // ESC [...H move cursor 695 /* CUP | HVP */ 696 if ((row = param[0]) < 1) 697 row = 1; 698 if (nparam < 2 || (column = param[1]) < 1) 699 column = 1; 700 701 fBuffer->SetCursor(column - 1, row - 1 ); 702 parsestate = groundtable; 703 break; 704 705 case CASE_ED: // ESC [ ...J clear screen 706 /* ED */ 707 switch (param[0]) { 708 case DEFAULT: 709 case 0: 710 fBuffer->EraseBelow(); 711 break; 712 713 case 1: 714 fBuffer->EraseAbove(); 715 break; 716 717 case 2: 718 fBuffer->EraseAll(); 719 break; 720 721 case 3: 722 fBuffer->EraseScrollback(); 723 break; 724 } 725 parsestate = groundtable; 726 break; 727 728 case CASE_EL: // ESC [ ...K delete line 729 /* EL */ 730 switch (param[0]) { 731 case DEFAULT: 732 case 0: 733 fBuffer->DeleteColumns(); 734 break; 735 736 case 1: 737 fBuffer->EraseCharsFrom(0, fBuffer->Cursor().x + 1); 738 break; 739 740 case 2: 741 fBuffer->DeleteColumnsFrom(0); 742 break; 743 } 744 parsestate = groundtable; 745 break; 746 747 case CASE_IL: 748 /* IL */ 749 if ((row = param[0]) < 1) 750 row = 1; 751 fBuffer->InsertLines(row); 752 parsestate = groundtable; 753 break; 754 755 case CASE_DL: 756 /* DL */ 757 if ((row = param[0]) < 1) 758 row = 1; 759 fBuffer->DeleteLines(row); 760 parsestate = groundtable; 761 break; 762 763 case CASE_DCH: 764 /* DCH */ 765 if ((row = param[0]) < 1) 766 row = 1; 767 fBuffer->DeleteChars(row); 768 parsestate = groundtable; 769 break; 770 771 case CASE_SET: 772 /* SET */ 773 if (param[0] == 4) 774 fBuffer->SetInsertMode(MODE_INSERT); 775 parsestate = groundtable; 776 break; 777 778 case CASE_RST: 779 /* RST */ 780 if (param[0] == 4) 781 fBuffer->SetInsertMode(MODE_OVER); 782 parsestate = groundtable; 783 break; 784 785 case CASE_SGR: 786 { 787 /* SGR */ 788 Attributes attributes = fBuffer->GetAttributes(); 789 for (row = 0; row < nparam; ++row) { 790 switch (param[row]) { 791 case DEFAULT: 792 case 0: /* Reset attribute */ 793 attributes.Reset(); 794 break; 795 796 case 1: /* Bold */ 797 case 5: 798 attributes |= BOLD; 799 break; 800 801 case 4: /* Underline */ 802 if ((row + 1) < nparam) { 803 row++; 804 switch (param[row]) { 805 case 0: 806 attributes.UnsetUnder(); 807 break; 808 case 1: 809 attributes.SetUnder(SINGLE_UNDERLINE); 810 break; 811 case 2: 812 attributes.SetUnder(DOUBLE_UNDERLINE); 813 break; 814 case 3: 815 attributes.SetUnder(CURLY_UNDERLINE); 816 break; 817 case 4: 818 attributes.SetUnder(DOTTED_UNDERLINE); 819 break; 820 case 5: 821 attributes.SetUnder(DASHED_UNDERLINE); 822 break; 823 default: 824 row = nparam; // force exit of the parsing 825 break; 826 } 827 } else 828 attributes.SetUnder(SINGLE_UNDERLINE); 829 break; 830 831 case 7: /* Inverse */ 832 attributes |= INVERSE; 833 break; 834 835 case 21: /* Double Underline */ 836 attributes.SetUnder(DOUBLE_UNDERLINE); 837 break; 838 839 case 22: /* Not Bold */ 840 attributes &= ~BOLD; 841 break; 842 843 case 24: /* Not Underline */ 844 attributes.UnsetUnder(); 845 break; 846 847 case 27: /* Not Inverse */ 848 attributes &= ~INVERSE; 849 break; 850 851 case 90: 852 case 91: 853 case 92: 854 case 93: 855 case 94: 856 case 95: 857 case 96: 858 case 97: 859 param[row] -= 60; 860 case 30: 861 case 31: 862 case 32: 863 case 33: 864 case 34: 865 case 35: 866 case 36: 867 case 37: 868 attributes.SetIndexedForeground(param[row] - 30); 869 break; 870 871 case 38: 872 { 873 if (nparam >= 3 && param[row+1] == 5) { 874 attributes.SetIndexedForeground(param[row+2]); 875 row += 2; 876 } else if (nparam >= 5 && param[row+1] == 2) { 877 attributes.SetDirectForeground(param[row+2], param[row+3], param[row+4]); 878 row += 4; 879 } else { 880 row = nparam; // force exit of the parsing 881 } 882 883 break; 884 } 885 886 case 39: 887 attributes.UnsetForeground(); 888 break; 889 890 case 100: 891 case 101: 892 case 102: 893 case 103: 894 case 104: 895 case 105: 896 case 106: 897 case 107: 898 param[row] -= 60; 899 case 40: 900 case 41: 901 case 42: 902 case 43: 903 case 44: 904 case 45: 905 case 46: 906 case 47: 907 attributes.SetIndexedBackground(param[row] - 40); 908 break; 909 910 case 48: 911 { 912 if (nparam >= 3 && param[row+1] == 5) { 913 attributes.SetIndexedBackground(param[row+2]); 914 row += 2; 915 } else if (nparam >= 5 && param[row+1] == 2) { 916 attributes.SetDirectBackground(param[row+2], param[row+3], param[row+4]); 917 row += 4; 918 } else { 919 row = nparam; // force exit of the parsing 920 } 921 922 break; 923 } 924 925 case 49: 926 attributes.UnsetBackground(); 927 break; 928 929 case 58: 930 { 931 if (nparam >= 3 && param[row+1] == 5) { 932 attributes.SetIndexedUnderline(param[row+2]); 933 row += 2; 934 } else if (nparam >= 5 && param[row+1] == 2) { 935 attributes.SetDirectUnderline(param[row+2], param[row+3], param[row+4]); 936 row += 4; 937 } else { 938 row = nparam; // force exit of the parsing 939 } 940 941 break; 942 } 943 944 case 59: 945 attributes.UnsetUnderline(); 946 break; 947 } 948 } 949 fBuffer->SetAttributes(attributes); 950 parsestate = groundtable; 951 break; 952 } 953 954 case CASE_CPR: 955 // Q & D hack by Y.Hayakawa (hida@sawada.riec.tohoku.ac.jp) 956 // 21-JUL-99 957 _DeviceStatusReport(param[0]); 958 parsestate = groundtable; 959 break; 960 961 case CASE_DA1: 962 // DA - report device attributes 963 if (param[0] < 1) { 964 // claim to be a VT102 965 write(fFd, "\033[?6c", 5); 966 } 967 parsestate = groundtable; 968 break; 969 970 case CASE_DECSTBM: 971 /* DECSTBM - set scrolling region */ 972 973 if ((top = param[0]) < 1) 974 top = 1; 975 976 if (nparam < 2) 977 bottom = fBuffer->Height(); 978 else 979 bottom = param[1]; 980 981 top--; 982 bottom--; 983 984 if (bottom > top) 985 fBuffer->SetScrollRegion(top, bottom); 986 987 parsestate = groundtable; 988 break; 989 990 case CASE_DECSCUSR_ETC: 991 // DECSCUSR - set cursor style VT520 992 if (nparam == 2 && param[1] == ' ') { 993 bool blinking = (param[0] & 0x01) != 0; 994 int style = -1; 995 switch (param[0]) { 996 case 0: 997 blinking = true; 998 case 1: 999 case 2: 1000 style = BLOCK_CURSOR; 1001 break; 1002 case 3: 1003 case 4: 1004 style = UNDERLINE_CURSOR; 1005 break; 1006 case 5: 1007 case 6: 1008 style = IBEAM_CURSOR; 1009 break; 1010 } 1011 1012 if (style != -1) 1013 fBuffer->SetCursorStyle(style, blinking); 1014 } 1015 parsestate = groundtable; 1016 break; 1017 1018 case CASE_DECREQTPARM: 1019 // DEXREQTPARM - request terminal parameters 1020 _DecReqTermParms(param[0]); 1021 parsestate = groundtable; 1022 break; 1023 1024 case CASE_DECSET: 1025 /* DECSET */ 1026 for (int i = 0; i < nparam; i++) 1027 _DecPrivateModeSet(param[i]); 1028 parsestate = groundtable; 1029 break; 1030 1031 case CASE_DECRST: 1032 /* DECRST */ 1033 for (int i = 0; i < nparam; i++) 1034 _DecPrivateModeReset(param[i]); 1035 parsestate = groundtable; 1036 break; 1037 1038 case CASE_DECALN: 1039 /* DECALN */ 1040 { 1041 Attributes attr; 1042 fBuffer->FillScreen(UTF8Char('E'), attr); 1043 parsestate = groundtable; 1044 } 1045 break; 1046 1047 // case CASE_GSETS: 1048 // screen->gsets[scstype] = GSET(c) | cs96; 1049 // parsestate = groundtable; 1050 // break; 1051 1052 case CASE_DECSC: 1053 /* DECSC */ 1054 fBuffer->SaveCursor(); 1055 parsestate = groundtable; 1056 break; 1057 1058 case CASE_DECRC: 1059 /* DECRC */ 1060 fBuffer->RestoreCursor(); 1061 parsestate = groundtable; 1062 break; 1063 1064 case CASE_HTS: 1065 /* HTS */ 1066 fBuffer->SetTabStop(fBuffer->Cursor().x); 1067 parsestate = groundtable; 1068 break; 1069 1070 case CASE_TBC: 1071 /* TBC */ 1072 if (param[0] < 1) 1073 fBuffer->ClearTabStop(fBuffer->Cursor().x); 1074 else if (param[0] == 3) 1075 fBuffer->ClearAllTabStops(); 1076 parsestate = groundtable; 1077 break; 1078 1079 case CASE_RI: 1080 /* RI */ 1081 fBuffer->InsertRI(); 1082 parsestate = groundtable; 1083 break; 1084 1085 case CASE_SS2: 1086 /* SS2 */ 1087 parsestate = groundtable; 1088 break; 1089 1090 case CASE_SS3: 1091 /* SS3 */ 1092 parsestate = groundtable; 1093 break; 1094 1095 case CASE_CSI_STATE: 1096 /* enter csi state */ 1097 nparam = 1; 1098 param[0] = DEFAULT; 1099 parsestate = gCsiTable; 1100 break; 1101 1102 case CASE_OSC: 1103 { 1104 /* Operating System Command: ESC ] */ 1105 uchar params[512]; 1106 // fill the buffer until BEL, ST or something else. 1107 bool isParsed = false; 1108 int32 skipCount = 0; // take care about UTF-8 characters 1109 for (uint i = 0; !isParsed && i < sizeof(params); i++) { 1110 params[i] = _NextParseChar(); 1111 1112 if (skipCount > 0) { 1113 skipCount--; 1114 continue; 1115 } 1116 1117 skipCount = UTF8Char::ByteCount(params[i]) - 1; 1118 if (skipCount > 0) 1119 continue; 1120 1121 switch (params[i]) { 1122 // BEL 1123 case 0x07: 1124 isParsed = true; 1125 break; 1126 // 8-bit ST 1127 case 0x9c: 1128 isParsed = true; 1129 break; 1130 // 7-bit ST is "ESC \" 1131 case '\\': 1132 // hm... Was \x1b replaced by 0 during parsing? 1133 if (i > 0 && params[i - 1] == 0) { 1134 isParsed = true; 1135 break; 1136 } 1137 default: 1138 if (!isprint(params[i] & 0x7f)) 1139 break; 1140 continue; 1141 } 1142 params[i] = '\0'; 1143 } 1144 1145 // watchdog for the 'end of buffer' case 1146 params[sizeof(params) - 1] = '\0'; 1147 1148 if (isParsed) 1149 _ProcessOperatingSystemControls(params); 1150 1151 parsestate = groundtable; 1152 break; 1153 } 1154 1155 case CASE_RIS: // ESC c ... Reset terminal. 1156 break; 1157 1158 case CASE_LS2: 1159 /* select G2 into GL */ 1160 curGL = 2; 1161 parsestate = groundtable; 1162 break; 1163 1164 case CASE_LS3: 1165 /* select G3 into GL */ 1166 curGL = 3; 1167 parsestate = groundtable; 1168 break; 1169 1170 case CASE_LS3R: 1171 /* select G3 into GR */ 1172 curGR = 3; 1173 parsestate = groundtable; 1174 break; 1175 1176 case CASE_LS2R: 1177 /* select G2 into GR */ 1178 curGR = 2; 1179 parsestate = groundtable; 1180 break; 1181 1182 case CASE_LS1R: 1183 /* select G1 into GR */ 1184 curGR = 1; 1185 parsestate = groundtable; 1186 break; 1187 1188 case CASE_VPA: // ESC [...d move cursor absolute vertical 1189 /* VPA (CV) */ 1190 if ((row = param[0]) < 1) 1191 row = 1; 1192 1193 // note beterm wants it 1-based unlike usual terminals 1194 fBuffer->SetCursorY(row - 1); 1195 parsestate = groundtable; 1196 break; 1197 1198 case CASE_HPA: // ESC [...G move cursor absolute horizontal 1199 /* HPA (CH) */ 1200 if ((column = param[0]) < 1) 1201 column = 1; 1202 1203 // note beterm wants it 1-based unlike usual terminals 1204 fBuffer->SetCursorX(column - 1); 1205 parsestate = groundtable; 1206 break; 1207 1208 case CASE_SU: // scroll screen up 1209 if ((row = param[0]) < 1) 1210 row = 1; 1211 fBuffer->ScrollBy(row); 1212 parsestate = groundtable; 1213 break; 1214 1215 case CASE_SD: // scroll screen down 1216 if ((row = param[0]) < 1) 1217 row = 1; 1218 fBuffer->ScrollBy(-row); 1219 parsestate = groundtable; 1220 break; 1221 1222 1223 case CASE_ECH: // erase characters 1224 if ((column = param[0]) < 1) 1225 column = 1; 1226 fBuffer->EraseChars(column); 1227 parsestate = groundtable; 1228 break; 1229 1230 case CASE_CBT: // cursor back tab 1231 if ((column = param[0]) < 1) 1232 column = 1; 1233 fBuffer->InsertCursorBackTab(column); 1234 parsestate = groundtable; 1235 break; 1236 1237 case CASE_CFT: // cursor forward tab 1238 if ((column= param[0]) < 1) 1239 column = 1; 1240 for (int32 i = 0; i < column; ++i) 1241 fBuffer->InsertTab(); 1242 parsestate = groundtable; 1243 break; 1244 1245 case CASE_CNL: // cursor next line 1246 if ((row= param[0]) < 1) 1247 row = 1; 1248 fBuffer->SetCursorX(0); 1249 fBuffer->MoveCursorDown(row); 1250 parsestate = groundtable; 1251 break; 1252 1253 case CASE_CPL: // cursor previous line 1254 if ((row= param[0]) < 1) 1255 row = 1; 1256 fBuffer->SetCursorX(0); 1257 fBuffer->MoveCursorUp(row); 1258 parsestate = groundtable; 1259 break; 1260 1261 case CASE_REP: // ESC [...b repeat last graphic char 1262 { 1263 int repetitions = param[0]; 1264 int maxRepetitions = fBuffer->Width() * fBuffer->Height(); 1265 if (repetitions > maxRepetitions) 1266 repetitions = maxRepetitions; 1267 for (int i = 0; i < repetitions; i++) 1268 fBuffer->InsertLastChar(); 1269 parsestate = groundtable; 1270 break; 1271 } 1272 default: 1273 break; 1274 } 1275 } catch (...) { 1276 break; 1277 } 1278 } 1279 1280 return B_OK; 1281 } 1282 1283 1284 /*static*/ int32 1285 TermParse::_ptyreader_thread(void *data) 1286 { 1287 return reinterpret_cast<TermParse *>(data)->PtyReader(); 1288 } 1289 1290 1291 /*static*/ int32 1292 TermParse::_escparse_thread(void *data) 1293 { 1294 return reinterpret_cast<TermParse *>(data)->EscParse(); 1295 } 1296 1297 1298 status_t 1299 TermParse::_ReadParserBuffer() 1300 { 1301 // We have to unlock the terminal buffer while waiting for data from the 1302 // PTY. We don't have to unlock when we don't need to wait, but we do it 1303 // anyway, so that TermView won't be starved when trying to synchronize. 1304 fBuffer->Unlock(); 1305 1306 // wait for new input from pty 1307 if (atomic_get(&fReadBufferSize) == 0) { 1308 status_t status = B_OK; 1309 while (atomic_get(&fReadBufferSize) == 0 && status == B_OK) { 1310 do { 1311 status = acquire_sem(fReaderSem); 1312 } while (status == B_INTERRUPTED); 1313 1314 // eat any sems that were released unconditionally 1315 int32 semCount; 1316 if (get_sem_count(fReaderSem, &semCount) == B_OK && semCount > 0) 1317 acquire_sem_etc(fReaderSem, semCount, B_RELATIVE_TIMEOUT, 0); 1318 } 1319 1320 if (status < B_OK) { 1321 fBuffer->Lock(); 1322 return status; 1323 } 1324 } 1325 1326 int32 toRead = atomic_get(&fReadBufferSize); 1327 if (toRead > ESC_PARSER_BUFFER_SIZE) 1328 toRead = ESC_PARSER_BUFFER_SIZE; 1329 1330 for (int32 i = 0; i < toRead; i++) { 1331 // TODO: This could be optimized using memcpy instead and 1332 // calculating space left as in the PtyReader(). 1333 fParserBuffer[i] = fReadBuffer[fBufferPosition]; 1334 fBufferPosition = (fBufferPosition + 1) % READ_BUF_SIZE; 1335 } 1336 1337 int32 bufferSize = atomic_add(&fReadBufferSize, -toRead); 1338 1339 // If the pty reader thread waits and we have made enough space in the 1340 // buffer now, let it run again. 1341 if (bufferSize > READ_BUF_SIZE - MIN_PTY_BUFFER_SPACE 1342 && bufferSize - toRead <= READ_BUF_SIZE - MIN_PTY_BUFFER_SPACE) { 1343 release_sem(fReaderLocker); 1344 } 1345 1346 fParserBufferSize = toRead; 1347 fParserBufferOffset = 0; 1348 1349 fBuffer->Lock(); 1350 return B_OK; 1351 } 1352 1353 1354 void 1355 TermParse::_DeviceStatusReport(int n) 1356 { 1357 char sbuf[16] ; 1358 int len; 1359 1360 switch (n) { 1361 case 5: 1362 { 1363 // Device status report requested 1364 // reply with "no malfunction detected" 1365 const char* toWrite = "\033[0n"; 1366 write(fFd, toWrite, strlen(toWrite)); 1367 break ; 1368 } 1369 case 6: 1370 // Cursor position report requested 1371 len = snprintf(sbuf, sizeof(sbuf), 1372 "\033[%" B_PRId32 ";%" B_PRId32 "R", 1373 fBuffer->Cursor().y + 1, 1374 fBuffer->Cursor().x + 1); 1375 write(fFd, sbuf, len); 1376 break ; 1377 default: 1378 return; 1379 } 1380 } 1381 1382 1383 void 1384 TermParse::_DecReqTermParms(int value) 1385 { 1386 // Terminal parameters report: 1387 // type (2 or 3); 1388 // no parity (1); 1389 // 8 bits per character (1); 1390 // transmit speed 38400bps (128); 1391 // receive speed 38400bps (128); 1392 // bit rate multiplier 16 (1); 1393 // no flags (0) 1394 char parms[] = "\033[?;1;1;128;128;1;0x"; 1395 1396 if (value < 1) 1397 parms[2] = '2'; 1398 else if (value == 1) 1399 parms[2] = '3'; 1400 else 1401 return; 1402 1403 write(fFd, parms, strlen(parms)); 1404 } 1405 1406 1407 void 1408 TermParse::_DecPrivateModeSet(int value) 1409 { 1410 switch (value) { 1411 case 1: 1412 // Application Cursor Keys (whatever that means). 1413 // Not supported yet. 1414 break; 1415 case 5: 1416 // Reverse Video (inverses colors for the complete screen 1417 // -- when followed by normal video, that's shortly flashes the 1418 // screen). 1419 // Not supported yet. 1420 break; 1421 case 6: 1422 // Set Origin Mode. 1423 fBuffer->SetOriginMode(true); 1424 break; 1425 case 9: 1426 // Set Mouse X and Y on button press. 1427 fBuffer->ReportX10MouseEvent(true); 1428 break; 1429 case 12: 1430 // Start Blinking Cursor. 1431 fBuffer->SetCursorBlinking(true); 1432 break; 1433 case 25: 1434 // Show Cursor. 1435 fBuffer->SetCursorHidden(false); 1436 break; 1437 case 47: 1438 // Use Alternate Screen Buffer. 1439 fBuffer->UseAlternateScreenBuffer(false); 1440 break; 1441 case 1000: 1442 // Send Mouse X & Y on button press and release. 1443 fBuffer->ReportNormalMouseEvent(true); 1444 break; 1445 case 1002: 1446 // Send Mouse X and Y on button press and release, and on motion 1447 // when the mouse enter a new cell 1448 fBuffer->ReportButtonMouseEvent(true); 1449 break; 1450 case 1003: 1451 // Use All Motion Mouse Tracking 1452 fBuffer->ReportAnyMouseEvent(true); 1453 break; 1454 case 1006: 1455 // Enable extended mouse coordinates with SGR scheme 1456 fBuffer->EnableExtendedMouseCoordinates(true); 1457 break; 1458 case 1034: 1459 // Interpret "meta" key, sets eighth bit. 1460 fBuffer->EnableInterpretMetaKey(true); 1461 break; 1462 case 1036: 1463 // Send ESC when Meta modifies a key 1464 fBuffer->EnableMetaKeySendsEscape(true); 1465 break; 1466 case 1039: 1467 // TODO: Send ESC when Alt modifies a key 1468 // Not supported yet. 1469 break; 1470 case 1049: 1471 // Save cursor as in DECSC and use Alternate Screen Buffer, clearing 1472 // it first. 1473 fBuffer->SaveCursor(); 1474 fBuffer->UseAlternateScreenBuffer(true); 1475 break; 1476 case 2004: 1477 // Enable bracketed paste mode 1478 fBuffer->EnableBracketedPasteMode(true); 1479 break; 1480 } 1481 } 1482 1483 1484 void 1485 TermParse::_DecPrivateModeReset(int value) 1486 { 1487 switch (value) { 1488 case 1: 1489 // Normal Cursor Keys (whatever that means). 1490 // Not supported yet. 1491 break; 1492 case 3: 1493 // 80 Column Mode. 1494 // Not supported yet. 1495 break; 1496 case 4: 1497 // Jump (Fast) Scroll. 1498 // Not supported yet. 1499 break; 1500 case 5: 1501 // Normal Video (Leaves Reverse Video, cf. there). 1502 // Not supported yet. 1503 break; 1504 case 6: 1505 // Reset Origin Mode. 1506 fBuffer->SetOriginMode(false); 1507 break; 1508 case 9: 1509 // Disable Mouse X and Y on button press. 1510 fBuffer->ReportX10MouseEvent(false); 1511 break; 1512 case 12: 1513 // Stop Blinking Cursor. 1514 fBuffer->SetCursorBlinking(false); 1515 break; 1516 case 25: 1517 // Hide Cursor 1518 fBuffer->SetCursorHidden(true); 1519 break; 1520 case 47: 1521 // Use Normal Screen Buffer. 1522 fBuffer->UseNormalScreenBuffer(); 1523 break; 1524 case 1000: 1525 // Don't send Mouse X & Y on button press and release. 1526 fBuffer->ReportNormalMouseEvent(false); 1527 break; 1528 case 1002: 1529 // Don't send Mouse X and Y on button press and release, and on motion 1530 // when the mouse enter a new cell 1531 fBuffer->ReportButtonMouseEvent(false); 1532 break; 1533 case 1003: 1534 // Disable All Motion Mouse Tracking. 1535 fBuffer->ReportAnyMouseEvent(false); 1536 break; 1537 case 1006: 1538 // Disable extended mouse coordinates with SGR scheme 1539 fBuffer->EnableExtendedMouseCoordinates(false); 1540 break; 1541 case 1034: 1542 // Don't interpret "meta" key. 1543 fBuffer->EnableInterpretMetaKey(false); 1544 break; 1545 case 1036: 1546 // Don't send ESC when Meta modifies a key 1547 fBuffer->EnableMetaKeySendsEscape(false); 1548 break; 1549 case 1039: 1550 // TODO: Don't send ESC when Alt modifies a key 1551 // Not supported yet. 1552 break; 1553 case 1049: 1554 // Use Normal Screen Buffer and restore cursor as in DECRC. 1555 fBuffer->UseNormalScreenBuffer(); 1556 fBuffer->RestoreCursor(); 1557 break; 1558 case 2004: 1559 // Disable bracketed paste mode 1560 fBuffer->EnableBracketedPasteMode(false); 1561 break; 1562 } 1563 } 1564 1565 1566 void 1567 TermParse::_ProcessOperatingSystemControls(uchar* params) 1568 { 1569 int mode = 0; 1570 for (uchar c = *params; c != ';' && c != '\0'; c = *(++params)) { 1571 mode *= 10; 1572 mode += c - '0'; 1573 } 1574 1575 // eat the separator 1576 if (*params == ';') 1577 params++; 1578 1579 static uint8 indexes[kTermColorCount]; 1580 static rgb_color colors[kTermColorCount]; 1581 1582 switch (mode) { 1583 case 0: // icon name and window title 1584 case 2: // window title 1585 fBuffer->SetTitle((const char*)params); 1586 break; 1587 case 4: // set colors (0 - 255) 1588 case 104: // reset colors (0 - 255) 1589 { 1590 bool reset = (mode / 100) == 1; 1591 1592 // colors can be in "idx1:name1;...;idxN:nameN;" sequence too! 1593 uint32 count = 0; 1594 char* p = strtok((char*)params, ";"); 1595 while (p != NULL && count < kTermColorCount) { 1596 indexes[count] = atoi(p); 1597 1598 if (!reset) { 1599 p = strtok(NULL, ";"); 1600 if (p == NULL) 1601 break; 1602 1603 if (gXColorsTable.LookUpColor(p, &colors[count]) == B_OK) 1604 count++; 1605 } else 1606 count++; 1607 1608 p = strtok(NULL, ";"); 1609 }; 1610 1611 if (count > 0) { 1612 if (!reset) 1613 fBuffer->SetColors(indexes, colors, count); 1614 else 1615 fBuffer->ResetColors(indexes, count); 1616 } 1617 } 1618 break; 1619 // set dynamic colors (10 - 19) 1620 case 10: // text foreground 1621 case 11: // text background 1622 case 12: // cursor back 1623 { 1624 int32 offset = mode - 10; 1625 int32 count = 0; 1626 if (strcmp((char*)params, "?") == 0) { 1627 fBuffer->GetColor(mode); 1628 break; 1629 } 1630 char* p = strtok((char*)params, ";"); 1631 do { 1632 if (gXColorsTable.LookUpColor(p, &colors[count]) != B_OK) { 1633 // dyna-colors are pos-sensitive - no chance to continue 1634 break; 1635 } 1636 1637 indexes[count] = 10 + offset + count; 1638 count++; 1639 p = strtok(NULL, ";"); 1640 1641 } while (p != NULL && (offset + count) < 10); 1642 1643 if (count > 0) { 1644 fBuffer->SetColors(indexes, colors, count, true); 1645 } 1646 } 1647 break; 1648 // reset dynamic colors (10 - 19) 1649 case 110: // text foreground 1650 case 111: // text background 1651 case 112: // cursor back 1652 { 1653 indexes[0] = mode; 1654 fBuffer->ResetColors(indexes, 1, true); 1655 } 1656 break; 1657 default: 1658 // printf("%d -> %s\n", mode, params); 1659 break; 1660 } 1661 } 1662