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