xref: /haiku/src/apps/terminal/TermParse.cpp (revision 37fedaf8494b34aad811abcc49e79aa32943f880)
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 						int32 skipCount = 0; // take care about UTF-8 characters
1052 						for (uint i = 0; !isParsed && i < sizeof(params); i++) {
1053 							params[i] = _NextParseChar();
1054 
1055 							if (skipCount > 0) {
1056 								skipCount--;
1057 								continue;
1058 							}
1059 
1060 							skipCount = UTF8Char::ByteCount(params[i]) - 1;
1061 							if (skipCount > 0)
1062 								continue;
1063 
1064 							switch (params[i]) {
1065 								// BEL
1066 								case 0x07:
1067 									isParsed = true;
1068 									break;
1069 								// 8-bit ST
1070 								case 0x9c:
1071 									isParsed = true;
1072 									break;
1073 								// 7-bit ST is "ESC \"
1074 								case '\\':
1075 								// hm... Was \x1b replaced by 0 during parsing?
1076 									if (i > 0 && params[i - 1] == 0) {
1077 										isParsed = true;
1078 										break;
1079 									}
1080 								default:
1081 									if (!isprint(params[i] & 0x7f))
1082 										break;
1083 									continue;
1084 							}
1085 							params[i] = '\0';
1086 						}
1087 
1088 						// watchdog for the 'end of buffer' case
1089 						params[sizeof(params) - 1] = '\0';
1090 
1091 						if (isParsed)
1092 							_ProcessOperatingSystemControls(params);
1093 
1094 						parsestate = groundtable;
1095 						break;
1096 					}
1097 
1098 				case CASE_RIS:		// ESC c ... Reset terminal.
1099 					break;
1100 
1101 				case CASE_LS2:
1102 					/* select G2 into GL */
1103 					curGL = 2;
1104 					parsestate = groundtable;
1105 					break;
1106 
1107 				case CASE_LS3:
1108 					/* select G3 into GL */
1109 					curGL = 3;
1110 					parsestate = groundtable;
1111 					break;
1112 
1113 				case CASE_LS3R:
1114 					/* select G3 into GR */
1115 					curGR = 3;
1116 					parsestate = groundtable;
1117 					break;
1118 
1119 				case CASE_LS2R:
1120 					/* select G2 into GR */
1121 					curGR = 2;
1122 					parsestate = groundtable;
1123 					break;
1124 
1125 				case CASE_LS1R:
1126 					/* select G1 into GR */
1127 					curGR = 1;
1128 					parsestate = groundtable;
1129 					break;
1130 
1131 				case CASE_VPA:		// ESC [...d move cursor absolute vertical
1132 					/* VPA (CV) */
1133 					if ((row = param[0]) < 1)
1134 						row = 1;
1135 
1136 					// note beterm wants it 1-based unlike usual terminals
1137 					fBuffer->SetCursorY(row - 1);
1138 					parsestate = groundtable;
1139 					break;
1140 
1141 				case CASE_HPA:		// ESC [...G move cursor absolute horizontal
1142 					/* HPA (CH) */
1143 					if ((column = param[0]) < 1)
1144 						column = 1;
1145 
1146 					// note beterm wants it 1-based unlike usual terminals
1147 					fBuffer->SetCursorX(column - 1);
1148 					parsestate = groundtable;
1149 					break;
1150 
1151 				case CASE_SU:	// scroll screen up
1152 					if ((row = param[0]) < 1)
1153 						row = 1;
1154 					fBuffer->ScrollBy(row);
1155 					parsestate = groundtable;
1156 					break;
1157 
1158 				case CASE_SD:	// scroll screen down
1159 					if ((row = param[0]) < 1)
1160 						row = 1;
1161 					fBuffer->ScrollBy(-row);
1162 					parsestate = groundtable;
1163 					break;
1164 
1165 
1166 				case CASE_ECH:	// erase characters
1167 					if ((column = param[0]) < 1)
1168 						column = 1;
1169 					fBuffer->EraseChars(column);
1170 					parsestate = groundtable;
1171 					break;
1172 
1173 				default:
1174 					break;
1175 			}
1176 		} catch (...) {
1177 			break;
1178 		}
1179 	}
1180 
1181 	return B_OK;
1182 }
1183 
1184 
1185 /*static*/ int32
1186 TermParse::_ptyreader_thread(void *data)
1187 {
1188 	return reinterpret_cast<TermParse *>(data)->PtyReader();
1189 }
1190 
1191 
1192 /*static*/ int32
1193 TermParse::_escparse_thread(void *data)
1194 {
1195 	return reinterpret_cast<TermParse *>(data)->EscParse();
1196 }
1197 
1198 
1199 status_t
1200 TermParse::_ReadParserBuffer()
1201 {
1202 	// We have to unlock the terminal buffer while waiting for data from the
1203 	// PTY. We don't have to unlock when we don't need to wait, but we do it
1204 	// anyway, so that TermView won't be starved when trying to synchronize.
1205 	fBuffer->Unlock();
1206 
1207 	// wait for new input from pty
1208 	if (atomic_get(&fReadBufferSize) == 0) {
1209 		status_t status = B_OK;
1210 		while (atomic_get(&fReadBufferSize) == 0 && status == B_OK) {
1211 			do {
1212 				status = acquire_sem(fReaderSem);
1213 			} while (status == B_INTERRUPTED);
1214 
1215 			// eat any sems that were released unconditionally
1216 			int32 semCount;
1217 			if (get_sem_count(fReaderSem, &semCount) == B_OK && semCount > 0)
1218 				acquire_sem_etc(fReaderSem, semCount, B_RELATIVE_TIMEOUT, 0);
1219 		}
1220 
1221 		if (status < B_OK) {
1222 			fBuffer->Lock();
1223 			return status;
1224 		}
1225 	}
1226 
1227 	int32 toRead = atomic_get(&fReadBufferSize);
1228 	if (toRead > ESC_PARSER_BUFFER_SIZE)
1229 		toRead = ESC_PARSER_BUFFER_SIZE;
1230 
1231 	for (int32 i = 0; i < toRead; i++) {
1232 		// TODO: This could be optimized using memcpy instead and
1233 		// calculating space left as in the PtyReader().
1234 		fParserBuffer[i] = fReadBuffer[fBufferPosition];
1235 		fBufferPosition = (fBufferPosition + 1) % READ_BUF_SIZE;
1236 	}
1237 
1238 	int32 bufferSize = atomic_add(&fReadBufferSize, -toRead);
1239 
1240 	// If the pty reader thread waits and we have made enough space in the
1241 	// buffer now, let it run again.
1242 	if (bufferSize > READ_BUF_SIZE - MIN_PTY_BUFFER_SPACE
1243 			&& bufferSize - toRead <= READ_BUF_SIZE - MIN_PTY_BUFFER_SPACE) {
1244 		release_sem(fReaderLocker);
1245 	}
1246 
1247 	fParserBufferSize = toRead;
1248 	fParserBufferOffset = 0;
1249 
1250 	fBuffer->Lock();
1251 	return B_OK;
1252 }
1253 
1254 
1255 void
1256 TermParse::_DeviceStatusReport(int n)
1257 {
1258 	char sbuf[16] ;
1259 	int len;
1260 
1261 	switch (n) {
1262 		case 5:
1263 			{
1264 				// Device status report requested
1265 				// reply with "no malfunction detected"
1266 				const char* toWrite = "\033[0n";
1267 				write(fFd, toWrite, strlen(toWrite));
1268 				break ;
1269 			}
1270 		case 6:
1271 			// Cursor position report requested
1272 			len = sprintf(sbuf, "\033[%" B_PRId32 ";%" B_PRId32 "R",
1273 					fBuffer->Cursor().y + 1,
1274 					fBuffer->Cursor().x + 1);
1275 			write(fFd, sbuf, len);
1276 			break ;
1277 		default:
1278 			return;
1279 	}
1280 }
1281 
1282 
1283 void
1284 TermParse::_DecReqTermParms(int value)
1285 {
1286 	// Terminal parameters report:
1287 	//   type (2 or 3);
1288 	//   no parity (1);
1289 	//   8 bits per character (1);
1290 	//   transmit speed 38400bps (128);
1291 	//   receive speed 38400bps (128);
1292 	//   bit rate multiplier 16 (1);
1293 	//   no flags (0)
1294 	char parms[] = "\033[?;1;1;128;128;1;0x";
1295 
1296 	if (value < 1)
1297 		parms[2] = '2';
1298 	else if (value == 1)
1299 		parms[2] = '3';
1300 	else
1301 		return;
1302 
1303 	write(fFd, parms, strlen(parms));
1304 }
1305 
1306 
1307 void
1308 TermParse::_DecPrivateModeSet(int value)
1309 {
1310 	switch (value) {
1311 		case 1:
1312 			// Application Cursor Keys (whatever that means).
1313 			// Not supported yet.
1314 			break;
1315 		case 5:
1316 			// Reverse Video (inverses colors for the complete screen
1317 			// -- when followed by normal video, that's shortly flashes the
1318 			// screen).
1319 			// Not supported yet.
1320 			break;
1321 		case 6:
1322 			// Set Origin Mode.
1323 			fBuffer->SetOriginMode(true);
1324 			break;
1325 		case 9:
1326 			// Set Mouse X and Y on button press.
1327 			fBuffer->ReportX10MouseEvent(true);
1328 			break;
1329 		case 12:
1330 			// Start Blinking Cursor.
1331 			fBuffer->SetCursorBlinking(true);
1332 			break;
1333 		case 25:
1334 			// Show Cursor.
1335 			fBuffer->SetCursorHidden(false);
1336 			break;
1337 		case 47:
1338 			// Use Alternate Screen Buffer.
1339 			fBuffer->UseAlternateScreenBuffer(false);
1340 			break;
1341 		case 1000:
1342 			// Send Mouse X & Y on button press and release.
1343 			fBuffer->ReportNormalMouseEvent(true);
1344 			break;
1345 		case 1002:
1346 			// Send Mouse X and Y on button press and release, and on motion
1347 			// when the mouse enter a new cell
1348 			fBuffer->ReportButtonMouseEvent(true);
1349 			break;
1350 		case 1003:
1351 			// Use All Motion Mouse Tracking
1352 			fBuffer->ReportAnyMouseEvent(true);
1353 			break;
1354 		case 1034:
1355 			// TODO: Interprete "meta" key, sets eighth bit.
1356 			// Not supported yet.
1357 			break;
1358 		case 1036:
1359 			// TODO: Send ESC when Meta modifies a key
1360 			// Not supported yet.
1361 			break;
1362 		case 1039:
1363 			// TODO: Send ESC when Alt modifies a key
1364 			// Not supported yet.
1365 			break;
1366 		case 1049:
1367 			// Save cursor as in DECSC and use Alternate Screen Buffer, clearing
1368 			// it first.
1369 			fBuffer->SaveCursor();
1370 			fBuffer->UseAlternateScreenBuffer(true);
1371 			break;
1372 	}
1373 }
1374 
1375 
1376 void
1377 TermParse::_DecPrivateModeReset(int value)
1378 {
1379 	switch (value) {
1380 		case 1:
1381 			// Normal Cursor Keys (whatever that means).
1382 			// Not supported yet.
1383 			break;
1384 		case 3:
1385 			// 80 Column Mode.
1386 			// Not supported yet.
1387 			break;
1388 		case 4:
1389 			// Jump (Fast) Scroll.
1390 			// Not supported yet.
1391 			break;
1392 		case 5:
1393 			// Normal Video (Leaves Reverse Video, cf. there).
1394 			// Not supported yet.
1395 			break;
1396 		case 6:
1397 			// Reset Origin Mode.
1398 			fBuffer->SetOriginMode(false);
1399 			break;
1400 		case 9:
1401 			// Disable Mouse X and Y on button press.
1402 			fBuffer->ReportX10MouseEvent(false);
1403 			break;
1404 		case 12:
1405 			// Stop Blinking Cursor.
1406 			fBuffer->SetCursorBlinking(false);
1407 			break;
1408 		case 25:
1409 			// Hide Cursor
1410 			fBuffer->SetCursorHidden(true);
1411 			break;
1412 		case 47:
1413 			// Use Normal Screen Buffer.
1414 			fBuffer->UseNormalScreenBuffer();
1415 			break;
1416 		case 1000:
1417 			// Don't send Mouse X & Y on button press and release.
1418 			fBuffer->ReportNormalMouseEvent(false);
1419 			break;
1420 		case 1002:
1421 			// Don't send Mouse X and Y on button press and release, and on motion
1422 			// when the mouse enter a new cell
1423 			fBuffer->ReportButtonMouseEvent(false);
1424 			break;
1425 		case 1003:
1426 			// Disable All Motion Mouse Tracking.
1427 			fBuffer->ReportAnyMouseEvent(false);
1428 			break;
1429 		case 1034:
1430 			// Don't interprete "meta" key.
1431 			// Not supported yet.
1432 			break;
1433 		case 1036:
1434 			// TODO: Don't send ESC when Meta modifies a key
1435 			// Not supported yet.
1436 			break;
1437 		case 1039:
1438 			// TODO: Don't send ESC when Alt modifies a key
1439 			// Not supported yet.
1440 			break;
1441 		case 1049:
1442 			// Use Normal Screen Buffer and restore cursor as in DECRC.
1443 			fBuffer->UseNormalScreenBuffer();
1444 			fBuffer->RestoreCursor();
1445 			break;
1446 	}
1447 }
1448 
1449 
1450 void
1451 TermParse::_ProcessOperatingSystemControls(uchar* params)
1452 {
1453 	int mode = 0;
1454 	for (uchar c = *params; c != ';' && c != '\0'; c = *(++params)) {
1455 		mode *= 10;
1456 		mode += c - '0';
1457 	}
1458 
1459 	// eat the separator
1460 	if (*params == ';')
1461 		params++;
1462 
1463 	static uint8 indexes[kTermColorCount];
1464 	static rgb_color colors[kTermColorCount];
1465 
1466 	switch (mode) {
1467 		case 0: // icon name and window title
1468 		case 2: // window title
1469 			fBuffer->SetTitle((const char*)params);
1470 			break;
1471 		case 4: // set colors (0 - 255)
1472 		case 104: // reset colors (0 - 255)
1473 			{
1474 				bool reset = (mode / 100) == 1;
1475 
1476 				// colors can be in "idx1:name1;...;idxN:nameN;" sequence too!
1477 				uint32 count = 0;
1478 				char* p = strtok((char*)params, ";");
1479 				do {
1480 					indexes[count] = atoi(p);
1481 
1482 					if (!reset) {
1483 						p = strtok(NULL, ";");
1484 						if (p == NULL)
1485 							break;
1486 
1487 						if (gXColorsTable.LookUpColor(p, &colors[count]) == B_OK)
1488 							count++;
1489 					} else
1490 						count++;
1491 
1492 					p = strtok(NULL, ";");
1493 				} while (p != NULL && count < kTermColorCount);
1494 
1495 				if (count > 0) {
1496 					if (!reset)
1497 						fBuffer->SetColors(indexes, colors, count);
1498 					else
1499 						fBuffer->ResetColors(indexes, count);
1500 				}
1501 			}
1502 			break;
1503 		// set dynamic colors (10 - 19)
1504 		case 10: // text foreground
1505 		case 11: // text background
1506 			{
1507 				int32 offset = mode - 10;
1508 				int32 count = 0;
1509 				char* p = strtok((char*)params, ";");
1510 				do {
1511 					if (gXColorsTable.LookUpColor(p, &colors[count]) != B_OK) {
1512 						// dyna-colors are pos-sensitive - no chance to continue
1513 						break;
1514 					}
1515 
1516 					indexes[count] = 10 + offset + count;
1517 					count++;
1518 					p = strtok(NULL, ";");
1519 
1520 				} while (p != NULL && (offset + count) < 10);
1521 
1522 				if (count > 0) {
1523 					fBuffer->SetColors(indexes, colors, count, true);
1524 				}
1525 			}
1526 			break;
1527 		// reset dynamic colors (10 - 19)
1528 		case 110: // text foreground
1529 		case 111: // text background
1530 			{
1531 				indexes[0] = mode;
1532 				fBuffer->ResetColors(indexes, 1, true);
1533 			}
1534 			break;
1535 		default:
1536 		//	printf("%d -> %s\n", mode, params);
1537 			break;
1538 	}
1539 }
1540