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