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
_NextParseChar()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
TermParse(int fd)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
~TermParse()97 TermParse::~TermParse()
98 {
99 StopThreads();
100 }
101
102
103 status_t
StartThreads(TerminalBuffer * buffer)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
StopThreads()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
_InitTermParse()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
_InitPtyReader()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
_StopTermParse()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
_StopPtyReader()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
PtyReader()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
DumpState(int * groundtable,int * parsestate,uchar c)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 *
_GuessGroundTable(int encoding)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
EscParse()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
_ptyreader_thread(void * data)1285 TermParse::_ptyreader_thread(void *data)
1286 {
1287 return reinterpret_cast<TermParse *>(data)->PtyReader();
1288 }
1289
1290
1291 /*static*/ int32
_escparse_thread(void * data)1292 TermParse::_escparse_thread(void *data)
1293 {
1294 return reinterpret_cast<TermParse *>(data)->EscParse();
1295 }
1296
1297
1298 status_t
_ReadParserBuffer()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
_DeviceStatusReport(int n)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
_DecReqTermParms(int value)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
_DecPrivateModeSet(int value)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
_DecPrivateModeReset(int value)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
_ProcessOperatingSystemControls(uchar * params)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