xref: /haiku/src/apps/terminal/TermParse.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
1 /*
2  * Copyright 2001-2023, Haiku, Inc. All rights reserved.
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 								if ((row + 1) < nparam) {
799 									row++;
800 									switch (param[row]) {
801 										case 0:
802 											attributes.UnsetUnder();
803 											break;
804 										case 1:
805 											attributes.SetUnder(SINGLE_UNDERLINE);
806 											break;
807 										case 2:
808 											attributes.SetUnder(DOUBLE_UNDERLINE);
809 											break;
810 										case 3:
811 											attributes.SetUnder(CURLY_UNDERLINE);
812 											break;
813 										case 4:
814 											attributes.SetUnder(DOTTED_UNDERLINE);
815 											break;
816 										case 5:
817 											attributes.SetUnder(DASHED_UNDERLINE);
818 											break;
819 										default:
820 											row = nparam; // force exit of the parsing
821 											break;
822 									}
823 								} else
824 									attributes.SetUnder(SINGLE_UNDERLINE);
825 								break;
826 
827 							case 7:	/* Inverse	*/
828 								attributes |= INVERSE;
829 								break;
830 
831 							case 21:	/* Double Underline	*/
832 								attributes.SetUnder(DOUBLE_UNDERLINE);
833 								break;
834 
835 							case 22:	/* Not Bold	*/
836 								attributes &= ~BOLD;
837 								break;
838 
839 							case 24:	/* Not Underline	*/
840 								attributes.UnsetUnder();
841 								break;
842 
843 							case 27:	/* Not Inverse	*/
844 								attributes &= ~INVERSE;
845 								break;
846 
847 							case 90:
848 							case 91:
849 							case 92:
850 							case 93:
851 							case 94:
852 							case 95:
853 							case 96:
854 							case 97:
855 								param[row] -= 60;
856 							case 30:
857 							case 31:
858 							case 32:
859 							case 33:
860 							case 34:
861 							case 35:
862 							case 36:
863 							case 37:
864 								attributes.SetIndexedForeground(param[row] - 30);
865 								break;
866 
867 							case 38:
868 							{
869 								if (nparam >= 3 && param[row+1] == 5) {
870 									attributes.SetIndexedForeground(param[row+2]);
871 									row += 2;
872 								} else if (nparam >= 5 && param[row+1] == 2) {
873 									attributes.SetDirectForeground(param[row+2], param[row+3], param[row+4]);
874 									row += 4;
875 								} else {
876 									row = nparam; // force exit of the parsing
877 								}
878 
879 								break;
880 							}
881 
882 							case 39:
883 								attributes.UnsetForeground();
884 								break;
885 
886 							case 100:
887 							case 101:
888 							case 102:
889 							case 103:
890 							case 104:
891 							case 105:
892 							case 106:
893 							case 107:
894 								param[row] -= 60;
895 							case 40:
896 							case 41:
897 							case 42:
898 							case 43:
899 							case 44:
900 							case 45:
901 							case 46:
902 							case 47:
903 								attributes.SetIndexedBackground(param[row] - 40);
904 								break;
905 
906 							case 48:
907 							{
908 								if (nparam >= 3 && param[row+1] == 5) {
909 									attributes.SetIndexedBackground(param[row+2]);
910 									row += 2;
911 								} else if (nparam >= 5 && param[row+1] == 2) {
912 									attributes.SetDirectBackground(param[row+2], param[row+3], param[row+4]);
913 									row += 4;
914 								} else {
915 									row = nparam; // force exit of the parsing
916 								}
917 
918 								break;
919 							}
920 
921 							case 49:
922 								attributes.UnsetBackground();
923 								break;
924 
925 							case 58:
926 							{
927 								if (nparam >= 3 && param[row+1] == 5) {
928 									attributes.SetIndexedUnderline(param[row+2]);
929 									row += 2;
930 								} else if (nparam >= 5 && param[row+1] == 2) {
931 									attributes.SetDirectUnderline(param[row+2], param[row+3], param[row+4]);
932 									row += 4;
933 								} else {
934 									row = nparam; // force exit of the parsing
935 								}
936 
937 								break;
938 							}
939 
940 							case 59:
941 								attributes.UnsetUnderline();
942 								break;
943 						}
944 					}
945 					fBuffer->SetAttributes(attributes);
946 					parsestate = groundtable;
947 					break;
948 				}
949 
950 				case CASE_CPR:
951 				// Q & D hack by Y.Hayakawa (hida@sawada.riec.tohoku.ac.jp)
952 				// 21-JUL-99
953 				_DeviceStatusReport(param[0]);
954 				parsestate = groundtable;
955 				break;
956 
957 				case CASE_DA1:
958 				// DA - report device attributes
959 				if (param[0] < 1) {
960 					// claim to be a VT102
961 					write(fFd, "\033[?6c", 5);
962 				}
963 				parsestate = groundtable;
964 				break;
965 
966 				case CASE_DECSTBM:
967 				/* DECSTBM - set scrolling region */
968 
969 				if ((top = param[0]) < 1)
970 					top = 1;
971 
972 				if (nparam < 2)
973 					bottom = fBuffer->Height();
974 				else
975 					bottom = param[1];
976 
977 				top--;
978 					bottom--;
979 
980 					if (bottom > top)
981 						fBuffer->SetScrollRegion(top, bottom);
982 
983 					parsestate = groundtable;
984 					break;
985 
986 				case CASE_DECSCUSR_ETC:
987 				// DECSCUSR - set cursor style VT520
988 					if (nparam == 2 && param[1] == ' ') {
989 						bool blinking = (param[0] & 0x01) != 0;
990 						int style = -1;
991 						switch (param[0]) {
992 							case 0:
993 								blinking = true;
994 							case 1:
995 							case 2:
996 								style = BLOCK_CURSOR;
997 								break;
998 							case 3:
999 							case 4:
1000 								style = UNDERLINE_CURSOR;
1001 								break;
1002 							case 5:
1003 							case 6:
1004 								style = IBEAM_CURSOR;
1005 								break;
1006 						}
1007 
1008 						if (style != -1)
1009 							fBuffer->SetCursorStyle(style, blinking);
1010 					}
1011 					parsestate = groundtable;
1012 					break;
1013 
1014 				case CASE_DECREQTPARM:
1015 					// DEXREQTPARM - request terminal parameters
1016 					_DecReqTermParms(param[0]);
1017 					parsestate = groundtable;
1018 					break;
1019 
1020 				case CASE_DECSET:
1021 					/* DECSET */
1022 					for (int i = 0; i < nparam; i++)
1023 						_DecPrivateModeSet(param[i]);
1024 					parsestate = groundtable;
1025 					break;
1026 
1027 				case CASE_DECRST:
1028 					/* DECRST */
1029 					for (int i = 0; i < nparam; i++)
1030 						_DecPrivateModeReset(param[i]);
1031 					parsestate = groundtable;
1032 					break;
1033 
1034 				case CASE_DECALN:
1035 					/* DECALN */
1036 					{
1037 						Attributes attr;
1038 						fBuffer->FillScreen(UTF8Char('E'), attr);
1039 						parsestate = groundtable;
1040 					}
1041 					break;
1042 
1043 					//	case CASE_GSETS:
1044 					//		screen->gsets[scstype] = GSET(c) | cs96;
1045 					//		parsestate = groundtable;
1046 					//		break;
1047 
1048 				case CASE_DECSC:
1049 					/* DECSC */
1050 					fBuffer->SaveCursor();
1051 					parsestate = groundtable;
1052 					break;
1053 
1054 				case CASE_DECRC:
1055 					/* DECRC */
1056 					fBuffer->RestoreCursor();
1057 					parsestate = groundtable;
1058 					break;
1059 
1060 				case CASE_HTS:
1061 					/* HTS */
1062 					fBuffer->SetTabStop(fBuffer->Cursor().x);
1063 					parsestate = groundtable;
1064 					break;
1065 
1066 				case CASE_TBC:
1067 					/* TBC */
1068 					if (param[0] < 1)
1069 						fBuffer->ClearTabStop(fBuffer->Cursor().x);
1070 					else if (param[0] == 3)
1071 						fBuffer->ClearAllTabStops();
1072 					parsestate = groundtable;
1073 					break;
1074 
1075 				case CASE_RI:
1076 					/* RI */
1077 					fBuffer->InsertRI();
1078 					parsestate = groundtable;
1079 					break;
1080 
1081 				case CASE_SS2:
1082 					/* SS2 */
1083 					parsestate = groundtable;
1084 					break;
1085 
1086 				case CASE_SS3:
1087 					/* SS3 */
1088 					parsestate = groundtable;
1089 					break;
1090 
1091 				case CASE_CSI_STATE:
1092 					/* enter csi state */
1093 					nparam = 1;
1094 					param[0] = DEFAULT;
1095 					parsestate = gCsiTable;
1096 					break;
1097 
1098 				case CASE_OSC:
1099 					{
1100 						/* Operating System Command: ESC ] */
1101 						uchar params[512];
1102 						// fill the buffer until BEL, ST or something else.
1103 						bool isParsed = false;
1104 						int32 skipCount = 0; // take care about UTF-8 characters
1105 						for (uint i = 0; !isParsed && i < sizeof(params); i++) {
1106 							params[i] = _NextParseChar();
1107 
1108 							if (skipCount > 0) {
1109 								skipCount--;
1110 								continue;
1111 							}
1112 
1113 							skipCount = UTF8Char::ByteCount(params[i]) - 1;
1114 							if (skipCount > 0)
1115 								continue;
1116 
1117 							switch (params[i]) {
1118 								// BEL
1119 								case 0x07:
1120 									isParsed = true;
1121 									break;
1122 								// 8-bit ST
1123 								case 0x9c:
1124 									isParsed = true;
1125 									break;
1126 								// 7-bit ST is "ESC \"
1127 								case '\\':
1128 								// hm... Was \x1b replaced by 0 during parsing?
1129 									if (i > 0 && params[i - 1] == 0) {
1130 										isParsed = true;
1131 										break;
1132 									}
1133 								default:
1134 									if (!isprint(params[i] & 0x7f))
1135 										break;
1136 									continue;
1137 							}
1138 							params[i] = '\0';
1139 						}
1140 
1141 						// watchdog for the 'end of buffer' case
1142 						params[sizeof(params) - 1] = '\0';
1143 
1144 						if (isParsed)
1145 							_ProcessOperatingSystemControls(params);
1146 
1147 						parsestate = groundtable;
1148 						break;
1149 					}
1150 
1151 				case CASE_RIS:		// ESC c ... Reset terminal.
1152 					break;
1153 
1154 				case CASE_LS2:
1155 					/* select G2 into GL */
1156 					curGL = 2;
1157 					parsestate = groundtable;
1158 					break;
1159 
1160 				case CASE_LS3:
1161 					/* select G3 into GL */
1162 					curGL = 3;
1163 					parsestate = groundtable;
1164 					break;
1165 
1166 				case CASE_LS3R:
1167 					/* select G3 into GR */
1168 					curGR = 3;
1169 					parsestate = groundtable;
1170 					break;
1171 
1172 				case CASE_LS2R:
1173 					/* select G2 into GR */
1174 					curGR = 2;
1175 					parsestate = groundtable;
1176 					break;
1177 
1178 				case CASE_LS1R:
1179 					/* select G1 into GR */
1180 					curGR = 1;
1181 					parsestate = groundtable;
1182 					break;
1183 
1184 				case CASE_VPA:		// ESC [...d move cursor absolute vertical
1185 					/* VPA (CV) */
1186 					if ((row = param[0]) < 1)
1187 						row = 1;
1188 
1189 					// note beterm wants it 1-based unlike usual terminals
1190 					fBuffer->SetCursorY(row - 1);
1191 					parsestate = groundtable;
1192 					break;
1193 
1194 				case CASE_HPA:		// ESC [...G move cursor absolute horizontal
1195 					/* HPA (CH) */
1196 					if ((column = param[0]) < 1)
1197 						column = 1;
1198 
1199 					// note beterm wants it 1-based unlike usual terminals
1200 					fBuffer->SetCursorX(column - 1);
1201 					parsestate = groundtable;
1202 					break;
1203 
1204 				case CASE_SU:	// scroll screen up
1205 					if ((row = param[0]) < 1)
1206 						row = 1;
1207 					fBuffer->ScrollBy(row);
1208 					parsestate = groundtable;
1209 					break;
1210 
1211 				case CASE_SD:	// scroll screen down
1212 					if ((row = param[0]) < 1)
1213 						row = 1;
1214 					fBuffer->ScrollBy(-row);
1215 					parsestate = groundtable;
1216 					break;
1217 
1218 
1219 				case CASE_ECH:	// erase characters
1220 					if ((column = param[0]) < 1)
1221 						column = 1;
1222 					fBuffer->EraseChars(column);
1223 					parsestate = groundtable;
1224 					break;
1225 
1226 				case CASE_CBT:	// cursor back tab
1227 					if ((column = param[0]) < 1)
1228 						column = 1;
1229 					fBuffer->InsertCursorBackTab(column);
1230 					parsestate = groundtable;
1231 					break;
1232 
1233 				case CASE_CFT:	// cursor forward tab
1234 					if ((column= param[0]) < 1)
1235 						column = 1;
1236 					for (int32 i = 0; i < column; ++i)
1237 						fBuffer->InsertTab();
1238 					parsestate = groundtable;
1239 					break;
1240 
1241 				case CASE_CNL:	// cursor next line
1242 					if ((row= param[0]) < 1)
1243 						row = 1;
1244 					fBuffer->SetCursorX(0);
1245 					fBuffer->MoveCursorDown(row);
1246 					parsestate = groundtable;
1247 					break;
1248 
1249 				case CASE_CPL:	// cursor previous line
1250 					if ((row= param[0]) < 1)
1251 						row = 1;
1252 					fBuffer->SetCursorX(0);
1253 					fBuffer->MoveCursorUp(row);
1254 					parsestate = groundtable;
1255 					break;
1256 
1257 				case CASE_REP:		// ESC [...b repeat last graphic char
1258 				{
1259 					int repetitions = param[0];
1260 					int maxRepetitions = fBuffer->Width() * fBuffer->Height();
1261 					if (repetitions > maxRepetitions)
1262 						repetitions = maxRepetitions;
1263 					for (int i = 0; i < repetitions; i++)
1264 						fBuffer->InsertLastChar();
1265 					parsestate = groundtable;
1266 					break;
1267 				}
1268 				default:
1269 					break;
1270 			}
1271 		} catch (...) {
1272 			break;
1273 		}
1274 	}
1275 
1276 	return B_OK;
1277 }
1278 
1279 
1280 /*static*/ int32
1281 TermParse::_ptyreader_thread(void *data)
1282 {
1283 	return reinterpret_cast<TermParse *>(data)->PtyReader();
1284 }
1285 
1286 
1287 /*static*/ int32
1288 TermParse::_escparse_thread(void *data)
1289 {
1290 	return reinterpret_cast<TermParse *>(data)->EscParse();
1291 }
1292 
1293 
1294 status_t
1295 TermParse::_ReadParserBuffer()
1296 {
1297 	// We have to unlock the terminal buffer while waiting for data from the
1298 	// PTY. We don't have to unlock when we don't need to wait, but we do it
1299 	// anyway, so that TermView won't be starved when trying to synchronize.
1300 	fBuffer->Unlock();
1301 
1302 	// wait for new input from pty
1303 	if (atomic_get(&fReadBufferSize) == 0) {
1304 		status_t status = B_OK;
1305 		while (atomic_get(&fReadBufferSize) == 0 && status == B_OK) {
1306 			do {
1307 				status = acquire_sem(fReaderSem);
1308 			} while (status == B_INTERRUPTED);
1309 
1310 			// eat any sems that were released unconditionally
1311 			int32 semCount;
1312 			if (get_sem_count(fReaderSem, &semCount) == B_OK && semCount > 0)
1313 				acquire_sem_etc(fReaderSem, semCount, B_RELATIVE_TIMEOUT, 0);
1314 		}
1315 
1316 		if (status < B_OK) {
1317 			fBuffer->Lock();
1318 			return status;
1319 		}
1320 	}
1321 
1322 	int32 toRead = atomic_get(&fReadBufferSize);
1323 	if (toRead > ESC_PARSER_BUFFER_SIZE)
1324 		toRead = ESC_PARSER_BUFFER_SIZE;
1325 
1326 	for (int32 i = 0; i < toRead; i++) {
1327 		// TODO: This could be optimized using memcpy instead and
1328 		// calculating space left as in the PtyReader().
1329 		fParserBuffer[i] = fReadBuffer[fBufferPosition];
1330 		fBufferPosition = (fBufferPosition + 1) % READ_BUF_SIZE;
1331 	}
1332 
1333 	int32 bufferSize = atomic_add(&fReadBufferSize, -toRead);
1334 
1335 	// If the pty reader thread waits and we have made enough space in the
1336 	// buffer now, let it run again.
1337 	if (bufferSize > READ_BUF_SIZE - MIN_PTY_BUFFER_SPACE
1338 			&& bufferSize - toRead <= READ_BUF_SIZE - MIN_PTY_BUFFER_SPACE) {
1339 		release_sem(fReaderLocker);
1340 	}
1341 
1342 	fParserBufferSize = toRead;
1343 	fParserBufferOffset = 0;
1344 
1345 	fBuffer->Lock();
1346 	return B_OK;
1347 }
1348 
1349 
1350 void
1351 TermParse::_DeviceStatusReport(int n)
1352 {
1353 	char sbuf[16] ;
1354 	int len;
1355 
1356 	switch (n) {
1357 		case 5:
1358 			{
1359 				// Device status report requested
1360 				// reply with "no malfunction detected"
1361 				const char* toWrite = "\033[0n";
1362 				write(fFd, toWrite, strlen(toWrite));
1363 				break ;
1364 			}
1365 		case 6:
1366 			// Cursor position report requested
1367 			len = snprintf(sbuf, sizeof(sbuf),
1368 				"\033[%" B_PRId32 ";%" B_PRId32 "R",
1369 				fBuffer->Cursor().y + 1,
1370 				fBuffer->Cursor().x + 1);
1371 			write(fFd, sbuf, len);
1372 			break ;
1373 		default:
1374 			return;
1375 	}
1376 }
1377 
1378 
1379 void
1380 TermParse::_DecReqTermParms(int value)
1381 {
1382 	// Terminal parameters report:
1383 	//   type (2 or 3);
1384 	//   no parity (1);
1385 	//   8 bits per character (1);
1386 	//   transmit speed 38400bps (128);
1387 	//   receive speed 38400bps (128);
1388 	//   bit rate multiplier 16 (1);
1389 	//   no flags (0)
1390 	char parms[] = "\033[?;1;1;128;128;1;0x";
1391 
1392 	if (value < 1)
1393 		parms[2] = '2';
1394 	else if (value == 1)
1395 		parms[2] = '3';
1396 	else
1397 		return;
1398 
1399 	write(fFd, parms, strlen(parms));
1400 }
1401 
1402 
1403 void
1404 TermParse::_DecPrivateModeSet(int value)
1405 {
1406 	switch (value) {
1407 		case 1:
1408 			// Application Cursor Keys (whatever that means).
1409 			// Not supported yet.
1410 			break;
1411 		case 5:
1412 			// Reverse Video (inverses colors for the complete screen
1413 			// -- when followed by normal video, that's shortly flashes the
1414 			// screen).
1415 			// Not supported yet.
1416 			break;
1417 		case 6:
1418 			// Set Origin Mode.
1419 			fBuffer->SetOriginMode(true);
1420 			break;
1421 		case 9:
1422 			// Set Mouse X and Y on button press.
1423 			fBuffer->ReportX10MouseEvent(true);
1424 			break;
1425 		case 12:
1426 			// Start Blinking Cursor.
1427 			fBuffer->SetCursorBlinking(true);
1428 			break;
1429 		case 25:
1430 			// Show Cursor.
1431 			fBuffer->SetCursorHidden(false);
1432 			break;
1433 		case 47:
1434 			// Use Alternate Screen Buffer.
1435 			fBuffer->UseAlternateScreenBuffer(false);
1436 			break;
1437 		case 1000:
1438 			// Send Mouse X & Y on button press and release.
1439 			fBuffer->ReportNormalMouseEvent(true);
1440 			break;
1441 		case 1002:
1442 			// Send Mouse X and Y on button press and release, and on motion
1443 			// when the mouse enter a new cell
1444 			fBuffer->ReportButtonMouseEvent(true);
1445 			break;
1446 		case 1003:
1447 			// Use All Motion Mouse Tracking
1448 			fBuffer->ReportAnyMouseEvent(true);
1449 			break;
1450 		case 1006:
1451 			// Enable extended mouse coordinates with SGR scheme
1452 			fBuffer->EnableExtendedMouseCoordinates(true);
1453 			break;
1454 		case 1034:
1455 			// Interpret "meta" key, sets eighth bit.
1456 			fBuffer->EnableInterpretMetaKey(true);
1457 			break;
1458 		case 1036:
1459 			// Send ESC when Meta modifies a key
1460 			fBuffer->EnableMetaKeySendsEscape(true);
1461 			break;
1462 		case 1039:
1463 			// TODO: Send ESC when Alt modifies a key
1464 			// Not supported yet.
1465 			break;
1466 		case 1049:
1467 			// Save cursor as in DECSC and use Alternate Screen Buffer, clearing
1468 			// it first.
1469 			fBuffer->SaveCursor();
1470 			fBuffer->UseAlternateScreenBuffer(true);
1471 			break;
1472 		case 2004:
1473 			// Enable bracketed paste mode
1474 			fBuffer->EnableBracketedPasteMode(true);
1475 			break;
1476 	}
1477 }
1478 
1479 
1480 void
1481 TermParse::_DecPrivateModeReset(int value)
1482 {
1483 	switch (value) {
1484 		case 1:
1485 			// Normal Cursor Keys (whatever that means).
1486 			// Not supported yet.
1487 			break;
1488 		case 3:
1489 			// 80 Column Mode.
1490 			// Not supported yet.
1491 			break;
1492 		case 4:
1493 			// Jump (Fast) Scroll.
1494 			// Not supported yet.
1495 			break;
1496 		case 5:
1497 			// Normal Video (Leaves Reverse Video, cf. there).
1498 			// Not supported yet.
1499 			break;
1500 		case 6:
1501 			// Reset Origin Mode.
1502 			fBuffer->SetOriginMode(false);
1503 			break;
1504 		case 9:
1505 			// Disable Mouse X and Y on button press.
1506 			fBuffer->ReportX10MouseEvent(false);
1507 			break;
1508 		case 12:
1509 			// Stop Blinking Cursor.
1510 			fBuffer->SetCursorBlinking(false);
1511 			break;
1512 		case 25:
1513 			// Hide Cursor
1514 			fBuffer->SetCursorHidden(true);
1515 			break;
1516 		case 47:
1517 			// Use Normal Screen Buffer.
1518 			fBuffer->UseNormalScreenBuffer();
1519 			break;
1520 		case 1000:
1521 			// Don't send Mouse X & Y on button press and release.
1522 			fBuffer->ReportNormalMouseEvent(false);
1523 			break;
1524 		case 1002:
1525 			// Don't send Mouse X and Y on button press and release, and on motion
1526 			// when the mouse enter a new cell
1527 			fBuffer->ReportButtonMouseEvent(false);
1528 			break;
1529 		case 1003:
1530 			// Disable All Motion Mouse Tracking.
1531 			fBuffer->ReportAnyMouseEvent(false);
1532 			break;
1533 		case 1006:
1534 			// Disable extended mouse coordinates with SGR scheme
1535 			fBuffer->EnableExtendedMouseCoordinates(false);
1536 			break;
1537 		case 1034:
1538 			// Don't interpret "meta" key.
1539 			fBuffer->EnableInterpretMetaKey(false);
1540 			break;
1541 		case 1036:
1542 			// Don't send ESC when Meta modifies a key
1543 			fBuffer->EnableMetaKeySendsEscape(false);
1544 			break;
1545 		case 1039:
1546 			// TODO: Don't send ESC when Alt modifies a key
1547 			// Not supported yet.
1548 			break;
1549 		case 1049:
1550 			// Use Normal Screen Buffer and restore cursor as in DECRC.
1551 			fBuffer->UseNormalScreenBuffer();
1552 			fBuffer->RestoreCursor();
1553 			break;
1554 		case 2004:
1555 			// Disable bracketed paste mode
1556 			fBuffer->EnableBracketedPasteMode(false);
1557 			break;
1558 	}
1559 }
1560 
1561 
1562 void
1563 TermParse::_ProcessOperatingSystemControls(uchar* params)
1564 {
1565 	int mode = 0;
1566 	for (uchar c = *params; c != ';' && c != '\0'; c = *(++params)) {
1567 		mode *= 10;
1568 		mode += c - '0';
1569 	}
1570 
1571 	// eat the separator
1572 	if (*params == ';')
1573 		params++;
1574 
1575 	static uint8 indexes[kTermColorCount];
1576 	static rgb_color colors[kTermColorCount];
1577 
1578 	switch (mode) {
1579 		case 0: // icon name and window title
1580 		case 2: // window title
1581 			fBuffer->SetTitle((const char*)params);
1582 			break;
1583 		case 4: // set colors (0 - 255)
1584 		case 104: // reset colors (0 - 255)
1585 			{
1586 				bool reset = (mode / 100) == 1;
1587 
1588 				// colors can be in "idx1:name1;...;idxN:nameN;" sequence too!
1589 				uint32 count = 0;
1590 				char* p = strtok((char*)params, ";");
1591 				while (p != NULL && count < kTermColorCount) {
1592 					indexes[count] = atoi(p);
1593 
1594 					if (!reset) {
1595 						p = strtok(NULL, ";");
1596 						if (p == NULL)
1597 							break;
1598 
1599 						if (gXColorsTable.LookUpColor(p, &colors[count]) == B_OK)
1600 							count++;
1601 					} else
1602 						count++;
1603 
1604 					p = strtok(NULL, ";");
1605 				};
1606 
1607 				if (count > 0) {
1608 					if (!reset)
1609 						fBuffer->SetColors(indexes, colors, count);
1610 					else
1611 						fBuffer->ResetColors(indexes, count);
1612 				}
1613 			}
1614 			break;
1615 		// set dynamic colors (10 - 19)
1616 		case 10: // text foreground
1617 		case 11: // text background
1618 		case 12: // cursor back
1619 			{
1620 				int32 offset = mode - 10;
1621 				int32 count = 0;
1622 				if (strcmp((char*)params, "?") == 0) {
1623 					fBuffer->GetColor(mode);
1624 					break;
1625 				}
1626 				char* p = strtok((char*)params, ";");
1627 				do {
1628 					if (gXColorsTable.LookUpColor(p, &colors[count]) != B_OK) {
1629 						// dyna-colors are pos-sensitive - no chance to continue
1630 						break;
1631 					}
1632 
1633 					indexes[count] = 10 + offset + count;
1634 					count++;
1635 					p = strtok(NULL, ";");
1636 
1637 				} while (p != NULL && (offset + count) < 10);
1638 
1639 				if (count > 0) {
1640 					fBuffer->SetColors(indexes, colors, count, true);
1641 				}
1642 			}
1643 			break;
1644 		// reset dynamic colors (10 - 19)
1645 		case 110: // text foreground
1646 		case 111: // text background
1647 		case 112: // cursor back
1648 			{
1649 				indexes[0] = mode;
1650 				fBuffer->ResetColors(indexes, 1, true);
1651 			}
1652 			break;
1653 		default:
1654 		//	printf("%d -> %s\n", mode, params);
1655 			break;
1656 	}
1657 }
1658