mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-11 02:10:17 +01:00
976 lines
30 KiB
C
976 lines
30 KiB
C
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is lineterm.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Ramalingam Saravanan.
|
|
* Portions created by the Initial Developer are Copyright (C) 1999
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
/* ltermOutput.c: LTERM PTY output processing
|
|
*/
|
|
|
|
/* public declarations */
|
|
#include "lineterm.h"
|
|
|
|
/* private declarations */
|
|
#include "ltermPrivate.h"
|
|
|
|
|
|
static int ltermAppendOutput(struct lterms *lts, const char *cbuf, int count,
|
|
UNISTYLE style, int interleaveCheck,
|
|
int *interleavedBytes, int rawIncompleteMax,
|
|
int *rawIncompleteBytes, char *rawIncompleteBuf);
|
|
static int ltermDecode(const char *rawBuf, int n_total,
|
|
UNICHAR* decodedBuf, int decodeMax,
|
|
int decodeNUL,
|
|
int *rawIncompleteBytes);
|
|
static int ltermPromptLocate(struct lterms *lts);
|
|
|
|
|
|
/** Processes output from decoded output buffer and returns *opcodes:
|
|
* OPCODES ::= SCREENDATA BELL? ( CLEAR | INSERT | DELETE | SCROLL )?
|
|
* if ScreenMode data is being returned.
|
|
* OPCODES ::= LINEDATA BELL? ( CLEAR
|
|
* | OUTPUT NEWLINE?)
|
|
* if LineMode data is being returned.
|
|
* OPVALS contains return value(s) for specific screen operations
|
|
* such as INSERT, DELETE, and SCROLL.
|
|
* OPROW contains the row value for specific screen operations such as
|
|
* INSERT, DELETE, and SCROLL (or -1, cursor row is to be used).
|
|
* @return 0 on success and -1 on error.
|
|
*/
|
|
int ltermProcessOutput(struct lterms *lts, int *opcodes, int *opvals,
|
|
int *oprow)
|
|
{
|
|
struct LtermOutput *lto = &(lts->ltermOutput);
|
|
UNICHAR uch;
|
|
UNISTYLE ustyle;
|
|
int charIndex, returnCode, consumedChars, remainingChars, j;
|
|
int bellFlag;
|
|
|
|
LTERM_LOG(ltermProcessOutput,30,("lto->outputMode=%d, cursorChar=%d, Chars=%d\n",
|
|
lto->outputMode, lto->outputCursorChar, lto->outputChars));
|
|
|
|
LTERM_LOG(ltermProcessOutput,32, ("lts->commandNumber=%d\n",
|
|
lts->commandNumber));
|
|
|
|
/* Set default returned opcodes, opvals, oprow */
|
|
*opcodes = 0;
|
|
*opvals = 0;
|
|
*oprow = -1;
|
|
|
|
charIndex = 0;
|
|
bellFlag = 0;
|
|
|
|
while ((*opcodes == 0) && (charIndex < lto->decodedChars)) {
|
|
uch = lto->decodedOutput[charIndex];
|
|
ustyle = lto->decodedStyle[charIndex] | lto->styleMask;
|
|
|
|
consumedChars = 1;
|
|
|
|
if (uch == U_ESCAPE) {
|
|
/* Process escape sequence */
|
|
int savedOutputMode = lto->outputMode;
|
|
|
|
LTERM_LOG(ltermProcessOutput,31,("ESCAPE sequence\n"));
|
|
|
|
returnCode = ltermProcessEscape(lts, lto->decodedOutput+charIndex,
|
|
lto->decodedChars-charIndex,
|
|
lto->decodedStyle+charIndex,
|
|
&consumedChars, opcodes, opvals, oprow);
|
|
if (returnCode < 0)
|
|
return -1;
|
|
|
|
if (returnCode == 1) {
|
|
/* Incomplete escape sequence */
|
|
lto->incompleteEscapeSequence = 1;
|
|
if (lto->outputMode == LTERM1_SCREEN_MODE)
|
|
*opcodes = LTERM_SCREENDATA_CODE;
|
|
else
|
|
*opcodes = LTERM_LINEDATA_CODE | LTERM_OUTPUT_CODE;
|
|
}
|
|
|
|
/* Assert that change in output mode is a loop terminating condition */
|
|
if (lto->outputMode != savedOutputMode)
|
|
assert(*opcodes != 0);
|
|
|
|
} else if (lto->outputMode == LTERM1_SCREEN_MODE) {
|
|
/* Screen mode processing */
|
|
|
|
if ((uch >= (UNICHAR)U_SPACE) && (uch != (UNICHAR)U_DEL)) {
|
|
/* Printable non-TAB character */
|
|
|
|
LTERM_LOG(ltermProcessOutput,39,("Screen mode, printable char - %c\n",
|
|
(char) uch));
|
|
|
|
if (lto->insertMode) {
|
|
/* Insert blank character at cursor position */
|
|
if (ltermInsDelEraseChar(lts, 1, LTERM_INSERT_ACTION) != 0)
|
|
return -1;
|
|
}
|
|
|
|
/* Mark column as being modified */
|
|
if ((lto->modifiedCol[lto->cursorRow] == -1) ||
|
|
(lto->modifiedCol[lto->cursorRow] > lto->cursorCol))
|
|
lto->modifiedCol[lto->cursorRow] = lto->cursorCol;
|
|
|
|
/* Replace character and style info at current cursor location */
|
|
j = lto->cursorRow*lts->nCols + lto->cursorCol;
|
|
|
|
assert(j < (lts->nRows * lts->nCols));
|
|
|
|
lto->screenChar[j] = uch;
|
|
lto->screenStyle[j] = ustyle;
|
|
|
|
if (lto->cursorCol < lts->nCols-1) {
|
|
/* Move cursor right */
|
|
lto->cursorCol++;
|
|
}
|
|
|
|
} else {
|
|
/* Control character */
|
|
|
|
switch (uch) {
|
|
case U_BACKSPACE: /* Backspace */
|
|
LTERM_LOG(ltermProcessOutput,32,("Screen mode, BACKSPACE\n"));
|
|
if (lto->cursorCol > 0)
|
|
lto->cursorCol--;
|
|
break;
|
|
|
|
case U_TAB: /* Tab */
|
|
LTERM_LOG(ltermProcessOutput,32,("Screen mode, TAB\n"));
|
|
lto->cursorCol = ((lto->cursorCol/8)+1)*8;
|
|
if (lto->cursorCol > lts->nCols-1)
|
|
lto->cursorCol = lts->nCols-1;
|
|
break;
|
|
|
|
case U_BEL: /* Bell */
|
|
LTERM_LOG(ltermProcessOutput,32,("************ Screen mode, BELL\n"));
|
|
bellFlag = 1;
|
|
break;
|
|
|
|
case U_CRETURN: /* Carriage return */
|
|
lto->cursorCol = 0;
|
|
break;
|
|
|
|
case U_LINEFEED: /* Newline */
|
|
LTERM_LOG(ltermProcessOutput,32,("************ Screen mode, NEWLINE\n\n"));
|
|
|
|
*opcodes = LTERM_SCREENDATA_CODE;
|
|
|
|
if (lto->cursorRow > lto->botScrollRow) {
|
|
/* Not bottom scrolling line; simply move cursor down one row */
|
|
lto->cursorRow--;
|
|
|
|
} else {
|
|
/* Delete top scrollable line, scrolling up */
|
|
if (ltermInsDelEraseLine(lts, 1, lto->topScrollRow, LTERM_DELETE_ACTION) != 0)
|
|
return -1;
|
|
*opcodes |= LTERM_DELETE_CODE;
|
|
*opvals = 1;
|
|
*oprow = lto->topScrollRow;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* Ignore other control characters (including NULs) */
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
/* Line mode processing */
|
|
|
|
if ( ((uch >= (UNICHAR)U_SPACE) && (uch != (UNICHAR)U_DEL)) ||
|
|
(uch == (UNICHAR)U_TAB)) {
|
|
/* Printable/TAB character; replace/insert at current cursor location
|
|
or append to end of line */
|
|
|
|
LTERM_LOG(ltermProcessOutput,39,("Line mode, printable char - %c\n",
|
|
(char) uch));
|
|
|
|
if (lto->outputCursorChar == lto->outputChars) {
|
|
/* Append single character to end of line */
|
|
|
|
if (lto->outputChars+1 > MAXCOLM1) {
|
|
/* Output buffer overflow; ignore character */
|
|
LTERM_WARNING("ltermProcessOutput: Warning - output line buffer overflow\n");
|
|
}
|
|
|
|
lto->outputLine[lto->outputChars] = uch;
|
|
lto->outputStyle[lto->outputChars] = ustyle;
|
|
lto->outputChars++; /* Insert character in output line */
|
|
|
|
lto->outputCursorChar++; /* Reposition cursor */
|
|
|
|
} else {
|
|
/* Replace/insert single character in the middle of line */
|
|
|
|
if (lto->insertMode) {
|
|
/* Insert blank character at cursor position */
|
|
if (ltermInsDelEraseChar(lts, 1, LTERM_INSERT_ACTION) != 0)
|
|
return -1;
|
|
}
|
|
|
|
/* Overwrite single character in the middle of line */
|
|
lto->outputLine[lto->outputCursorChar] = uch;
|
|
lto->outputStyle[lto->outputCursorChar] = ustyle;
|
|
|
|
/* Note modifications */
|
|
if (lto->outputCursorChar < lto->outputModifiedChar)
|
|
lto->outputModifiedChar = lto->outputCursorChar;
|
|
|
|
lto->outputCursorChar++; /* Reposition cursor */
|
|
}
|
|
|
|
} else {
|
|
/* Control character */
|
|
|
|
switch (uch) {
|
|
case U_BACKSPACE: /* Backspace */
|
|
LTERM_LOG(ltermProcessOutput,32,("Line mode, BACKSPACE\n"));
|
|
if (lto->outputCursorChar > 0)
|
|
lto->outputCursorChar--;
|
|
break;
|
|
|
|
case U_CRETURN: /* Carriage return */
|
|
LTERM_LOG(ltermProcessOutput,32,("Line mode, CRETURN\n"));
|
|
lto->outputCursorChar = 0;
|
|
break;
|
|
|
|
case U_BEL: /* Bell */
|
|
LTERM_LOG(ltermProcessOutput,32,("************ Line mode, BELL\n"));
|
|
bellFlag = 1;
|
|
break;
|
|
|
|
case U_CTL_L: /* Formfeed; clear line and return */
|
|
LTERM_LOG(ltermProcessOutput,32,("************ Line mode, FORMFEED\n\n"));
|
|
|
|
ltermClearOutputLine(lts);
|
|
*opcodes = LTERM_LINEDATA_CODE | LTERM_CLEAR_CODE;
|
|
break;
|
|
|
|
case U_LINEFEED: /* Newline; return complete line */
|
|
LTERM_LOG(ltermProcessOutput,32,("************ Line mode, NEWLINE\n\n"));
|
|
|
|
*opcodes = LTERM_LINEDATA_CODE
|
|
| LTERM_OUTPUT_CODE
|
|
| LTERM_NEWLINE_CODE;
|
|
break;
|
|
|
|
default:
|
|
/* Ignore other control characters (including NULs) */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Increment character index */
|
|
charIndex += consumedChars;
|
|
}
|
|
|
|
/* Determine count of unprocessed characters */
|
|
remainingChars = lto->decodedChars - charIndex;
|
|
|
|
if (remainingChars > 0) {
|
|
/* Move unprocessed output to beginning of decode buffer */
|
|
|
|
LTERM_LOG(ltermProcessOutput,32,("Moved %d chars to beginning of decodedOutput\n", remainingChars));
|
|
for (j=0; j<remainingChars; j++) {
|
|
lto->decodedOutput[j] = lto->decodedOutput[j+charIndex];
|
|
lto->decodedStyle[j] = lto->decodedStyle[j+charIndex];
|
|
}
|
|
}
|
|
|
|
/* Update remaining decoded character count */
|
|
lto->decodedChars = remainingChars;
|
|
|
|
if (*opcodes == 0) {
|
|
/* All output processed; without any special terminating condition */
|
|
if (lto->outputMode == LTERM1_SCREEN_MODE) {
|
|
/* Full screen mode */
|
|
*opcodes = LTERM_SCREENDATA_CODE;
|
|
|
|
} else {
|
|
/* Line mode */
|
|
*opcodes = LTERM_LINEDATA_CODE | LTERM_OUTPUT_CODE;
|
|
}
|
|
}
|
|
|
|
/* Set bell code */
|
|
if (bellFlag)
|
|
*opcodes |= LTERM_BELL_CODE;
|
|
|
|
if (*opcodes & LTERM_LINEDATA_CODE) {
|
|
/* Returning line mode data; check for prompt */
|
|
|
|
if ((lts->commandNumber == 0) ||
|
|
(lto->outputModifiedChar < lto->promptChars)) {
|
|
/* If not command line or if "prompt string" may have been modified,
|
|
* search for prompt
|
|
*/
|
|
int promptLen;
|
|
|
|
LTERM_LOG(ltermProcessOutput,39,("Prompt? modifiedChar=%d, promptChars=%d\n",
|
|
lto->outputModifiedChar, lto->promptChars));
|
|
|
|
/* Reset modification marker */
|
|
lto->outputModifiedChar = lto->outputChars;
|
|
|
|
/* Check if prompt string is present in output */
|
|
promptLen = ltermPromptLocate(lts);
|
|
|
|
if (promptLen > 0) {
|
|
/* Prompt string found */
|
|
lto->promptChars = promptLen;
|
|
|
|
if (lts->commandNumber == 0) {
|
|
/* Set command number */
|
|
|
|
/* New command number */
|
|
if (lts->lastCommandNum == 0xFFFF)
|
|
lts->lastCommandNum = 0;
|
|
lts->lastCommandNum++;
|
|
|
|
lts->commandNumber = lts->lastCommandNum;
|
|
|
|
LTERM_LOG(ltermProcessOutput,32,
|
|
("************ Prompt found; commandNumber=%d\n\n",
|
|
lts->commandNumber));
|
|
}
|
|
|
|
} else {
|
|
/* No prompt string */
|
|
if (lts->commandNumber != 0) {
|
|
/* Unset command number and prompt columns */
|
|
UNICHAR temLine[2] = {0, LTERM_WRITE_PLAIN_INPUT};
|
|
|
|
lts->commandNumber = 0;
|
|
lto->promptChars = 0;
|
|
|
|
/* Notify "input thread" by writing null input record */
|
|
WRITE(lts->writeBUFFER, temLine, 2*sizeof(UNICHAR));
|
|
|
|
LTERM_LOG(ltermProcessOutput,32,
|
|
("************ No prompt found; not command line\n\n"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (lto->outputMode == LTERM1_SCREEN_MODE) {
|
|
char modifiedRows[81];
|
|
int showRows = (lts->nRows < 80) ? lts->nRows : 80;
|
|
for (j=0; j<showRows; j++) {
|
|
if (lto->modifiedCol[j] > -1)
|
|
modifiedRows[j] = 'M';
|
|
else
|
|
modifiedRows[j] = '.';
|
|
}
|
|
modifiedRows[showRows] = '\0';
|
|
LTERM_LOG(ltermProcessOutput,38,("modifiedRows=%s\n", modifiedRows));
|
|
}
|
|
|
|
LTERM_LOG(ltermProcessOutput,31,("returned opcodes=0x%X\n", *opcodes));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/** Clears output line buffer */
|
|
void ltermClearOutputLine(struct lterms *lts)
|
|
{
|
|
struct LtermOutput *lto = &(lts->ltermOutput);
|
|
|
|
LTERM_LOG(ltermClearOutputLine,40,("\n"));
|
|
|
|
lto->outputChars = 0;
|
|
lto->outputCursorChar = 0;
|
|
lto->outputModifiedChar = 0;
|
|
lto->promptChars = 0;
|
|
|
|
lts->commandNumber = 0;
|
|
}
|
|
|
|
|
|
/** Clears output screen buffer (allocating memory, if first time/resized) */
|
|
int ltermClearOutputScreen(struct lterms *lts)
|
|
{
|
|
struct LtermOutput *lto = &(lts->ltermOutput);
|
|
int j;
|
|
|
|
LTERM_LOG(ltermClearOutputScreen,40,("\n"));
|
|
|
|
if (lto->screenChar == NULL) {
|
|
/* Allocate memory for full screen */
|
|
int screenSize = lts->nRows * lts->nCols;
|
|
|
|
lto->screenChar = (UNICHAR *) MALLOC(screenSize * sizeof(UNICHAR));
|
|
|
|
if (lto->screenChar == NULL) {
|
|
LTERM_ERROR("ltermClearOutputScreen: Error - failed to allocate memory for chars\n");
|
|
return -1;
|
|
}
|
|
|
|
assert(lto->screenStyle == NULL);
|
|
lto->screenStyle = (UNISTYLE *) MALLOC(screenSize * sizeof(UNISTYLE));
|
|
|
|
if (lto->screenStyle == NULL) {
|
|
LTERM_ERROR("ltermClearOutputScreen: Error - failed to allocate memory for style\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Position cursor at home */
|
|
lto->cursorRow = lts->nRows - 1;
|
|
lto->cursorCol = 0;
|
|
|
|
/* Blank out entire screen */
|
|
if (ltermInsDelEraseLine(lts, lts->nRows, lts->nRows-1, LTERM_ERASE_ACTION) != 0)
|
|
return -1;
|
|
|
|
/* No rows modified yet */
|
|
for (j=0; j<lts->nRows; j++) {
|
|
lto->modifiedCol[j] = -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/** Saves current output mode value and switches to stream output mode,
|
|
* with specified opcodes and terminator string.
|
|
* @return 0 on success and -1 on error.
|
|
*/
|
|
int ltermSwitchToStreamMode(struct lterms *lts, int streamOpcodes,
|
|
const UNICHAR *streamTerminator)
|
|
{
|
|
struct LtermOutput *lto = &(lts->ltermOutput);
|
|
int strLength;
|
|
|
|
LTERM_LOG(ltermSwitchToStreamMode,40,("streamOpcodes=0x%x\n",streamOpcodes));
|
|
|
|
if (streamTerminator != NULL) {
|
|
/* Save terminator string (may be null) */
|
|
strLength = ucslen(streamTerminator);
|
|
ucsncpy( lto->streamTerminator, streamTerminator, MAXSTREAMTERM);
|
|
|
|
LTERM_LOGUNICODE(ltermSwitchToStreamMode,41,(streamTerminator,
|
|
(int) strLength));
|
|
} else {
|
|
/* Null terminator */
|
|
strLength = 0;
|
|
lto->streamTerminator[0] = U_NUL;
|
|
}
|
|
|
|
if (strLength > MAXSTREAMTERM-1) {
|
|
LTERM_ERROR("ltermSwitchToStreamMode: Error - terminator string too long\n");
|
|
return -1;
|
|
}
|
|
|
|
if (lts->options & LTERM_NONUL_FLAG) {
|
|
/* No decoding of NUL characters */
|
|
if (strLength == 0) {
|
|
LTERM_ERROR("ltermSwitchToStreamMode: Error - null terminator string not allowed\n");
|
|
return -1;
|
|
}
|
|
} else {
|
|
/* Decoding NUL characters */
|
|
if (strLength > 0) {
|
|
LTERM_ERROR("ltermSwitchToStreamMode: Error - terminator string must be NUL\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
lto->savedOutputMode = lto->outputMode;
|
|
lto->outputMode = LTERM0_STREAM_MODE;
|
|
lto->streamOpcodes = streamOpcodes;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/** Switches to screen output mode.
|
|
* @return 0 on success and -1 on error.
|
|
*/
|
|
int ltermSwitchToScreenMode(struct lterms *lts)
|
|
{
|
|
struct LtermOutput *lto = &(lts->ltermOutput);
|
|
|
|
LTERM_LOG(ltermSwitchToScreenMode,40,("\n"));
|
|
|
|
if (lto->outputMode == LTERM2_LINE_MODE) {
|
|
/* Switching from line mode to screen mode */
|
|
|
|
/* Clear styleMask */
|
|
lto->styleMask = 0;
|
|
|
|
/* Clear screen */
|
|
if (ltermClearOutputScreen(lts) != 0)
|
|
return -1;
|
|
|
|
/* Reset returned cursor location */
|
|
lto->returnedCursorRow = -1;
|
|
lto->returnedCursorCol = -1;
|
|
|
|
/* Scrolling region */
|
|
lto->topScrollRow = lts->nRows-1;
|
|
lto->botScrollRow = 0;
|
|
|
|
/* Disable input echo */
|
|
lts->restoreInputEcho = !lts->disabledInputEcho;
|
|
lts->disabledInputEcho = 1;
|
|
|
|
/* Switch to raw input mode */
|
|
ltermSwitchToRawMode(lts);
|
|
|
|
}
|
|
|
|
lto->outputMode = LTERM1_SCREEN_MODE;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/** Switches to line output mode.
|
|
* @return 0 on success and -1 on error.
|
|
*/
|
|
int ltermSwitchToLineMode(struct lterms *lts)
|
|
{
|
|
struct LtermOutput *lto = &(lts->ltermOutput);
|
|
int j;
|
|
|
|
LTERM_LOG(ltermSwitchToLineMode,40,("\n"));
|
|
|
|
if (lto->outputMode == LTERM1_SCREEN_MODE) {
|
|
/* Switching from screen mode to line mode */
|
|
|
|
/* Switch to line input mode */
|
|
ltermClearInputLine(lts);
|
|
|
|
if (lts->restoreInputEcho) {
|
|
/* Re-enable input echo */
|
|
lts->disabledInputEcho = 0;
|
|
lts->restoreInputEcho = 0;
|
|
}
|
|
|
|
/* Clear styleMask */
|
|
lto->styleMask = 0;
|
|
|
|
/* Clear output line */
|
|
ltermClearOutputLine(lts);
|
|
|
|
/* Copy bottom line to line output buffer ??? */
|
|
lto->outputChars = lts->nCols;
|
|
|
|
assert(lts->nCols < MAXCOL);
|
|
|
|
for (j=0; j<lts->nCols; j++) {
|
|
lto->outputLine[j] = lto->screenChar[j];
|
|
lto->outputStyle[j] = lto->screenStyle[j];
|
|
}
|
|
|
|
}
|
|
|
|
lto->outputMode = LTERM2_LINE_MODE;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/** Reads data from process STDOUT and/or STDERR using file descriptors
|
|
* from the LTERM POLL structure, and converts to Unicode, saving
|
|
* incomplete character encodings in corresponding incomplete raw buffers.
|
|
* Decoded characters are appended to the decodedOutput buffer,
|
|
* with appropriate style settings.
|
|
* If READERR is false, STDERR is never read.
|
|
* @return the number of decoded characters appended (>=0) if successful,
|
|
* -1 if an error occurred during processing, or
|
|
* -2 if pseudo-TTY has been closed.
|
|
*/
|
|
int ltermReceiveData(struct lterms *lts, int readERR)
|
|
{
|
|
struct LtermOutput *lto = &(lts->ltermOutput);
|
|
char temERRBuf[MAXCOL], temOUTBuf[MAXCOL];
|
|
int readERRMax, readOUTMax;
|
|
int nReadERR, nTotalERR, nReadOUT, nTotalOUT;
|
|
int interleavedBytes, n_decoded, n_decoded_tot, j;
|
|
|
|
LTERM_LOG(ltermReceiveData,30,("\n"));
|
|
|
|
nTotalERR = 0;
|
|
if (readERR && (lto->pollFD[POLL_STDERR].POLL_REVENTS != 0)) {
|
|
/* Read data from STDERR */
|
|
|
|
/* Read maximum number of bytes that will all fit into decodedOutput
|
|
when decoded, assuming number of decoded characters will not exceed
|
|
the number of encoded bytes */
|
|
|
|
readERRMax = MAXCOLM1 - lto->decodedChars - lto->rawERRBytes;
|
|
|
|
/* Reduce by half to leave some space for STDOUT data */
|
|
readERRMax = readERRMax / 2;
|
|
|
|
if (readERRMax <= 0) {
|
|
/* Decoded buffer overflow */
|
|
LTERM_WARNING("ltermReceiveData: Warning - decoded buffer overflow\n");
|
|
|
|
/* Non-fatal error recovery; return without reading */
|
|
return 0;
|
|
}
|
|
|
|
/* Copy any incomplete raw output to beginning of buffer */
|
|
for (j=0; j<lto->rawERRBytes; j++)
|
|
temERRBuf[j] = lto->rawERRBuf[j];
|
|
|
|
/* Read STDERRdata (blocking mode) */
|
|
nReadERR = READ(lto->pollFD[POLL_STDERR].fd,
|
|
temERRBuf + lto->rawERRBytes, (SIZE_T) readERRMax);
|
|
|
|
if (nReadERR < 0) {
|
|
#if defined(DEBUG_LTERM) && !defined(USE_NSPR_IO)
|
|
int errcode = errno;
|
|
/* perror("ltermReceiveData"); */
|
|
#else
|
|
int errcode = 0;
|
|
#endif
|
|
LTERM_ERROR( "ltermReceiveData: Error %d in reading from process STDERR\n",
|
|
errcode);
|
|
return -1;
|
|
}
|
|
|
|
if (nReadERR == 0) {
|
|
/* End of file => pseudo-TTY has been closed */
|
|
LTERM_LOG(ltermReceiveData,31,("pseudo-TTY has been closed\n"));
|
|
|
|
/* Suspend LTERM */
|
|
lts->suspended = 1;
|
|
|
|
return -2;
|
|
}
|
|
|
|
|
|
LTERM_LOG(ltermReceiveData,32,("Read %d raw bytes from STDERR\n", nReadERR));
|
|
nTotalERR = nReadERR + lto->rawERRBytes;
|
|
}
|
|
|
|
nTotalOUT = 0;
|
|
if (lto->pollFD[POLL_STDOUT].POLL_REVENTS != 0) {
|
|
/* Read data from STDOUT */
|
|
|
|
/* Read maximum number of bytes that will all fit into decodedOutput
|
|
when decoded, assuming number of decoded characters will not exceed
|
|
the number of encoded bytes */
|
|
readOUTMax = MAXCOLM1 - lto->decodedChars - lto->rawOUTBytes - nTotalERR;
|
|
|
|
if (readOUTMax <= 0) {
|
|
/* Decoded buffer overflow */
|
|
LTERM_WARNING("ltermReceiveData: Warning - decoded buffer overflow\n");
|
|
|
|
/* Non-fatal error recovery; return without reading */
|
|
return 0;
|
|
}
|
|
|
|
/* Copy any incomplete raw output to beginning of buffer */
|
|
for (j=0; j<lto->rawOUTBytes; j++)
|
|
temOUTBuf[j] = lto->rawOUTBuf[j];
|
|
|
|
/* Read STDOUTdata (blocking mode) */
|
|
nReadOUT = READ(lto->pollFD[POLL_STDOUT].fd,
|
|
temOUTBuf + lto->rawOUTBytes, (SIZE_T) readOUTMax);
|
|
|
|
if (nReadOUT < 0) {
|
|
#if defined(DEBUG_LTERM) && !defined(USE_NSPR_IO)
|
|
int errcode = errno;
|
|
/* perror("ltermReceiveData"); */
|
|
#else
|
|
int errcode = 0;
|
|
#endif
|
|
LTERM_ERROR( "ltermReceiveData: Error %d in reading from process STDOUT\n",
|
|
errcode);
|
|
return -1;
|
|
}
|
|
|
|
if (nReadOUT == 0) {
|
|
/* End of file => pseudo-TTY has been closed */
|
|
LTERM_LOG(ltermReceiveData,31,("pseudo-TTY has been closed\n"));
|
|
|
|
/* Suspend LTERM */
|
|
lts->suspended = 1;
|
|
|
|
return -2;
|
|
}
|
|
|
|
LTERM_LOG(ltermReceiveData,32,("Read %d raw bytes from STDOUT\n", nReadOUT));
|
|
|
|
nTotalOUT = nReadOUT + lto->rawOUTBytes;
|
|
}
|
|
|
|
n_decoded_tot = 0;
|
|
|
|
if (lts->readERRfirst) {
|
|
/* Decode STDERR data first */
|
|
interleavedBytes = 0;
|
|
n_decoded = ltermAppendOutput(lts, temERRBuf, nTotalERR,
|
|
LTERM_STDERR_STYLE,
|
|
lts->interleave, &interleavedBytes,
|
|
MAXRAWINCOMPLETE,
|
|
&(lto->rawERRBytes), lto->rawERRBuf);
|
|
if (n_decoded < 0)
|
|
return -1;
|
|
n_decoded_tot += n_decoded;
|
|
|
|
/* Decode STDOUT data next */
|
|
n_decoded = ltermAppendOutput(lts, temOUTBuf, nTotalOUT,
|
|
LTERM_STDOUT_STYLE,
|
|
0, NULL,
|
|
MAXRAWINCOMPLETE,
|
|
&(lto->rawOUTBytes), lto->rawOUTBuf);
|
|
if (n_decoded < 0)
|
|
return -1;
|
|
n_decoded_tot += n_decoded;
|
|
|
|
if (interleavedBytes > 0) {
|
|
/* Decode remaining STDERR data */
|
|
n_decoded = ltermAppendOutput(lts, temERRBuf+interleavedBytes,
|
|
nTotalERR-interleavedBytes,
|
|
LTERM_STDERR_STYLE,
|
|
0, NULL,
|
|
MAXRAWINCOMPLETE,
|
|
&(lto->rawERRBytes), lto->rawERRBuf);
|
|
if (n_decoded < 0)
|
|
return -1;
|
|
n_decoded_tot += n_decoded;
|
|
}
|
|
|
|
} else {
|
|
/* Decode STDOUT data first */
|
|
interleavedBytes = 0;
|
|
n_decoded = ltermAppendOutput(lts, temOUTBuf, nTotalOUT,
|
|
LTERM_STDOUT_STYLE,
|
|
lts->interleave, &interleavedBytes,
|
|
MAXRAWINCOMPLETE,
|
|
&(lto->rawOUTBytes), lto->rawOUTBuf);
|
|
if (n_decoded < 0)
|
|
return -1;
|
|
n_decoded_tot += n_decoded;
|
|
|
|
/* Decode STDERR data next */
|
|
n_decoded = ltermAppendOutput(lts, temERRBuf, nTotalERR,
|
|
LTERM_STDERR_STYLE,
|
|
0, NULL,
|
|
MAXRAWINCOMPLETE,
|
|
&(lto->rawERRBytes), lto->rawERRBuf);
|
|
if (n_decoded < 0)
|
|
return -1;
|
|
n_decoded_tot += n_decoded;
|
|
|
|
if (interleavedBytes > 0) {
|
|
/* Decode remaining STDOUT data */
|
|
n_decoded = ltermAppendOutput(lts, temOUTBuf+interleavedBytes,
|
|
nTotalOUT-interleavedBytes,
|
|
LTERM_STDOUT_STYLE,
|
|
0, NULL,
|
|
MAXRAWINCOMPLETE,
|
|
&(lto->rawOUTBytes), lto->rawOUTBuf);
|
|
if (n_decoded < 0)
|
|
return -1;
|
|
n_decoded_tot += n_decoded;
|
|
}
|
|
}
|
|
|
|
if (n_decoded_tot > 0) {
|
|
/* New characters have been decoded; no longer incomplete escape seq. */
|
|
lto->incompleteEscapeSequence = 0;
|
|
}
|
|
|
|
return n_decoded_tot;
|
|
}
|
|
|
|
|
|
/** Process COUNT bytes raw data from CBUF, converting complete
|
|
* character encodings to Unicode, saving RAWINCOMPLETEBYTES in
|
|
* RAWINCOMPLETEBUF, whose maximum size is specified by RAWINCOMPLETEMAX.
|
|
* If INTERLEAVECHECK, check for starting NEWLINE and other interleavable
|
|
* characters, and if found "decode" just those characters and set
|
|
* INTERLEAVEDBYTES to the number of bytes consumed on return;
|
|
* otherwise INTERLEAVEDBYTES is set to 0.
|
|
* The decoded characters are appended to the decodedOutput buffer,
|
|
* with style STYLE.
|
|
* @return the number of characters appended (>=0) if successful,
|
|
* -1 if an error occurred during processing, or
|
|
* -2 if pseudo-TTY has been closed.
|
|
*/
|
|
static int ltermAppendOutput(struct lterms *lts, const char *cbuf, int count,
|
|
UNISTYLE style, int interleaveCheck,
|
|
int *interleavedBytes, int rawIncompleteMax,
|
|
int *rawIncompleteBytes, char *rawIncompleteBuf)
|
|
{
|
|
struct LtermOutput *lto = &(lts->ltermOutput);
|
|
int decodeMax, n_decoded, decodeNUL, j;
|
|
|
|
LTERM_LOG(ltermAppendOutput,30,("count=%d, style=0x%X, iCheck=%d, rawIncMax=%d\n",
|
|
count, style, interleaveCheck, rawIncompleteMax));
|
|
|
|
if (interleaveCheck && (count > 0) && (cbuf[0] == '\n')) {
|
|
/* Raw buffer starts with NEWLINE character; interleave */
|
|
|
|
assert(lto->decodedChars < MAXCOLM1);
|
|
|
|
/* "Decode" just the NEWLINE character */
|
|
lto->decodedOutput[lto->decodedChars] = U_LINEFEED;
|
|
lto->decodedStyle[lto->decodedChars] = LTERM_STDOUT_STYLE;
|
|
|
|
lto->decodedChars++;
|
|
|
|
*interleavedBytes = 1;
|
|
|
|
LTERM_LOG(ltermAppendOutput,32,("INTERLEAVED %d bytes\n",
|
|
*interleavedBytes));
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (interleavedBytes != NULL)
|
|
*interleavedBytes = 0;
|
|
|
|
if (count == 0)
|
|
return 0;
|
|
|
|
/* Decode all complete encoded character sequences */
|
|
decodeMax = MAXCOLM1 - lto->decodedChars;
|
|
decodeNUL = (lts->options & LTERM_NONUL_FLAG) == 0;
|
|
n_decoded = ltermDecode(cbuf, count,
|
|
lto->decodedOutput+lto->decodedChars,
|
|
decodeMax, decodeNUL,
|
|
rawIncompleteBytes);
|
|
|
|
if (n_decoded < 0)
|
|
return -1;
|
|
|
|
/* Save any incomplete raw byte encodings */
|
|
if (*rawIncompleteBytes > rawIncompleteMax) {
|
|
LTERM_ERROR("ltermAppendOutput: Error - too many incomplete raw characters\n");
|
|
return -1;
|
|
}
|
|
|
|
for (j=0; j<*rawIncompleteBytes; j++)
|
|
rawIncompleteBuf[j] = cbuf[j+count-*rawIncompleteBytes];
|
|
|
|
/* Set decoded character styles */
|
|
for (j=lto->decodedChars; j<lto->decodedChars+n_decoded; j++)
|
|
lto->decodedStyle[j] = style;
|
|
|
|
/* Increment decoded character count */
|
|
lto->decodedChars += n_decoded;
|
|
|
|
LTERM_LOG(ltermAppendOutput,32,("Appended %d bytes\n", n_decoded));
|
|
|
|
return n_decoded;
|
|
}
|
|
|
|
|
|
/** Decodes N_TOTAL bytes in RAWBUF, returning upto DECODEMAX Unicode
|
|
* characters in DECODEDBUF, leaving *RAWINCOMPLETEBYTES at the end of
|
|
* RAWBUF undecoded.
|
|
* Any NUL (zero) characters in RAWBUF are skipped, unless
|
|
* decodeNUL is true.
|
|
* If there is not enough space in DECODEDBUF for all characters to be
|
|
* decoded, an error is returned.
|
|
* @return the number of Unicoded characters decoded (>=0) or -1 on error.
|
|
*/
|
|
static int ltermDecode(const char *rawBuf, int n_total,
|
|
UNICHAR* decodedBuf, int decodeMax,
|
|
int decodeNUL,
|
|
int *rawIncompleteBytes)
|
|
{
|
|
int n_decoded, result;
|
|
|
|
LTERM_LOG(ltermDecode,40,("\n"));
|
|
|
|
if (decodeMax < n_total) {
|
|
LTERM_ERROR("ltermDecode: Error - decode buffer overflow\n");
|
|
return -1;
|
|
}
|
|
|
|
result = utf8toucs(rawBuf, n_total, decodedBuf, decodeMax,
|
|
!decodeNUL, rawIncompleteBytes, &n_decoded);
|
|
|
|
if (result != 0) {
|
|
LTERM_WARNING("ltermDecode: Warning - Invalid UTF8 data encountered\n");
|
|
}
|
|
|
|
LTERM_LOG(ltermDecode,41,("result=%d, incomplete=%d, n_decoded=%d\n",
|
|
result, *rawIncompleteBytes, n_decoded));
|
|
LTERM_LOGUNICODE(ltermDecode,42,(decodedBuf, n_decoded));
|
|
|
|
return n_decoded;
|
|
}
|
|
|
|
|
|
/** Search for prompt string in the output line buffer.
|
|
* TEMPORARY implementation: assume promptRegexp is just a list of delimiters
|
|
* @return the length of the matching prompt string, or
|
|
* 0 if there is no match.
|
|
*/
|
|
static int ltermPromptLocate(struct lterms *lts)
|
|
{
|
|
struct LtermOutput *lto = &(lts->ltermOutput);
|
|
int prefixCount, promptLen;
|
|
|
|
LTERM_LOG(ltermPromptLocate,49,("lto->outputChars=%d\n",
|
|
lto->outputChars));
|
|
|
|
/* Assert that there is at least one free character in the buffer */
|
|
assert(lto->outputChars < MAXCOL);
|
|
|
|
if (lto->outputChars == 0)
|
|
return 0;
|
|
|
|
/* Insert null character at the end of the output buffer */
|
|
lto->outputLine[lto->outputChars] = U_NUL;
|
|
|
|
/* Determine length of initial non-delimiter prefix */
|
|
prefixCount = ucscspn(lto->outputLine, lts->promptRegexp);
|
|
|
|
if (prefixCount+1 >= lto->outputChars) {
|
|
promptLen = 0;
|
|
|
|
} else {
|
|
/* Skip any whitespace following the delimiter */
|
|
const UNICHAR spaceStr[3] = {U_SPACE, U_TAB, U_NUL};
|
|
int spaceCount = ucsspn(lto->outputLine+prefixCount+1, spaceStr);
|
|
|
|
promptLen = prefixCount + 1 + spaceCount;
|
|
|
|
LTERM_LOGUNICODE(ltermPromptLocate,41,(lto->outputLine, promptLen));
|
|
}
|
|
|
|
return promptLen;
|
|
}
|