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