xref: /haiku/src/apps/terminal/TermParse.cpp (revision e81a954787e50e56a7f06f72705b7859b6ab06d1)
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 = 0;
366 	int bottom = 0;
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 	for (int i = 0; i < NPARAM; i++)
376 		param[i] = DEFAULT;
377 
378 	int row = 0;
379 	int column = 0;
380 
381 	// default encoding system is UTF8
382 	int *groundtable = gUTF8GroundTable;
383 	int *parsestate = gUTF8GroundTable;
384 
385 	// handle alternative character sets G0 - G4
386 	const char** graphSets[4] = { NULL, NULL, NULL, NULL };
387 	int curGL = 0;
388 	int curGR = 0;
389 
390 	BAutolock locker(fBuffer);
391 
392 	while (!fQuitting) {
393 		try {
394 			uchar c = _NextParseChar();
395 
396 			//DumpState(groundtable, parsestate, c);
397 
398 			if (currentEncoding != fBuffer->Encoding()) {
399 				// Change coding, change parse table.
400 				groundtable = _GuessGroundTable(fBuffer->Encoding());
401 				parsestate = groundtable;
402 				currentEncoding = fBuffer->Encoding();
403 			}
404 
405 	//debug_printf("TermParse: char: '%c' (%d), parse state: %d\n", c, c, parsestate[c]);
406 			int32 srcLen = 0;
407 			int32 dstLen = sizeof(dstbuf);
408 			int32 dummyState = 0;
409 
410 			switch (parsestate[c]) {
411 				case CASE_PRINT:
412 				{
413 					int curGS = c < 128 ? curGL : curGR;
414 					const char** curGraphSet = graphSets[curGS];
415 					if (curGraphSet != NULL) {
416 						int offset = c - (c < 128 ? 0x20 : 0xA0);
417 						if (offset >= 0 && offset < 96
418 							&& curGraphSet[offset] != 0) {
419 							fBuffer->InsertChar(curGraphSet[offset]);
420 							break;
421 						}
422 					}
423 					fBuffer->InsertChar((char)c);
424 					break;
425 				}
426 				case CASE_PRINT_GR:
427 				{
428 					/* case iso8859 gr character, or euc */
429 					switch (currentEncoding) {
430 						case B_EUC_CONVERSION:
431 						case B_EUC_KR_CONVERSION:
432 						case B_JIS_CONVERSION:
433 						case B_BIG5_CONVERSION:
434 							cbuf[srcLen++] = c;
435 							c = _NextParseChar();
436 							cbuf[srcLen++] = c;
437 							break;
438 
439 						case B_GBK_CONVERSION:
440 							cbuf[srcLen++] = c;
441 							do {
442 								// GBK-compatible codepoints are 2-bytes long
443 								c = _NextParseChar();
444 								cbuf[srcLen++] = c;
445 
446 								// GB18030 extends GBK with 4-byte codepoints
447 								// using 2nd byte from range 0x30...0x39
448 								if (srcLen == 2 && (c < 0x30 || c > 0x39))
449 									break;
450 							} while (srcLen < 4);
451 							break;
452 
453 						default: // ISO-8859-1...10 and MacRoman
454 							cbuf[srcLen++] = c;
455 							break;
456 					}
457 
458 					if (srcLen > 0) {
459 						int encoding = currentEncoding == B_JIS_CONVERSION
460 							? B_EUC_CONVERSION : currentEncoding;
461 
462 						convert_to_utf8(encoding, cbuf, &srcLen,
463 								dstbuf, &dstLen, &dummyState, '?');
464 
465 						fBuffer->InsertChar(UTF8Char(dstbuf, dstLen));
466 					}
467 					break;
468 				}
469 
470 				case CASE_LF:
471 					fBuffer->InsertLF();
472 					break;
473 
474 				case CASE_CR:
475 					fBuffer->InsertCR();
476 					break;
477 
478 				case CASE_INDEX:
479 					fBuffer->InsertLF();
480 					parsestate = groundtable;
481 					break;
482 
483 				case CASE_NEXT_LINE:
484 					fBuffer->NextLine();
485 					parsestate = groundtable;
486 					break;
487 
488 				case CASE_SJIS_KANA:
489 					cbuf[srcLen++] = c;
490 					convert_to_utf8(currentEncoding, cbuf, &srcLen,
491 							dstbuf, &dstLen, &dummyState, '?');
492 					fBuffer->InsertChar(UTF8Char(dstbuf, dstLen));
493 					break;
494 
495 				case CASE_SJIS_INSTRING:
496 					cbuf[srcLen++] = c;
497 					c = _NextParseChar();
498 					cbuf[srcLen++] = c;
499 
500 					convert_to_utf8(currentEncoding, cbuf, &srcLen,
501 							dstbuf, &dstLen, &dummyState, '?');
502 					fBuffer->InsertChar(UTF8Char(dstbuf, dstLen));
503 					break;
504 
505 				case CASE_UTF8_2BYTE:
506 					cbuf[srcLen++] = c;
507 					c = _NextParseChar();
508 					if (groundtable[c] != CASE_UTF8_INSTRING)
509 						break;
510 					cbuf[srcLen++] = c;
511 
512 					fBuffer->InsertChar(UTF8Char(cbuf, srcLen));
513 					break;
514 
515 				case CASE_UTF8_3BYTE:
516 					cbuf[srcLen++] = c;
517 
518 					do {
519 						c = _NextParseChar();
520 						if (groundtable[c] != CASE_UTF8_INSTRING) {
521 							srcLen = 0;
522 							break;
523 						}
524 						cbuf[srcLen++] = c;
525 
526 					} while (srcLen != 3);
527 
528 					if (srcLen > 0)
529 						fBuffer->InsertChar(UTF8Char(cbuf, srcLen));
530 					break;
531 
532 				case CASE_SCS_STATE:
533 				{
534 					int set = -1;
535 					switch (c) {
536 						case '(':
537 							set = 0;
538 							break;
539 						case ')':
540 						case '-':
541 							set = 1;
542 							break;
543 						case '*':
544 						case '.':
545 							set = 2;
546 							break;
547 						case '+':
548 						case '/':
549 							set = 3;
550 							break;
551 						default:
552 							break;
553 					}
554 
555 					if (set > -1) {
556 						char page = _NextParseChar();
557 						switch (page) {
558 							case '0':
559 								graphSets[set] = gLineDrawGraphSet;
560 								break;
561 							default:
562 								graphSets[set] = NULL;
563 								break;
564 						}
565 					}
566 
567 					parsestate = groundtable;
568 					break;
569 				}
570 
571 				case CASE_GROUND_STATE:
572 					/* exit ignore mode */
573 					parsestate = groundtable;
574 					break;
575 
576 				case CASE_BELL:
577 					beep();
578 					break;
579 
580 				case CASE_BS:
581 					fBuffer->MoveCursorLeft(1);
582 					break;
583 
584 				case CASE_TAB:
585 					fBuffer->InsertTab();
586 					break;
587 
588 				case CASE_ESC:
589 					/* escape */
590 					parsestate = gEscTable;
591 					break;
592 
593 				case CASE_IGNORE_STATE:
594 					/* Ies: ignore anything else */
595 					parsestate = gIgnoreTable;
596 					break;
597 
598 				case CASE_IGNORE_ESC:
599 					/* Ign: escape */
600 					parsestate = gIesTable;
601 					break;
602 
603 				case CASE_IGNORE:
604 					/* Ignore character */
605 					break;
606 
607 				case CASE_LS1:
608 					/* select G1 into GL */
609 					curGL = 1;
610 					parsestate = groundtable;
611 					break;
612 
613 				case CASE_LS0:
614 					/* select G0 into GL */
615 					curGL = 0;
616 					parsestate = groundtable;
617 					break;
618 
619 				case CASE_SCR_STATE:	// ESC #
620 					/* enter scr state */
621 					parsestate = gScrTable;
622 					break;
623 
624 				case CASE_ESC_IGNORE:
625 					/* unknown escape sequence */
626 					parsestate = gEscIgnoreTable;
627 					break;
628 
629 				case CASE_ESC_DIGIT:	// ESC [ number
630 					/* digit in csi or dec mode */
631 					if ((row = param[nparam - 1]) == DEFAULT)
632 						row = 0;
633 					param[nparam - 1] = 10 * row + (c - '0');
634 					break;
635 
636 				case CASE_ESC_SEMI:		// ESC ;
637 					/* semicolon in csi or dec mode */
638 					if (nparam < NPARAM)
639 						param[nparam++] = DEFAULT;
640 					break;
641 
642 				case CASE_CSI_SP: // ESC [N q
643 					// part of change cursor style DECSCUSR
644 					if (nparam < NPARAM)
645 						param[nparam++] = ' ';
646 					break;
647 
648 				case CASE_DEC_STATE:
649 					/* enter dec mode */
650 					parsestate = gDecTable;
651 					break;
652 
653 				case CASE_ICH:		// ESC [@ insert charactor
654 					/* ICH */
655 					if ((row = param[0]) < 1)
656 						row = 1;
657 					fBuffer->InsertSpace(row);
658 					parsestate = groundtable;
659 					break;
660 
661 				case CASE_CUU:		// ESC [A cursor up, up arrow key.
662 					/* CUU */
663 					if ((row = param[0]) < 1)
664 						row = 1;
665 					fBuffer->MoveCursorUp(row);
666 					parsestate = groundtable;
667 					break;
668 
669 				case CASE_CUD:		// ESC [B cursor down, down arrow key.
670 					/* CUD */
671 					if ((row = param[0]) < 1)
672 						row = 1;
673 					fBuffer->MoveCursorDown(row);
674 					parsestate = groundtable;
675 					break;
676 
677 				case CASE_CUF:		// ESC [C cursor forword
678 					/* CUF */
679 					if ((row = param[0]) < 1)
680 						row = 1;
681 					fBuffer->MoveCursorRight(row);
682 					parsestate = groundtable;
683 					break;
684 
685 				case CASE_CUB:		// ESC [D cursor backword
686 					/* CUB */
687 					if ((row = param[0]) < 1)
688 						row = 1;
689 					fBuffer->MoveCursorLeft(row);
690 					parsestate = groundtable;
691 					break;
692 
693 				case CASE_CUP:		// ESC [...H move cursor
694 					/* CUP | HVP */
695 					if ((row = param[0]) < 1)
696 						row = 1;
697 					if (nparam < 2 || (column = param[1]) < 1)
698 						column = 1;
699 
700 					fBuffer->SetCursor(column - 1, row - 1 );
701 					parsestate = groundtable;
702 					break;
703 
704 				case CASE_ED:		// ESC [ ...J clear screen
705 					/* ED */
706 					switch (param[0]) {
707 						case DEFAULT:
708 						case 0:
709 							fBuffer->EraseBelow();
710 							break;
711 
712 						case 1:
713 							fBuffer->EraseAbove();
714 							break;
715 
716 						case 2:
717 							fBuffer->EraseAll();
718 							break;
719 					}
720 					parsestate = groundtable;
721 					break;
722 
723 				case CASE_EL:		// ESC [ ...K delete line
724 					/* EL */
725 					switch (param[0]) {
726 						case DEFAULT:
727 						case 0:
728 							fBuffer->DeleteColumns();
729 							break;
730 
731 						case 1:
732 							fBuffer->EraseCharsFrom(0, fBuffer->Cursor().x + 1);
733 							break;
734 
735 						case 2:
736 							fBuffer->DeleteColumnsFrom(0);
737 							break;
738 					}
739 					parsestate = groundtable;
740 					break;
741 
742 				case CASE_IL:
743 					/* IL */
744 					if ((row = param[0]) < 1)
745 						row = 1;
746 					fBuffer->InsertLines(row);
747 					parsestate = groundtable;
748 					break;
749 
750 				case CASE_DL:
751 					/* DL */
752 					if ((row = param[0]) < 1)
753 						row = 1;
754 					fBuffer->DeleteLines(row);
755 					parsestate = groundtable;
756 					break;
757 
758 				case CASE_DCH:
759 					/* DCH */
760 					if ((row = param[0]) < 1)
761 						row = 1;
762 					fBuffer->DeleteChars(row);
763 					parsestate = groundtable;
764 					break;
765 
766 				case CASE_SET:
767 					/* SET */
768 					if (param[0] == 4)
769 						fBuffer->SetInsertMode(MODE_INSERT);
770 					parsestate = groundtable;
771 					break;
772 
773 				case CASE_RST:
774 					/* RST */
775 					if (param[0] == 4)
776 						fBuffer->SetInsertMode(MODE_OVER);
777 					parsestate = groundtable;
778 					break;
779 
780 				case CASE_SGR:
781 				{
782 					/* SGR */
783 					uint32 attributes = fBuffer->GetAttributes();
784 					for (row = 0; row < nparam; ++row) {
785 						switch (param[row]) {
786 							case DEFAULT:
787 							case 0: /* Reset attribute */
788 								attributes = 0;
789 								break;
790 
791 							case 1: /* Bold     */
792 							case 5:
793 								attributes |= BOLD;
794 								break;
795 
796 							case 4:	/* Underline	*/
797 								attributes |= UNDERLINE;
798 								break;
799 
800 							case 7:	/* Inverse	*/
801 								attributes |= INVERSE;
802 								break;
803 
804 							case 22:	/* Not Bold	*/
805 								attributes &= ~BOLD;
806 								break;
807 
808 							case 24:	/* Not Underline	*/
809 								attributes &= ~UNDERLINE;
810 								break;
811 
812 							case 27:	/* Not Inverse	*/
813 								attributes &= ~INVERSE;
814 								break;
815 
816 							case 90:
817 							case 91:
818 							case 92:
819 							case 93:
820 							case 94:
821 							case 95:
822 							case 96:
823 							case 97:
824 								param[row] -= 60;
825 							case 30:
826 							case 31:
827 							case 32:
828 							case 33:
829 							case 34:
830 							case 35:
831 							case 36:
832 							case 37:
833 								attributes &= ~FORECOLOR;
834 								attributes |= FORECOLORED(param[row] - 30);
835 								attributes |= FORESET;
836 								break;
837 
838 							case 38:
839 							{
840 								int color = -1;
841 								if (nparam == 3 && param[1] == 5)
842 									color = param[2];
843 								else if (nparam == 5 && param[1] == 2)
844 									color = fBuffer->GuessPaletteColor(
845 										param[2], param[3], param[4]);
846 
847 								if (color >= 0) {
848 									attributes &= ~FORECOLOR;
849 									attributes |= FORECOLORED(color);
850 									attributes |= FORESET;
851 								}
852 
853 								row = nparam; // force exit of the parsing
854 								break;
855 							}
856 
857 							case 39:
858 								attributes &= ~FORESET;
859 								break;
860 
861 							case 100:
862 							case 101:
863 							case 102:
864 							case 103:
865 							case 104:
866 							case 105:
867 							case 106:
868 							case 107:
869 								param[row] -= 60;
870 							case 40:
871 							case 41:
872 							case 42:
873 							case 43:
874 							case 44:
875 							case 45:
876 							case 46:
877 							case 47:
878 								attributes &= ~BACKCOLOR;
879 								attributes |= BACKCOLORED(param[row] - 40);
880 								attributes |= BACKSET;
881 								break;
882 
883 							case 48:
884 							{
885 								int color = -1;
886 								if (nparam == 3 && param[1] == 5)
887 									color = param[2];
888 								else if (nparam == 5 && param[1] == 2)
889 									color = fBuffer->GuessPaletteColor(
890 										param[2], param[3], param[4]);
891 
892 								if (color >= 0) {
893 									attributes &= ~BACKCOLOR;
894 									attributes |= BACKCOLORED(color);
895 									attributes |= BACKSET;
896 								}
897 
898 								row = nparam; // force exit of the parsing
899 								break;
900 							}
901 
902 							case 49:
903 								attributes &= ~BACKSET;
904 								break;
905 						}
906 					}
907 					fBuffer->SetAttributes(attributes);
908 					parsestate = groundtable;
909 					break;
910 				}
911 
912 				case CASE_CPR:
913 				// Q & D hack by Y.Hayakawa (hida@sawada.riec.tohoku.ac.jp)
914 				// 21-JUL-99
915 				_DeviceStatusReport(param[0]);
916 				parsestate = groundtable;
917 				break;
918 
919 				case CASE_DA1:
920 				// DA - report device attributes
921 				if (param[0] < 1) {
922 					// claim to be a VT102
923 					write(fFd, "\033[?6c", 5);
924 				}
925 				parsestate = groundtable;
926 				break;
927 
928 				case CASE_DECSTBM:
929 				/* DECSTBM - set scrolling region */
930 
931 				if ((top = param[0]) < 1)
932 					top = 1;
933 
934 				if (nparam < 2)
935 					bottom = fBuffer->Height();
936 				else
937 					bottom = param[1];
938 
939 				top--;
940 					bottom--;
941 
942 					if (bottom > top)
943 						fBuffer->SetScrollRegion(top, bottom);
944 
945 					parsestate = groundtable;
946 					break;
947 
948 				case CASE_DECSCUSR_ETC:
949 				// DECSCUSR - set cursor style VT520
950 					if (nparam == 2 && param[1] == ' ') {
951 						bool blinking = (param[0] & 0x01) != 0;
952 						int style = -1;
953 						switch (param[0]) {
954 							case 0:
955 								blinking = true;
956 							case 1:
957 							case 2:
958 								style = BLOCK_CURSOR;
959 								break;
960 							case 3:
961 							case 4:
962 								style = UNDERLINE_CURSOR;
963 								break;
964 							case 5:
965 							case 6:
966 								style = IBEAM_CURSOR;
967 								break;
968 						}
969 
970 						if (style != -1)
971 							fBuffer->SetCursorStyle(style, blinking);
972 					}
973 					parsestate = groundtable;
974 					break;
975 
976 				case CASE_DECREQTPARM:
977 					// DEXREQTPARM - request terminal parameters
978 					_DecReqTermParms(param[0]);
979 					parsestate = groundtable;
980 					break;
981 
982 				case CASE_DECSET:
983 					/* DECSET */
984 					for (int i = 0; i < nparam; i++)
985 						_DecPrivateModeSet(param[i]);
986 					parsestate = groundtable;
987 					break;
988 
989 				case CASE_DECRST:
990 					/* DECRST */
991 					for (int i = 0; i < nparam; i++)
992 						_DecPrivateModeReset(param[i]);
993 					parsestate = groundtable;
994 					break;
995 
996 				case CASE_DECALN:
997 					/* DECALN */
998 					fBuffer->FillScreen(UTF8Char('E'), 0);
999 					parsestate = groundtable;
1000 					break;
1001 
1002 					//	case CASE_GSETS:
1003 					//		screen->gsets[scstype] = GSET(c) | cs96;
1004 					//		parsestate = groundtable;
1005 					//		break;
1006 
1007 				case CASE_DECSC:
1008 					/* DECSC */
1009 					fBuffer->SaveCursor();
1010 					parsestate = groundtable;
1011 					break;
1012 
1013 				case CASE_DECRC:
1014 					/* DECRC */
1015 					fBuffer->RestoreCursor();
1016 					parsestate = groundtable;
1017 					break;
1018 
1019 				case CASE_HTS:
1020 					/* HTS */
1021 					fBuffer->SetTabStop(fBuffer->Cursor().x);
1022 					parsestate = groundtable;
1023 					break;
1024 
1025 				case CASE_TBC:
1026 					/* TBC */
1027 					if (param[0] < 1)
1028 						fBuffer->ClearTabStop(fBuffer->Cursor().x);
1029 					else if (param[0] == 3)
1030 						fBuffer->ClearAllTabStops();
1031 					parsestate = groundtable;
1032 					break;
1033 
1034 				case CASE_RI:
1035 					/* RI */
1036 					fBuffer->InsertRI();
1037 					parsestate = groundtable;
1038 					break;
1039 
1040 				case CASE_SS2:
1041 					/* SS2 */
1042 					parsestate = groundtable;
1043 					break;
1044 
1045 				case CASE_SS3:
1046 					/* SS3 */
1047 					parsestate = groundtable;
1048 					break;
1049 
1050 				case CASE_CSI_STATE:
1051 					/* enter csi state */
1052 					nparam = 1;
1053 					param[0] = DEFAULT;
1054 					parsestate = gCsiTable;
1055 					break;
1056 
1057 				case CASE_OSC:
1058 					{
1059 						/* Operating System Command: ESC ] */
1060 						uchar params[512];
1061 						// fill the buffer until BEL, ST or something else.
1062 						bool isParsed = false;
1063 						int32 skipCount = 0; // take care about UTF-8 characters
1064 						for (uint i = 0; !isParsed && i < sizeof(params); i++) {
1065 							params[i] = _NextParseChar();
1066 
1067 							if (skipCount > 0) {
1068 								skipCount--;
1069 								continue;
1070 							}
1071 
1072 							skipCount = UTF8Char::ByteCount(params[i]) - 1;
1073 							if (skipCount > 0)
1074 								continue;
1075 
1076 							switch (params[i]) {
1077 								// BEL
1078 								case 0x07:
1079 									isParsed = true;
1080 									break;
1081 								// 8-bit ST
1082 								case 0x9c:
1083 									isParsed = true;
1084 									break;
1085 								// 7-bit ST is "ESC \"
1086 								case '\\':
1087 								// hm... Was \x1b replaced by 0 during parsing?
1088 									if (i > 0 && params[i - 1] == 0) {
1089 										isParsed = true;
1090 										break;
1091 									}
1092 								default:
1093 									if (!isprint(params[i] & 0x7f))
1094 										break;
1095 									continue;
1096 							}
1097 							params[i] = '\0';
1098 						}
1099 
1100 						// watchdog for the 'end of buffer' case
1101 						params[sizeof(params) - 1] = '\0';
1102 
1103 						if (isParsed)
1104 							_ProcessOperatingSystemControls(params);
1105 
1106 						parsestate = groundtable;
1107 						break;
1108 					}
1109 
1110 				case CASE_RIS:		// ESC c ... Reset terminal.
1111 					break;
1112 
1113 				case CASE_LS2:
1114 					/* select G2 into GL */
1115 					curGL = 2;
1116 					parsestate = groundtable;
1117 					break;
1118 
1119 				case CASE_LS3:
1120 					/* select G3 into GL */
1121 					curGL = 3;
1122 					parsestate = groundtable;
1123 					break;
1124 
1125 				case CASE_LS3R:
1126 					/* select G3 into GR */
1127 					curGR = 3;
1128 					parsestate = groundtable;
1129 					break;
1130 
1131 				case CASE_LS2R:
1132 					/* select G2 into GR */
1133 					curGR = 2;
1134 					parsestate = groundtable;
1135 					break;
1136 
1137 				case CASE_LS1R:
1138 					/* select G1 into GR */
1139 					curGR = 1;
1140 					parsestate = groundtable;
1141 					break;
1142 
1143 				case CASE_VPA:		// ESC [...d move cursor absolute vertical
1144 					/* VPA (CV) */
1145 					if ((row = param[0]) < 1)
1146 						row = 1;
1147 
1148 					// note beterm wants it 1-based unlike usual terminals
1149 					fBuffer->SetCursorY(row - 1);
1150 					parsestate = groundtable;
1151 					break;
1152 
1153 				case CASE_HPA:		// ESC [...G move cursor absolute horizontal
1154 					/* HPA (CH) */
1155 					if ((column = param[0]) < 1)
1156 						column = 1;
1157 
1158 					// note beterm wants it 1-based unlike usual terminals
1159 					fBuffer->SetCursorX(column - 1);
1160 					parsestate = groundtable;
1161 					break;
1162 
1163 				case CASE_SU:	// scroll screen up
1164 					if ((row = param[0]) < 1)
1165 						row = 1;
1166 					fBuffer->ScrollBy(row);
1167 					parsestate = groundtable;
1168 					break;
1169 
1170 				case CASE_SD:	// scroll screen down
1171 					if ((row = param[0]) < 1)
1172 						row = 1;
1173 					fBuffer->ScrollBy(-row);
1174 					parsestate = groundtable;
1175 					break;
1176 
1177 
1178 				case CASE_ECH:	// erase characters
1179 					if ((column = param[0]) < 1)
1180 						column = 1;
1181 					fBuffer->EraseChars(column);
1182 					parsestate = groundtable;
1183 					break;
1184 
1185 				case CASE_CBT:	// cursor back tab
1186 					if ((column = param[0]) < 1)
1187 						column = 1;
1188 					fBuffer->InsertCursorBackTab(column);
1189 					parsestate = groundtable;
1190 					break;
1191 
1192 				case CASE_CFT:	// cursor forward tab
1193 					if ((column= param[0]) < 1)
1194 						column = 1;
1195 					for (int32 i = 0; i < column; ++i)
1196 						fBuffer->InsertTab();
1197 					parsestate = groundtable;
1198 					break;
1199 
1200 				case CASE_CNL:	// cursor next line
1201 					if ((row= param[0]) < 1)
1202 						row = 1;
1203 					fBuffer->SetCursorX(0);
1204 					fBuffer->MoveCursorDown(row);
1205 					parsestate = groundtable;
1206 					break;
1207 
1208 				case CASE_CPL:	// cursor previous line
1209 					if ((row= param[0]) < 1)
1210 						row = 1;
1211 					fBuffer->SetCursorX(0);
1212 					fBuffer->MoveCursorUp(row);
1213 					parsestate = groundtable;
1214 					break;
1215 				default:
1216 					break;
1217 			}
1218 		} catch (...) {
1219 			break;
1220 		}
1221 	}
1222 
1223 	return B_OK;
1224 }
1225 
1226 
1227 /*static*/ int32
1228 TermParse::_ptyreader_thread(void *data)
1229 {
1230 	return reinterpret_cast<TermParse *>(data)->PtyReader();
1231 }
1232 
1233 
1234 /*static*/ int32
1235 TermParse::_escparse_thread(void *data)
1236 {
1237 	return reinterpret_cast<TermParse *>(data)->EscParse();
1238 }
1239 
1240 
1241 status_t
1242 TermParse::_ReadParserBuffer()
1243 {
1244 	// We have to unlock the terminal buffer while waiting for data from the
1245 	// PTY. We don't have to unlock when we don't need to wait, but we do it
1246 	// anyway, so that TermView won't be starved when trying to synchronize.
1247 	fBuffer->Unlock();
1248 
1249 	// wait for new input from pty
1250 	if (atomic_get(&fReadBufferSize) == 0) {
1251 		status_t status = B_OK;
1252 		while (atomic_get(&fReadBufferSize) == 0 && status == B_OK) {
1253 			do {
1254 				status = acquire_sem(fReaderSem);
1255 			} while (status == B_INTERRUPTED);
1256 
1257 			// eat any sems that were released unconditionally
1258 			int32 semCount;
1259 			if (get_sem_count(fReaderSem, &semCount) == B_OK && semCount > 0)
1260 				acquire_sem_etc(fReaderSem, semCount, B_RELATIVE_TIMEOUT, 0);
1261 		}
1262 
1263 		if (status < B_OK) {
1264 			fBuffer->Lock();
1265 			return status;
1266 		}
1267 	}
1268 
1269 	int32 toRead = atomic_get(&fReadBufferSize);
1270 	if (toRead > ESC_PARSER_BUFFER_SIZE)
1271 		toRead = ESC_PARSER_BUFFER_SIZE;
1272 
1273 	for (int32 i = 0; i < toRead; i++) {
1274 		// TODO: This could be optimized using memcpy instead and
1275 		// calculating space left as in the PtyReader().
1276 		fParserBuffer[i] = fReadBuffer[fBufferPosition];
1277 		fBufferPosition = (fBufferPosition + 1) % READ_BUF_SIZE;
1278 	}
1279 
1280 	int32 bufferSize = atomic_add(&fReadBufferSize, -toRead);
1281 
1282 	// If the pty reader thread waits and we have made enough space in the
1283 	// buffer now, let it run again.
1284 	if (bufferSize > READ_BUF_SIZE - MIN_PTY_BUFFER_SPACE
1285 			&& bufferSize - toRead <= READ_BUF_SIZE - MIN_PTY_BUFFER_SPACE) {
1286 		release_sem(fReaderLocker);
1287 	}
1288 
1289 	fParserBufferSize = toRead;
1290 	fParserBufferOffset = 0;
1291 
1292 	fBuffer->Lock();
1293 	return B_OK;
1294 }
1295 
1296 
1297 void
1298 TermParse::_DeviceStatusReport(int n)
1299 {
1300 	char sbuf[16] ;
1301 	int len;
1302 
1303 	switch (n) {
1304 		case 5:
1305 			{
1306 				// Device status report requested
1307 				// reply with "no malfunction detected"
1308 				const char* toWrite = "\033[0n";
1309 				write(fFd, toWrite, strlen(toWrite));
1310 				break ;
1311 			}
1312 		case 6:
1313 			// Cursor position report requested
1314 			len = sprintf(sbuf, "\033[%" B_PRId32 ";%" B_PRId32 "R",
1315 					fBuffer->Cursor().y + 1,
1316 					fBuffer->Cursor().x + 1);
1317 			write(fFd, sbuf, len);
1318 			break ;
1319 		default:
1320 			return;
1321 	}
1322 }
1323 
1324 
1325 void
1326 TermParse::_DecReqTermParms(int value)
1327 {
1328 	// Terminal parameters report:
1329 	//   type (2 or 3);
1330 	//   no parity (1);
1331 	//   8 bits per character (1);
1332 	//   transmit speed 38400bps (128);
1333 	//   receive speed 38400bps (128);
1334 	//   bit rate multiplier 16 (1);
1335 	//   no flags (0)
1336 	char parms[] = "\033[?;1;1;128;128;1;0x";
1337 
1338 	if (value < 1)
1339 		parms[2] = '2';
1340 	else if (value == 1)
1341 		parms[2] = '3';
1342 	else
1343 		return;
1344 
1345 	write(fFd, parms, strlen(parms));
1346 }
1347 
1348 
1349 void
1350 TermParse::_DecPrivateModeSet(int value)
1351 {
1352 	switch (value) {
1353 		case 1:
1354 			// Application Cursor Keys (whatever that means).
1355 			// Not supported yet.
1356 			break;
1357 		case 5:
1358 			// Reverse Video (inverses colors for the complete screen
1359 			// -- when followed by normal video, that's shortly flashes the
1360 			// screen).
1361 			// Not supported yet.
1362 			break;
1363 		case 6:
1364 			// Set Origin Mode.
1365 			fBuffer->SetOriginMode(true);
1366 			break;
1367 		case 9:
1368 			// Set Mouse X and Y on button press.
1369 			fBuffer->ReportX10MouseEvent(true);
1370 			break;
1371 		case 12:
1372 			// Start Blinking Cursor.
1373 			fBuffer->SetCursorBlinking(true);
1374 			break;
1375 		case 25:
1376 			// Show Cursor.
1377 			fBuffer->SetCursorHidden(false);
1378 			break;
1379 		case 47:
1380 			// Use Alternate Screen Buffer.
1381 			fBuffer->UseAlternateScreenBuffer(false);
1382 			break;
1383 		case 1000:
1384 			// Send Mouse X & Y on button press and release.
1385 			fBuffer->ReportNormalMouseEvent(true);
1386 			break;
1387 		case 1002:
1388 			// Send Mouse X and Y on button press and release, and on motion
1389 			// when the mouse enter a new cell
1390 			fBuffer->ReportButtonMouseEvent(true);
1391 			break;
1392 		case 1003:
1393 			// Use All Motion Mouse Tracking
1394 			fBuffer->ReportAnyMouseEvent(true);
1395 			break;
1396 		case 1034:
1397 			// TODO: Interprete "meta" key, sets eighth bit.
1398 			// Not supported yet.
1399 			break;
1400 		case 1036:
1401 			// TODO: Send ESC when Meta modifies a key
1402 			// Not supported yet.
1403 			break;
1404 		case 1039:
1405 			// TODO: Send ESC when Alt modifies a key
1406 			// Not supported yet.
1407 			break;
1408 		case 1049:
1409 			// Save cursor as in DECSC and use Alternate Screen Buffer, clearing
1410 			// it first.
1411 			fBuffer->SaveCursor();
1412 			fBuffer->UseAlternateScreenBuffer(true);
1413 			break;
1414 	}
1415 }
1416 
1417 
1418 void
1419 TermParse::_DecPrivateModeReset(int value)
1420 {
1421 	switch (value) {
1422 		case 1:
1423 			// Normal Cursor Keys (whatever that means).
1424 			// Not supported yet.
1425 			break;
1426 		case 3:
1427 			// 80 Column Mode.
1428 			// Not supported yet.
1429 			break;
1430 		case 4:
1431 			// Jump (Fast) Scroll.
1432 			// Not supported yet.
1433 			break;
1434 		case 5:
1435 			// Normal Video (Leaves Reverse Video, cf. there).
1436 			// Not supported yet.
1437 			break;
1438 		case 6:
1439 			// Reset Origin Mode.
1440 			fBuffer->SetOriginMode(false);
1441 			break;
1442 		case 9:
1443 			// Disable Mouse X and Y on button press.
1444 			fBuffer->ReportX10MouseEvent(false);
1445 			break;
1446 		case 12:
1447 			// Stop Blinking Cursor.
1448 			fBuffer->SetCursorBlinking(false);
1449 			break;
1450 		case 25:
1451 			// Hide Cursor
1452 			fBuffer->SetCursorHidden(true);
1453 			break;
1454 		case 47:
1455 			// Use Normal Screen Buffer.
1456 			fBuffer->UseNormalScreenBuffer();
1457 			break;
1458 		case 1000:
1459 			// Don't send Mouse X & Y on button press and release.
1460 			fBuffer->ReportNormalMouseEvent(false);
1461 			break;
1462 		case 1002:
1463 			// Don't send Mouse X and Y on button press and release, and on motion
1464 			// when the mouse enter a new cell
1465 			fBuffer->ReportButtonMouseEvent(false);
1466 			break;
1467 		case 1003:
1468 			// Disable All Motion Mouse Tracking.
1469 			fBuffer->ReportAnyMouseEvent(false);
1470 			break;
1471 		case 1034:
1472 			// Don't interprete "meta" key.
1473 			// Not supported yet.
1474 			break;
1475 		case 1036:
1476 			// TODO: Don't send ESC when Meta modifies a key
1477 			// Not supported yet.
1478 			break;
1479 		case 1039:
1480 			// TODO: Don't send ESC when Alt modifies a key
1481 			// Not supported yet.
1482 			break;
1483 		case 1049:
1484 			// Use Normal Screen Buffer and restore cursor as in DECRC.
1485 			fBuffer->UseNormalScreenBuffer();
1486 			fBuffer->RestoreCursor();
1487 			break;
1488 	}
1489 }
1490 
1491 
1492 void
1493 TermParse::_ProcessOperatingSystemControls(uchar* params)
1494 {
1495 	int mode = 0;
1496 	for (uchar c = *params; c != ';' && c != '\0'; c = *(++params)) {
1497 		mode *= 10;
1498 		mode += c - '0';
1499 	}
1500 
1501 	// eat the separator
1502 	if (*params == ';')
1503 		params++;
1504 
1505 	static uint8 indexes[kTermColorCount];
1506 	static rgb_color colors[kTermColorCount];
1507 
1508 	switch (mode) {
1509 		case 0: // icon name and window title
1510 		case 2: // window title
1511 			fBuffer->SetTitle((const char*)params);
1512 			break;
1513 		case 4: // set colors (0 - 255)
1514 		case 104: // reset colors (0 - 255)
1515 			{
1516 				bool reset = (mode / 100) == 1;
1517 
1518 				// colors can be in "idx1:name1;...;idxN:nameN;" sequence too!
1519 				uint32 count = 0;
1520 				char* p = strtok((char*)params, ";");
1521 				while (p != NULL && count < kTermColorCount) {
1522 					indexes[count] = atoi(p);
1523 
1524 					if (!reset) {
1525 						p = strtok(NULL, ";");
1526 						if (p == NULL)
1527 							break;
1528 
1529 						if (gXColorsTable.LookUpColor(p, &colors[count]) == B_OK)
1530 							count++;
1531 					} else
1532 						count++;
1533 
1534 					p = strtok(NULL, ";");
1535 				};
1536 
1537 				if (count > 0) {
1538 					if (!reset)
1539 						fBuffer->SetColors(indexes, colors, count);
1540 					else
1541 						fBuffer->ResetColors(indexes, count);
1542 				}
1543 			}
1544 			break;
1545 		// set dynamic colors (10 - 19)
1546 		case 10: // text foreground
1547 		case 11: // text background
1548 			{
1549 				int32 offset = mode - 10;
1550 				int32 count = 0;
1551 				char* p = strtok((char*)params, ";");
1552 				do {
1553 					if (gXColorsTable.LookUpColor(p, &colors[count]) != B_OK) {
1554 						// dyna-colors are pos-sensitive - no chance to continue
1555 						break;
1556 					}
1557 
1558 					indexes[count] = 10 + offset + count;
1559 					count++;
1560 					p = strtok(NULL, ";");
1561 
1562 				} while (p != NULL && (offset + count) < 10);
1563 
1564 				if (count > 0) {
1565 					fBuffer->SetColors(indexes, colors, count, true);
1566 				}
1567 			}
1568 			break;
1569 		// reset dynamic colors (10 - 19)
1570 		case 110: // text foreground
1571 		case 111: // text background
1572 			{
1573 				indexes[0] = mode;
1574 				fBuffer->ResetColors(indexes, 1, true);
1575 			}
1576 			break;
1577 		default:
1578 		//	printf("%d -> %s\n", mode, params);
1579 			break;
1580 	}
1581 }
1582