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