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
40extern int gUTF8GroundTable[];		/* UTF8 Ground table */
41extern int gISO8859GroundTable[];	/* ISO8859 & EUC Ground table */
42extern int gWinCPGroundTable[];		/* Windows cp1252, cp1251, koi-8r */
43extern int gSJISGroundTable[];		/* Shift-JIS Ground table */
44
45extern int gEscTable[];				/* ESC */
46extern int gCsiTable[];				/* ESC [ */
47extern int gDecTable[];				/* ESC [ ? */
48extern int gScrTable[];				/* ESC # */
49extern int gIgnoreTable[];			/* ignore table */
50extern int gIesTable[];				/* ignore ESC table */
51extern int gEscIgnoreTable[];		/* ESC ignore table */
52
53extern 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.
60inline uchar
61TermParse::_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
78TermParse::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
97TermParse::~TermParse()
98{
99	StopThreads();
100}
101
102
103status_t
104TermParse::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
129status_t
130TermParse::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.
147status_t
148TermParse::_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.
166status_t
167TermParse::_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
199void
200TermParse::_StopTermParse()
201{
202	if (fParseThread >= 0) {
203		status_t dummy;
204		wait_for_thread(fParseThread, &dummy);
205		fParseThread = -1;
206	}
207}
208
209
210void
211TermParse::_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.
234int32
235TermParse::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
282void
283TermParse::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
319int *
320TermParse::_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
363int32
364TermParse::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
1281TermParse::_ptyreader_thread(void *data)
1282{
1283	return reinterpret_cast<TermParse *>(data)->PtyReader();
1284}
1285
1286
1287/*static*/ int32
1288TermParse::_escparse_thread(void *data)
1289{
1290	return reinterpret_cast<TermParse *>(data)->EscParse();
1291}
1292
1293
1294status_t
1295TermParse::_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
1350void
1351TermParse::_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
1379void
1380TermParse::_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
1403void
1404TermParse::_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
1480void
1481TermParse::_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
1562void
1563TermParse::_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