⛏️ index : haiku.git

/*
 * Copyright 2001-2024, Haiku, Inc. All rights reserved.
 * Copyright (c) 2003-4 Kian Duffy <myob@users.sourceforge.net>
 * Parts Copyright (C) 1998,99 Kazuho Okui and Takashi Murai.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *		Kian Duffy, myob@users.sourceforge.net
 *		Simon South, simon@simonsouth.net
 *		Siarzhuk Zharski, zharik@gmx.li
 */


//! Escape sequence parse and character encoding.


#include "TermParse.h"

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

#include <Autolock.h>
#include <Beep.h>
#include <Catalog.h>
#include <Locale.h>
#include <Message.h>
#include <UTF8.h>

#include "Colors.h"
#include "TermConst.h"
#include "TerminalBuffer.h"
#include "VTparse.h"


extern int gUTF8GroundTable[];		/* UTF8 Ground table */
extern int gISO8859GroundTable[];	/* ISO8859 & EUC Ground table */
extern int gWinCPGroundTable[];		/* Windows cp1252, cp1251, koi-8r */
extern int gSJISGroundTable[];		/* Shift-JIS Ground table */

extern int gEscTable[];				/* ESC */
extern int gCsiTable[];				/* ESC [ */
extern int gDecTable[];				/* ESC [ ? */
extern int gScrTable[];				/* ESC # */
extern int gIgnoreTable[];			/* ignore table */
extern int gIesTable[];				/* ignore ESC table */
extern int gEscIgnoreTable[];		/* ESC ignore table */

extern const char* gLineDrawGraphSet[]; /* may be used for G0, G1, G2, G3 */

#define DEFAULT -1
#define NPARAM 10		// Max parameters


//! Get char from pty reader buffer.
inline uchar
TermParse::_NextParseChar()
{
	if (fParserBufferOffset >= fParserBufferSize) {
		// parser buffer empty
		status_t error = _ReadParserBuffer();
		if (error != B_OK)
			throw error;
	}

#ifdef USE_DEBUG_SNAPSHOTS
	fBuffer->CaptureChar(fParserBuffer[fParserBufferOffset]);
#endif

	return fParserBuffer[fParserBufferOffset++];
}


TermParse::TermParse(int fd)
	:
	fFd(fd),
	fParseThread(-1),
	fReaderThread(-1),
	fReaderSem(-1),
	fReaderLocker(-1),
	fBufferPosition(0),
	fReadBufferSize(0),
	fParserBufferSize(0),
	fParserBufferOffset(0),
	fBuffer(NULL),
	fQuitting(true)
{
	memset(fReadBuffer, 0, READ_BUF_SIZE);
	memset(fParserBuffer, 0, ESC_PARSER_BUFFER_SIZE);
}


TermParse::~TermParse()
{
	StopThreads();
}


status_t
TermParse::StartThreads(TerminalBuffer *buffer)
{
	if (fBuffer != NULL)
		return B_ERROR;

	fQuitting = false;
	fBuffer = buffer;

	status_t status = _InitPtyReader();
	if (status < B_OK) {
		fBuffer = NULL;
		return status;
	}

	status = _InitTermParse();
	if (status < B_OK) {
		_StopPtyReader();
		fBuffer = NULL;
		return status;
	}

	return B_OK;
}


status_t
TermParse::StopThreads()
{
	if (fBuffer == NULL)
		return B_ERROR;

	fQuitting = true;

	_StopPtyReader();
	_StopTermParse();

	fBuffer = NULL;

	return B_OK;
}


//! Initialize and spawn EscParse thread.
status_t
TermParse::_InitTermParse()
{
	if (fParseThread >= 0)
		return B_ERROR; // we might want to return B_OK instead ?

	fParseThread = spawn_thread(_escparse_thread, "EscParse",
		B_DISPLAY_PRIORITY, this);

	if (fParseThread < 0)
		return fParseThread;

	resume_thread(fParseThread);

	return B_OK;
}


//! Initialize and spawn PtyReader thread.
status_t
TermParse::_InitPtyReader()
{
	if (fReaderThread >= 0)
		return B_ERROR; // same as above

	fReaderSem = create_sem(0, "pty_reader_sem");
	if (fReaderSem < 0)
		return fReaderSem;

	fReaderLocker = create_sem(0, "pty_locker_sem");
	if (fReaderLocker < 0) {
		delete_sem(fReaderSem);
		fReaderSem = -1;
		return fReaderLocker;
	}

	fReaderThread = spawn_thread(_ptyreader_thread, "PtyReader",
		B_NORMAL_PRIORITY, this);
	if (fReaderThread < 0) {
		delete_sem(fReaderSem);
		fReaderSem = -1;
		delete_sem(fReaderLocker);
		fReaderLocker = -1;
		return fReaderThread;
	}

	resume_thread(fReaderThread);

	return B_OK;
}


void
TermParse::_StopTermParse()
{
	if (fParseThread >= 0) {
		status_t dummy;
		wait_for_thread(fParseThread, &dummy);
		fParseThread = -1;
	}
}


void
TermParse::_StopPtyReader()
{
	if (fReaderSem >= 0) {
		delete_sem(fReaderSem);
		fReaderSem = -1;
	}
	if (fReaderLocker >= 0) {
		delete_sem(fReaderLocker);
		fReaderLocker = -1;
	}

	if (fReaderThread >= 0) {
		suspend_thread(fReaderThread);

		status_t status;
		wait_for_thread(fReaderThread, &status);

		fReaderThread = -1;
	}
}


//! Get data from pty device.
int32
TermParse::PtyReader()
{
	int32 bufferSize = 0;
	int32 readPos = 0;
	while (!fQuitting) {
		// If Pty Buffer nearly full, snooze this thread, and continue.
		while (READ_BUF_SIZE - bufferSize < MIN_PTY_BUFFER_SPACE) {
			status_t status;
			do {
				status = acquire_sem(fReaderLocker);
			} while (status == B_INTERRUPTED);
			if (status < B_OK)
				return status;

			bufferSize = fReadBufferSize;
		}

		// Read PTY
		uchar buf[READ_BUF_SIZE];
		ssize_t nread = read(fFd, buf, READ_BUF_SIZE - bufferSize);
		if (nread <= 0) {
			fBuffer->NotifyQuit(errno);
			return B_OK;
		}

		// Copy read string to PtyBuffer.

		int32 left = READ_BUF_SIZE - readPos;

		if (nread >= left) {
			memcpy(fReadBuffer + readPos, buf, left);
			memcpy(fReadBuffer, buf + left, nread - left);
		} else
			memcpy(fReadBuffer + readPos, buf, nread);

		bufferSize = atomic_add(&fReadBufferSize, nread);
		if (bufferSize == 0)
			release_sem(fReaderSem);

		bufferSize += nread;
		readPos = (readPos + nread) % READ_BUF_SIZE;
	}

	return B_OK;
}


void
TermParse::DumpState(int *groundtable, int *parsestate, uchar c)
{
	static const struct {
		int *p;
		const char *name;
	} tables[] = {
#define T(t) \
	{ t, #t }
		T(gUTF8GroundTable),
		T(gISO8859GroundTable),
		T(gWinCPGroundTable),
		T(gSJISGroundTable),
		T(gEscTable),
		T(gCsiTable),
		T(gDecTable),
		T(gScrTable),
		T(gIgnoreTable),
		T(gIesTable),
		T(gEscIgnoreTable),
		{ NULL, NULL }
	};
	int i;
	fprintf(stderr, "groundtable: ");
	for (i = 0; tables[i].p; i++) {
		if (tables[i].p == groundtable)
			fprintf(stderr, "%s\t", tables[i].name);
	}
	fprintf(stderr, "parsestate: ");
	for (i = 0; tables[i].p; i++) {
		if (tables[i].p == parsestate)
			fprintf(stderr, "%s\t", tables[i].name);
	}
	fprintf(stderr, "char: 0x%02x (%d)\n", c, c);
}


int *
TermParse::_GuessGroundTable(int encoding)
{
	switch (encoding) {
		case B_ISO1_CONVERSION:
		case B_ISO2_CONVERSION:
		case B_ISO3_CONVERSION:
		case B_ISO4_CONVERSION:
		case B_ISO5_CONVERSION:
		case B_ISO6_CONVERSION:
		case B_ISO7_CONVERSION:
		case B_ISO8_CONVERSION:
		case B_ISO9_CONVERSION:
		case B_ISO10_CONVERSION:
		case B_ISO13_CONVERSION:
		case B_ISO14_CONVERSION:
		case B_ISO15_CONVERSION:
		case B_EUC_CONVERSION:
		case B_EUC_KR_CONVERSION:
		case B_JIS_CONVERSION:
		case B_BIG5_CONVERSION:
			return gISO8859GroundTable;

		case B_KOI8R_CONVERSION:
		case B_MS_WINDOWS_1251_CONVERSION:
		case B_MS_WINDOWS_CONVERSION:
		case B_MAC_ROMAN_CONVERSION:
		case B_MS_DOS_866_CONVERSION:
		case B_GBK_CONVERSION:
		case B_MS_DOS_CONVERSION:
			return gWinCPGroundTable;

		case B_SJIS_CONVERSION:
			return gSJISGroundTable;

		case M_UTF8:
		default:
			break;
	}

	return gUTF8GroundTable;
}


int32
TermParse::EscParse()
{
	int top = 0;
	int bottom = 0;

	char cbuf[4] = { 0 };
	char dstbuf[4] = { 0 };

	int currentEncoding = -1;

	int param[NPARAM];
	int nparam = 1;
	for (int i = 0; i < NPARAM; i++)
		param[i] = DEFAULT;

	int row = 0;
	int column = 0;

	// default encoding system is UTF8
	int *groundtable = gUTF8GroundTable;
	int *parsestate = gUTF8GroundTable;

	// handle alternative character sets G0 - G4
	const char** graphSets[4] = { NULL, NULL, NULL, NULL };
	int curGL = 0;
	int curGR = 0;

	BAutolock locker(fBuffer);

	while (!fQuitting) {
		try {
			uchar c = _NextParseChar();

			//DumpState(groundtable, parsestate, c);

			if (currentEncoding != fBuffer->Encoding()) {
				// Change coding, change parse table.
				groundtable = _GuessGroundTable(fBuffer->Encoding());
				parsestate = groundtable;
				currentEncoding = fBuffer->Encoding();
			}

	//debug_printf("TermParse: char: '%c' (%d), parse state: %d\n", c, c, parsestate[c]);
			int32 srcLen = 0;
			int32 dstLen = sizeof(dstbuf);
			int32 dummyState = 0;

			switch (parsestate[c]) {
				case CASE_PRINT:
				{
					int curGS = c < 128 ? curGL : curGR;
					const char** curGraphSet = graphSets[curGS];
					if (curGraphSet != NULL) {
						int offset = c - (c < 128 ? 0x20 : 0xA0);
						if (offset >= 0 && offset < 96
							&& curGraphSet[offset] != 0) {
							fBuffer->InsertChar(curGraphSet[offset]);
							break;
						}
					}
					fBuffer->InsertChar((char)c);
					break;
				}
				case CASE_PRINT_GR:
				{
					/* case iso8859 gr character, or euc */
					switch (currentEncoding) {
						case B_EUC_CONVERSION:
						case B_EUC_KR_CONVERSION:
						case B_JIS_CONVERSION:
						case B_BIG5_CONVERSION:
							cbuf[srcLen++] = c;
							c = _NextParseChar();
							cbuf[srcLen++] = c;
							break;

						case B_GBK_CONVERSION:
							cbuf[srcLen++] = c;
							do {
								// GBK-compatible codepoints are 2-bytes long
								c = _NextParseChar();
								cbuf[srcLen++] = c;

								// GB18030 extends GBK with 4-byte codepoints
								// using 2nd byte from range 0x30...0x39
								if (srcLen == 2 && (c < 0x30 || c > 0x39))
									break;
							} while (srcLen < 4);
							break;

						default: // ISO-8859-1...10 and MacRoman
							cbuf[srcLen++] = c;
							break;
					}

					if (srcLen > 0) {
						int encoding = currentEncoding == B_JIS_CONVERSION
							? B_EUC_CONVERSION : currentEncoding;

						convert_to_utf8(encoding, cbuf, &srcLen,
								dstbuf, &dstLen, &dummyState, '?');

						fBuffer->InsertChar(UTF8Char(dstbuf, dstLen));
					}
					break;
				}

				case CASE_LF:
					fBuffer->InsertLF();
					break;

				case CASE_CR:
					fBuffer->InsertCR();
					break;

				case CASE_INDEX:
					fBuffer->InsertLF();
					parsestate = groundtable;
					break;

				case CASE_NEXT_LINE:
					fBuffer->NextLine();
					parsestate = groundtable;
					break;

				case CASE_SJIS_KANA:
					cbuf[srcLen++] = c;
					convert_to_utf8(currentEncoding, cbuf, &srcLen,
							dstbuf, &dstLen, &dummyState, '?');
					fBuffer->InsertChar(UTF8Char(dstbuf, dstLen));
					break;

				case CASE_SJIS_INSTRING:
					cbuf[srcLen++] = c;
					c = _NextParseChar();
					cbuf[srcLen++] = c;

					convert_to_utf8(currentEncoding, cbuf, &srcLen,
							dstbuf, &dstLen, &dummyState, '?');
					fBuffer->InsertChar(UTF8Char(dstbuf, dstLen));
					break;

				case CASE_UTF8_2BYTE:
					cbuf[srcLen++] = c;
					c = _NextParseChar();
					if (groundtable[c] != CASE_UTF8_INSTRING)
						break;
					cbuf[srcLen++] = c;

					fBuffer->InsertChar(UTF8Char(cbuf, srcLen));
					break;

				case CASE_UTF8_3BYTE:
					cbuf[srcLen++] = c;

					do {
						c = _NextParseChar();
						if (groundtable[c] != CASE_UTF8_INSTRING) {
							srcLen = 0;
							break;
						}
						cbuf[srcLen++] = c;

					} while (srcLen != 3);

					if (srcLen > 0)
						fBuffer->InsertChar(UTF8Char(cbuf, srcLen));
					break;

				case CASE_SCS_STATE:
				{
					int set = -1;
					switch (c) {
						case '(':
							set = 0;
							break;
						case ')':
						case '-':
							set = 1;
							break;
						case '*':
						case '.':
							set = 2;
							break;
						case '+':
						case '/':
							set = 3;
							break;
						default:
							break;
					}

					if (set > -1) {
						char page = _NextParseChar();
						switch (page) {
							case '0':
								graphSets[set] = gLineDrawGraphSet;
								break;
							default:
								graphSets[set] = NULL;
								break;
						}
					}

					parsestate = groundtable;
					break;
				}

				case CASE_GROUND_STATE:
					/* exit ignore mode */
					parsestate = groundtable;
					break;

				case CASE_BELL:
					beep();
					break;

				case CASE_BS:
					fBuffer->MoveCursorLeft(1);
					break;

				case CASE_TAB:
					fBuffer->InsertTab();
					break;

				case CASE_ESC:
					/* escape */
					parsestate = gEscTable;
					break;

				case CASE_IGNORE_STATE:
					/* Ies: ignore anything else */
					parsestate = gIgnoreTable;
					break;

				case CASE_IGNORE_ESC:
					/* Ign: escape */
					parsestate = gIesTable;
					break;

				case CASE_IGNORE:
					/* Ignore character */
					break;

				case CASE_LS1:
					/* select G1 into GL */
					curGL = 1;
					parsestate = groundtable;
					break;

				case CASE_LS0:
					/* select G0 into GL */
					curGL = 0;
					parsestate = groundtable;
					break;

				case CASE_SCR_STATE:	// ESC #
					/* enter scr state */
					parsestate = gScrTable;
					break;

				case CASE_ESC_IGNORE:
					/* unknown escape sequence */
					parsestate = gEscIgnoreTable;
					break;

				case CASE_ESC_DIGIT:	// ESC [ number
					/* digit in csi or dec mode */
					if ((row = param[nparam - 1]) == DEFAULT)
						row = 0;
					param[nparam - 1] = 10 * row + (c - '0');
					break;

				case CASE_ESC_SEMI:		// ESC ;
					/* semicolon in csi or dec mode */
					if (nparam < NPARAM)
						param[nparam++] = DEFAULT;
					break;

				case CASE_CSI_SP: // ESC [N q
					// part of change cursor style DECSCUSR
					if (nparam < NPARAM)
						param[nparam++] = ' ';
					break;

				case CASE_CSI_EXCL: // ESC [N p
					// part of soft reset DECSTR
					if (nparam < NPARAM)
						param[nparam++] = '!';
					break;

				case CASE_DEC_DOL: // ESC [? N p
					// part of change cursor style DECRQM
					if (nparam < NPARAM)
						param[nparam++] = '$';
					break;

				case CASE_DEC_STATE:
					/* enter dec mode */
					parsestate = gDecTable;
					break;

				case CASE_ICH:		// ESC [@ insert charactor
					/* ICH */
					if ((row = param[0]) < 1)
						row = 1;
					fBuffer->InsertSpace(row);
					parsestate = groundtable;
					break;

				case CASE_CUU:		// ESC [A cursor up, up arrow key.
					/* CUU */
					if ((row = param[0]) < 1)
						row = 1;
					fBuffer->MoveCursorUp(row);
					parsestate = groundtable;
					break;

				case CASE_CUD:		// ESC [B cursor down, down arrow key.
					/* CUD */
					if ((row = param[0]) < 1)
						row = 1;
					fBuffer->MoveCursorDown(row);
					parsestate = groundtable;
					break;

				case CASE_CUF:		// ESC [C cursor forword
					/* CUF */
					if ((row = param[0]) < 1)
						row = 1;
					fBuffer->MoveCursorRight(row);
					parsestate = groundtable;
					break;

				case CASE_CUB:		// ESC [D cursor backword
					/* CUB */
					if ((row = param[0]) < 1)
						row = 1;
					fBuffer->MoveCursorLeft(row);
					parsestate = groundtable;
					break;

				case CASE_CUP:		// ESC [...H move cursor
					/* CUP | HVP */
					if ((row = param[0]) < 1)
						row = 1;
					if (nparam < 2 || (column = param[1]) < 1)
						column = 1;

					fBuffer->SetCursor(column - 1, row - 1 );
					parsestate = groundtable;
					break;

				case CASE_ED:		// ESC [ ...J clear screen
					/* ED */
					switch (param[0]) {
						case DEFAULT:
						case 0:
							fBuffer->EraseBelow();
							break;

						case 1:
							fBuffer->EraseAbove();
							break;

						case 2:
							fBuffer->EraseAll();
							break;

						case 3:
							fBuffer->EraseScrollback();
							break;
					}
					parsestate = groundtable;
					break;

				case CASE_EL:		// ESC [ ...K delete line
					/* EL */
					switch (param[0]) {
						case DEFAULT:
						case 0:
							fBuffer->DeleteColumns();
							break;

						case 1:
							fBuffer->EraseCharsFrom(0, fBuffer->Cursor().x + 1);
							break;

						case 2:
							fBuffer->DeleteColumnsFrom(0);
							break;
					}
					parsestate = groundtable;
					break;

				case CASE_IL:
					/* IL */
					if ((row = param[0]) < 1)
						row = 1;
					fBuffer->InsertLines(row);
					parsestate = groundtable;
					break;

				case CASE_DL:
					/* DL */
					if ((row = param[0]) < 1)
						row = 1;
					fBuffer->DeleteLines(row);
					parsestate = groundtable;
					break;

				case CASE_DCH:
					/* DCH */
					if ((row = param[0]) < 1)
						row = 1;
					fBuffer->DeleteChars(row);
					parsestate = groundtable;
					break;

				case CASE_SET:
					/* SET */
					if (param[0] == 4)
						fBuffer->SetInsertMode(MODE_INSERT);
					parsestate = groundtable;
					break;

				case CASE_RST:
					/* RST */
					if (param[0] == 4)
						fBuffer->SetInsertMode(MODE_OVER);
					parsestate = groundtable;
					break;

				case CASE_SGR:
				{
					/* SGR */
					Attributes attributes = fBuffer->GetAttributes();
					for (row = 0; row < nparam; ++row) {
						switch (param[row]) {
							case DEFAULT:
							case 0: /* Reset attribute */
								attributes.Reset();
								break;

							case 1: /* Bold     */
							case 5:
								attributes |= BOLD;
								break;

							case 4:	/* Underline	*/
								if ((row + 1) < nparam) {
									row++;
									switch (param[row]) {
										case 0:
											attributes.UnsetUnder();
											break;
										case 1:
											attributes.SetUnder(SINGLE_UNDERLINE);
											break;
										case 2:
											attributes.SetUnder(DOUBLE_UNDERLINE);
											break;
										case 3:
											attributes.SetUnder(CURLY_UNDERLINE);
											break;
										case 4:
											attributes.SetUnder(DOTTED_UNDERLINE);
											break;
										case 5:
											attributes.SetUnder(DASHED_UNDERLINE);
											break;
										default:
											row = nparam; // force exit of the parsing
											break;
									}
								} else
									attributes.SetUnder(SINGLE_UNDERLINE);
								break;

							case 7:	/* Inverse	*/
								attributes |= INVERSE;
								break;

							case 8:	/* Hidden	*/
								attributes |= HIDDEN;
								break;

							case 21:	/* Double Underline	*/
								attributes.SetUnder(DOUBLE_UNDERLINE);
								break;

							case 22:	/* Not Bold	*/
								attributes &= ~BOLD;
								break;

							case 24:	/* Not Underline	*/
								attributes.UnsetUnder();
								break;

							case 27:	/* Not Inverse	*/
								attributes &= ~INVERSE;
								break;

							case 28:	/* Not Hidden	*/
								attributes &= ~HIDDEN;
								break;

							case 53:	/* Overline	*/
								attributes |= OVERLINE;
								break;

							case 55:	/* Not Overline	*/
								attributes &= ~OVERLINE;
								break;

							case 90:
							case 91:
							case 92:
							case 93:
							case 94:
							case 95:
							case 96:
							case 97:
								param[row] -= 60;
							case 30:
							case 31:
							case 32:
							case 33:
							case 34:
							case 35:
							case 36:
							case 37:
								attributes.SetIndexedForeground(param[row] - 30);
								break;

							case 38:
							{
								if (nparam >= 3 && param[row+1] == 5) {
									attributes.SetIndexedForeground(param[row+2]);
									row += 2;
								} else if (nparam >= 5 && param[row+1] == 2) {
									attributes.SetDirectForeground(param[row+2], param[row+3], param[row+4]);
									row += 4;
								} else {
									row = nparam; // force exit of the parsing
								}

								break;
							}

							case 39:
								attributes.UnsetForeground();
								break;

							case 100:
							case 101:
							case 102:
							case 103:
							case 104:
							case 105:
							case 106:
							case 107:
								param[row] -= 60;
							case 40:
							case 41:
							case 42:
							case 43:
							case 44:
							case 45:
							case 46:
							case 47:
								attributes.SetIndexedBackground(param[row] - 40);
								break;

							case 48:
							{
								if (nparam >= 3 && param[row+1] == 5) {
									attributes.SetIndexedBackground(param[row+2]);
									row += 2;
								} else if (nparam >= 5 && param[row+1] == 2) {
									attributes.SetDirectBackground(param[row+2], param[row+3], param[row+4]);
									row += 4;
								} else {
									row = nparam; // force exit of the parsing
								}

								break;
							}

							case 49:
								attributes.UnsetBackground();
								break;

							case 58:
							{
								if (nparam >= 3 && param[row+1] == 5) {
									attributes.SetIndexedUnderline(param[row+2]);
									row += 2;
								} else if (nparam >= 5 && param[row+1] == 2) {
									attributes.SetDirectUnderline(param[row+2], param[row+3], param[row+4]);
									row += 4;
								} else {
									row = nparam; // force exit of the parsing
								}

								break;
							}

							case 59:
								attributes.UnsetUnderline();
								break;
						}
					}
					fBuffer->SetAttributes(attributes);
					parsestate = groundtable;
					break;
				}

				case CASE_CPR:
				// Q & D hack by Y.Hayakawa (hida@sawada.riec.tohoku.ac.jp)
				// 21-JUL-99
				_DeviceStatusReport(param[0]);
				parsestate = groundtable;
				break;

				case CASE_DA1:
				// DA - report device attributes
				if (param[0] < 1) {
					// claim to be a VT102
					write(fFd, "\033[?6c", 5);
				}
				parsestate = groundtable;
				break;

				case CASE_DECSTBM:
				/* DECSTBM - set scrolling region */

				if ((top = param[0]) < 1)
					top = 1;

				if (nparam < 2)
					bottom = fBuffer->Height();
				else
					bottom = param[1];

				top--;
					bottom--;

					if (bottom > top)
						fBuffer->SetScrollRegion(top, bottom);

					parsestate = groundtable;
					break;

				case CASE_DECSCUSR_ETC:
				// DECSCUSR - set cursor style VT520
					if (nparam == 2 && param[1] == ' ') {
						bool blinking = (param[0] & 0x01) != 0;
						int style = -1;
						switch (param[0]) {
							case 0:
								blinking = true;
							case 1:
							case 2:
								style = BLOCK_CURSOR;
								break;
							case 3:
							case 4:
								style = UNDERLINE_CURSOR;
								break;
							case 5:
							case 6:
								style = IBEAM_CURSOR;
								break;
						}

						if (style != -1) {
							fBuffer->SetCursorStyle(style);
							if (blinking)
								fBuffer->SetMode(MODE_CURSOR_BLINKING);
							else
								fBuffer->ResetMode(MODE_CURSOR_BLINKING);
						}
					}
					parsestate = groundtable;
					break;

				case CASE_DECREQTPARM:
					// DEXREQTPARM - request terminal parameters
					_DecReqTermParms(param[0]);
					parsestate = groundtable;
					break;

				case CASE_DECSET:
					/* DECSET */
					for (int i = 0; i < nparam; i++)
						_DecPrivateModeSet(param[i]);
					parsestate = groundtable;
					break;

				case CASE_DECRST:
					/* DECRST */
					for (int i = 0; i < nparam; i++)
						_DecPrivateModeReset(param[i]);
					parsestate = groundtable;
					break;

				case CASE_DECALN:
					/* DECALN */
					{
						Attributes attr;
						fBuffer->FillScreen(UTF8Char('E'), attr);
						parsestate = groundtable;
					}
					break;

					//	case CASE_GSETS:
					//		screen->gsets[scstype] = GSET(c) | cs96;
					//		parsestate = groundtable;
					//		break;

				case CASE_DECSC:
					/* DECSC */
					fBuffer->SaveCursor();
					parsestate = groundtable;
					break;

				case CASE_DECRC:
					/* DECRC */
					fBuffer->RestoreCursor();
					parsestate = groundtable;
					break;

				case CASE_HTS:
					/* HTS */
					fBuffer->SetTabStop(fBuffer->Cursor().x);
					parsestate = groundtable;
					break;

				case CASE_TBC:
					/* TBC */
					if (param[0] < 1)
						fBuffer->ClearTabStop(fBuffer->Cursor().x);
					else if (param[0] == 3)
						fBuffer->ClearAllTabStops();
					parsestate = groundtable;
					break;

				case CASE_RI:
					/* RI */
					fBuffer->InsertRI();
					parsestate = groundtable;
					break;

				case CASE_SS2:
					/* SS2 */
					parsestate = groundtable;
					break;

				case CASE_SS3:
					/* SS3 */
					parsestate = groundtable;
					break;

				case CASE_CSI_STATE:
					/* enter csi state */
					nparam = 1;
					param[0] = DEFAULT;
					parsestate = gCsiTable;
					break;

				case CASE_OSC:
					{
						/* Operating System Command: ESC ] */
						uchar params[512];
						// fill the buffer until BEL, ST or something else.
						bool isParsed = false;
						int32 skipCount = 0; // take care about UTF-8 characters
						for (uint i = 0; !isParsed && i < sizeof(params); i++) {
							params[i] = _NextParseChar();

							if (skipCount > 0) {
								skipCount--;
								continue;
							}

							skipCount = UTF8Char::ByteCount(params[i]) - 1;
							if (skipCount > 0)
								continue;

							switch (params[i]) {
								// BEL
								case 0x07:
									isParsed = true;
									break;
								// 8-bit ST
								case 0x9c:
									isParsed = true;
									break;
								// 7-bit ST is "ESC \"
								case '\\':
								// hm... Was \x1b replaced by 0 during parsing?
									if (i > 0 && params[i - 1] == 0) {
										isParsed = true;
										break;
									}
								default:
									if (!isprint(params[i] & 0x7f))
										break;
									continue;
							}
							params[i] = '\0';
						}

						// watchdog for the 'end of buffer' case
						params[sizeof(params) - 1] = '\0';

						if (isParsed)
							_ProcessOperatingSystemControls(params);

						parsestate = groundtable;
						break;
					}

				case CASE_RIS:		// ESC c ... Reset terminal.
					break;

				case CASE_LS2:
					/* select G2 into GL */
					curGL = 2;
					parsestate = groundtable;
					break;

				case CASE_LS3:
					/* select G3 into GL */
					curGL = 3;
					parsestate = groundtable;
					break;

				case CASE_LS3R:
					/* select G3 into GR */
					curGR = 3;
					parsestate = groundtable;
					break;

				case CASE_LS2R:
					/* select G2 into GR */
					curGR = 2;
					parsestate = groundtable;
					break;

				case CASE_LS1R:
					/* select G1 into GR */
					curGR = 1;
					parsestate = groundtable;
					break;

				case CASE_VPA:		// ESC [...d move cursor absolute vertical
					/* VPA (CV) */
					if ((row = param[0]) < 1)
						row = 1;

					// note beterm wants it 1-based unlike usual terminals
					fBuffer->SetCursorY(row - 1);
					parsestate = groundtable;
					break;

				case CASE_HPA:		// ESC [...G move cursor absolute horizontal
					/* HPA (CH) */
					if ((column = param[0]) < 1)
						column = 1;

					// note beterm wants it 1-based unlike usual terminals
					fBuffer->SetCursorX(column - 1);
					parsestate = groundtable;
					break;

				case CASE_SU:	// scroll screen up
					if ((row = param[0]) < 1)
						row = 1;
					fBuffer->ScrollBy(row);
					parsestate = groundtable;
					break;

				case CASE_SD:	// scroll screen down
					if ((row = param[0]) < 1)
						row = 1;
					fBuffer->ScrollBy(-row);
					parsestate = groundtable;
					break;


				case CASE_ECH:	// erase characters
					if ((column = param[0]) < 1)
						column = 1;
					fBuffer->EraseChars(column);
					parsestate = groundtable;
					break;

				case CASE_CBT:	// cursor back tab
					if ((column = param[0]) < 1)
						column = 1;
					fBuffer->InsertCursorBackTab(column);
					parsestate = groundtable;
					break;

				case CASE_CFT:	// cursor forward tab
					if ((column = param[0]) < 1)
						column = 1;
					for (int32 i = 0; i < column; ++i)
						fBuffer->InsertTab();
					parsestate = groundtable;
					break;

				case CASE_CNL:	// cursor next line
					if ((row = param[0]) < 1)
						row = 1;
					fBuffer->SetCursorX(0);
					fBuffer->MoveCursorDown(row);
					parsestate = groundtable;
					break;

				case CASE_CPL:	// cursor previous line
					if ((row = param[0]) < 1)
						row = 1;
					fBuffer->SetCursorX(0);
					fBuffer->MoveCursorUp(row);
					parsestate = groundtable;
					break;

				case CASE_REP:		// ESC [...b repeat last graphic char
				{
					int repetitions = param[0];
					if (repetitions < 1)
						repetitions = 1;
					int maxRepetitions = fBuffer->Width() * fBuffer->Height();
					if (repetitions > maxRepetitions)
						repetitions = maxRepetitions;
					for (int i = 0; i < repetitions; i++)
						fBuffer->InsertLastChar();
					parsestate = groundtable;
					break;
				}

				case CASE_DECRQM:	// DECRQM - request mode to terminal
					if (nparam == 2 && param[1] == '$') {
						_DecPrivateModeRequest(param[0]);
					}
					parsestate = groundtable;
					break;

				case CASE_DECSTR:	// DECSTR - soft terminal reset
					if (nparam == 2 && param[1] == '!') {
						Attributes attributes;
						fBuffer->SetAttributes(attributes);
					}
					parsestate = groundtable;
					break;

				default:
					break;
			}
		} catch (...) {
			break;
		}
	}

	return B_OK;
}


/*static*/ int32
TermParse::_ptyreader_thread(void *data)
{
	return reinterpret_cast<TermParse *>(data)->PtyReader();
}


/*static*/ int32
TermParse::_escparse_thread(void *data)
{
	return reinterpret_cast<TermParse *>(data)->EscParse();
}


status_t
TermParse::_ReadParserBuffer()
{
	// We have to unlock the terminal buffer while waiting for data from the
	// PTY. We don't have to unlock when we don't need to wait, but we do it
	// anyway, so that TermView won't be starved when trying to synchronize.
	fBuffer->Unlock();

	// wait for new input from pty
	if (atomic_get(&fReadBufferSize) == 0) {
		status_t status = B_OK;
		while (atomic_get(&fReadBufferSize) == 0 && status == B_OK) {
			do {
				status = acquire_sem(fReaderSem);
			} while (status == B_INTERRUPTED);

			// eat any sems that were released unconditionally
			int32 semCount;
			if (get_sem_count(fReaderSem, &semCount) == B_OK && semCount > 0)
				acquire_sem_etc(fReaderSem, semCount, B_RELATIVE_TIMEOUT, 0);
		}

		if (status < B_OK) {
			fBuffer->Lock();
			return status;
		}
	}

	int32 toRead = atomic_get(&fReadBufferSize);
	if (toRead > ESC_PARSER_BUFFER_SIZE)
		toRead = ESC_PARSER_BUFFER_SIZE;

	for (int32 i = 0; i < toRead; i++) {
		// TODO: This could be optimized using memcpy instead and
		// calculating space left as in the PtyReader().
		fParserBuffer[i] = fReadBuffer[fBufferPosition];
		fBufferPosition = (fBufferPosition + 1) % READ_BUF_SIZE;
	}

	int32 bufferSize = atomic_add(&fReadBufferSize, -toRead);

	// If the pty reader thread waits and we have made enough space in the
	// buffer now, let it run again.
	if (bufferSize > READ_BUF_SIZE - MIN_PTY_BUFFER_SPACE
			&& bufferSize - toRead <= READ_BUF_SIZE - MIN_PTY_BUFFER_SPACE) {
		release_sem(fReaderLocker);
	}

	fParserBufferSize = toRead;
	fParserBufferOffset = 0;

	fBuffer->Lock();
	return B_OK;
}


void
TermParse::_DeviceStatusReport(int n)
{
	char sbuf[16] ;
	int len;

	switch (n) {
		case 5:
			{
				// Device status report requested
				// reply with "no malfunction detected"
				const char* toWrite = "\033[0n";
				write(fFd, toWrite, strlen(toWrite));
				break ;
			}
		case 6:
			// Cursor position report requested
			len = snprintf(sbuf, sizeof(sbuf),
				"\033[%" B_PRId32 ";%" B_PRId32 "R",
				fBuffer->Cursor().y + 1,
				fBuffer->Cursor().x + 1);
			write(fFd, sbuf, len);
			break ;
		default:
			return;
	}
}


void
TermParse::_DecReqTermParms(int value)
{
	// Terminal parameters report:
	//   type (2 or 3);
	//   no parity (1);
	//   8 bits per character (1);
	//   transmit speed 38400bps (128);
	//   receive speed 38400bps (128);
	//   bit rate multiplier 16 (1);
	//   no flags (0)
	char parms[] = "\033[?;1;1;128;128;1;0x";

	if (value < 1)
		parms[2] = '2';
	else if (value == 1)
		parms[2] = '3';
	else
		return;

	write(fFd, parms, strlen(parms));
}


void
TermParse::_DecPrivateModeSet(int value)
{
	switch (value) {
		case 1:
			// Application Cursor Keys (whatever that means).
			// Not supported yet.
			break;
		case 5:
			// Reverse Video (inverses colors for the complete screen
			// -- when followed by normal video, that's shortly flashes the
			// screen).
			// Not supported yet.
			break;
		case 6:
			// Set Origin Mode.
			fBuffer->SetOriginMode(true);
			break;
		case 9:
			// Set Mouse X and Y on button press.
			fBuffer->SetMode(MODE_REPORT_X10_MOUSE_EVENT);
			break;
		case 12:
			// Start Blinking Cursor.
			fBuffer->SetMode(MODE_CURSOR_BLINKING);
			break;
		case 25:
			// Show Cursor.
			fBuffer->ResetMode(MODE_CURSOR_HIDDEN);
			break;
		case 47:
			// Use Alternate Screen Buffer.
			fBuffer->UseAlternateScreenBuffer(false);
			break;
		case 1000:
			// Send Mouse X & Y on button press and release.
			fBuffer->SetMode(MODE_REPORT_NORMAL_MOUSE_EVENT);
			break;
		case 1002:
			// Send Mouse X and Y on button press and release, and on motion
			// when the mouse enter a new cell
			fBuffer->SetMode(MODE_REPORT_BUTTON_MOUSE_EVENT);
			break;
		case 1003:
			// Use All Motion Mouse Tracking
			fBuffer->SetMode(MODE_REPORT_ANY_MOUSE_EVENT);
			break;
		case 1006:
			// Enable extended mouse coordinates with SGR scheme
			fBuffer->SetMode(MODE_EXTENDED_MOUSE_COORDINATES);
			break;
		case 1034:
			// Interpret "meta" key, sets eighth bit.
			fBuffer->SetMode(MODE_INTERPRET_META_KEY);
			break;
		case 1036:
			// Send ESC when Meta modifies a key
			fBuffer->SetMode(MODE_META_KEY_SENDS_ESCAPE);
			break;
		case 1039:
			// TODO: Send ESC when Alt modifies a key
			// Not supported yet.
			break;
		case 1049:
			// Save cursor as in DECSC and use Alternate Screen Buffer, clearing
			// it first.
			fBuffer->SaveCursor();
			fBuffer->UseAlternateScreenBuffer(true);
			break;
		case 2004:
			// Enable bracketed paste mode
			fBuffer->SetMode(MODE_BRACKETED_PASTE);
			break;
	}
}


void
TermParse::_DecPrivateModeReset(int value)
{
	switch (value) {
		case 1:
			// Normal Cursor Keys (whatever that means).
			// Not supported yet.
			break;
		case 3:
			// 80 Column Mode.
			// Not supported yet.
			break;
		case 4:
			// Jump (Fast) Scroll.
			// Not supported yet.
			break;
		case 5:
			// Normal Video (Leaves Reverse Video, cf. there).
			// Not supported yet.
			break;
		case 6:
			// Reset Origin Mode.
			fBuffer->SetOriginMode(false);
			break;
		case 9:
			// Disable Mouse X and Y on button press.
			fBuffer->ResetMode(MODE_REPORT_X10_MOUSE_EVENT);
			break;
		case 12:
			// Stop Blinking Cursor.
			fBuffer->ResetMode(MODE_CURSOR_BLINKING);
			break;
		case 25:
			// Hide Cursor
			fBuffer->SetMode(MODE_CURSOR_HIDDEN);
			break;
		case 47:
			// Use Normal Screen Buffer.
			fBuffer->UseNormalScreenBuffer();
			break;
		case 1000:
			// Don't send Mouse X & Y on button press and release.
			fBuffer->ResetMode(MODE_REPORT_NORMAL_MOUSE_EVENT);
			break;
		case 1002:
			// Don't send Mouse X and Y on button press and release, and on motion
			// when the mouse enter a new cell
			fBuffer->ResetMode(MODE_REPORT_BUTTON_MOUSE_EVENT);
			break;
		case 1003:
			// Disable All Motion Mouse Tracking.
			fBuffer->ResetMode(MODE_REPORT_ANY_MOUSE_EVENT);
			break;
		case 1006:
			// Disable extended mouse coordinates with SGR scheme
			fBuffer->ResetMode(MODE_EXTENDED_MOUSE_COORDINATES);
			break;
		case 1034:
			// Don't interpret "meta" key.
			fBuffer->ResetMode(MODE_INTERPRET_META_KEY);
			break;
		case 1036:
			// Don't send ESC when Meta modifies a key
			fBuffer->ResetMode(MODE_META_KEY_SENDS_ESCAPE);
			break;
		case 1039:
			// TODO: Don't send ESC when Alt modifies a key
			// Not supported yet.
			break;
		case 1049:
			// Use Normal Screen Buffer and restore cursor as in DECRC.
			fBuffer->UseNormalScreenBuffer();
			fBuffer->RestoreCursor();
			break;
		case 2004:
			// Disable bracketed paste mode
			fBuffer->ResetMode(MODE_BRACKETED_PASTE);
			break;
	}
}


void
TermParse::_DecPrivateModeRequest(int value)
{
	BString reply;
	switch (value) {
		case 12:
			// Request cursor blinking mode
			reply.SetToFormat("\033[?12;%u$y\033\\",
				fBuffer->IsMode(MODE_CURSOR_BLINKING) ? 1 : 2);
			break;
		case 1006:
			// Request extended mouse coordinates with SGR scheme
			reply.SetToFormat("\033[?1006;%u$y\033\\",
				fBuffer->IsMode(MODE_EXTENDED_MOUSE_COORDINATES) ? 1 : 2);
			break;
		case 2004:
			// Request bracketed paste mode
			reply.SetToFormat("\033[?2004;%u$y\033\\",
				fBuffer->IsMode(MODE_BRACKETED_PASTE) ? 1 : 2);
			break;
		default:
			return;
	}
	_WriteReply(reply);
}


void
TermParse::_ProcessOperatingSystemControls(uchar* params)
{
	int mode = 0;
	for (uchar c = *params; c != ';' && c != '\0'; c = *(++params)) {
		mode *= 10;
		mode += c - '0';
	}

	// eat the separator
	if (*params == ';')
		params++;

	static uint8 indexes[kTermColorCount];
	static rgb_color colors[kTermColorCount];

	switch (mode) {
		case 0: // icon name and window title
		case 2: // window title
			fBuffer->SetTitle((const char*)params);
			break;
		case 4: // set colors (0 - 255)
		case 104: // reset colors (0 - 255)
			{
				bool reset = (mode / 100) == 1;

				// colors can be in "idx1:name1;...;idxN:nameN;" sequence too!
				uint32 count = 0;
				char* p = strtok((char*)params, ";");
				while (p != NULL && count < kTermColorCount) {
					indexes[count] = atoi(p);

					if (!reset) {
						p = strtok(NULL, ";");
						if (p == NULL)
							break;

						if (gXColorsTable.LookUpColor(p, &colors[count]) == B_OK)
							count++;
					} else
						count++;

					p = strtok(NULL, ";");
				};

				if (count > 0) {
					if (!reset)
						fBuffer->SetColors(indexes, colors, count);
					else
						fBuffer->ResetColors(indexes, count);
				}
			}
			break;
		// set dynamic colors (10 - 19)
		case 10: // text foreground
		case 11: // text background
		case 12: // cursor back
			{
				int32 offset = mode - 10;
				int32 count = 0;
				if (strcmp((char*)params, "?") == 0) {
					fBuffer->GetColor(mode);
					break;
				}
				char* p = strtok((char*)params, ";");
				do {
					if (gXColorsTable.LookUpColor(p, &colors[count]) != B_OK) {
						// dyna-colors are pos-sensitive - no chance to continue
						break;
					}

					indexes[count] = 10 + offset + count;
					count++;
					p = strtok(NULL, ";");

				} while (p != NULL && (offset + count) < 10);

				if (count > 0) {
					fBuffer->SetColors(indexes, colors, count, true);
				}
			}
			break;
		// reset dynamic colors (10 - 19)
		case 110: // text foreground
		case 111: // text background
		case 112: // cursor back
			{
				indexes[0] = mode;
				fBuffer->ResetColors(indexes, 1, true);
			}
			break;
		// handle hyperlinks
		case 8:
		{
			char* id = NULL;
			char* start = (char*)params;
			char* end = strpbrk(start, ";:");
			for (; end != NULL; start = end + 1) {
				end = strpbrk(start, ";:");
				if (end == NULL)
					break;
				if (end - start > 3 && strncmp(start, "id=", 3) == 0)
					id = strndup(start + 3, end - start + 3);
				if (*end == ';')
					break;
			}
			if (end == NULL)
				break;
			BString uri(end + 1);
			Attributes attributes = fBuffer->GetAttributes();
			if (uri.IsEmpty()) {
				attributes.SetHyperlink(0);
			} else {
				attributes.SetHyperlink(fBuffer->PutHyperLink(id, uri));
			}
			free(id);
			fBuffer->SetAttributes(attributes);
			break;
		}
		default:
		//	printf("%d -> %s\n", mode, params);
			break;
	}
}


void
TermParse::_WriteReply(BString &reply)
{
	write(fFd, reply.String(), reply.Length());
}