/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jackpal.androidterm.emulatorview;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.util.Locale;
import android.util.Log;
/**
* Renders text into a screen. Contains all the terminal-specific knowledge and
* state. Emulates a subset of the X Window System xterm terminal, which in turn
* is an emulator for a subset of the Digital Equipment Corporation vt100
* terminal. Missing functionality: text attributes (bold, underline, reverse
* video, color) alternate screen cursor key and keypad escape sequences.
*/
class TerminalEmulator {
public void setKeyListener(TermKeyListener l) {
mKeyListener = l;
}
private TermKeyListener mKeyListener;
/**
* The cursor row. Numbered 0..mRows-1.
*/
private int mCursorRow;
/**
* The cursor column. Numbered 0..mColumns-1.
*/
private int mCursorCol;
/**
* The number of character rows in the terminal screen.
*/
private int mRows;
/**
* The number of character columns in the terminal screen.
*/
private int mColumns;
/**
* Stores the characters that appear on the screen of the emulated terminal.
*/
private TranscriptScreen mMainBuffer;
private TranscriptScreen mAltBuffer;
private TranscriptScreen mScreen;
/**
* The terminal session this emulator is bound to.
*/
private TermSession mSession;
/**
* Keeps track of the current argument of the current escape sequence.
* Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. (Typically just 0 or 1.)
*/
private int mArgIndex;
/**
* The number of parameter arguments. This name comes from the ANSI standard
* for terminal escape codes.
*/
private static final int MAX_ESCAPE_PARAMETERS = 16;
/**
* Holds the arguments of the current escape sequence.
*/
private int[] mArgs = new int[MAX_ESCAPE_PARAMETERS];
/**
* Holds OSC arguments, which can be strings.
*/
private byte[] mOSCArg = new byte[MAX_OSC_STRING_LENGTH];
private int mOSCArgLength;
private int mOSCArgTokenizerIndex;
/**
* Don't know what the actual limit is, this seems OK for now.
*/
private static final int MAX_OSC_STRING_LENGTH = 512;
// Escape processing states:
/**
* Escape processing state: Not currently in an escape sequence.
*/
private static final int ESC_NONE = 0;
/**
* Escape processing state: Have seen an ESC character
*/
private static final int ESC = 1;
/**
* Escape processing state: Have seen ESC POUND
*/
private static final int ESC_POUND = 2;
/**
* Escape processing state: Have seen ESC and a character-set-select char
*/
private static final int ESC_SELECT_LEFT_PAREN = 3;
/**
* Escape processing state: Have seen ESC and a character-set-select char
*/
private static final int ESC_SELECT_RIGHT_PAREN = 4;
/**
* Escape processing state: ESC [
*/
private static final int ESC_LEFT_SQUARE_BRACKET = 5;
/**
* Escape processing state: ESC [ ?
*/
private static final int ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK = 6;
/**
* Escape processing state: ESC %
*/
private static final int ESC_PERCENT = 7;
/**
* Escape processing state: ESC ] (AKA OSC - Operating System Controls)
*/
private static final int ESC_RIGHT_SQUARE_BRACKET = 8;
/**
* Escape processing state: ESC ] (AKA OSC - Operating System Controls)
*/
private static final int ESC_RIGHT_SQUARE_BRACKET_ESC = 9;
/**
* True if the current escape sequence should continue, false if the current
* escape sequence should be terminated. Used when parsing a single
* character.
*/
private boolean mContinueSequence;
/**
* The current state of the escape sequence state machine.
*/
private int mEscapeState;
/**
* Saved state of the cursor row, Used to implement the save/restore cursor
* position escape sequences.
*/
private int mSavedCursorRow;
/**
* Saved state of the cursor column, Used to implement the save/restore
* cursor position escape sequences.
*/
private int mSavedCursorCol;
private int mSavedEffect;
private int mSavedDecFlags_DECSC_DECRC;
// DecSet booleans
/**
* This mask indicates 132-column mode is set. (As opposed to 80-column
* mode.)
*/
private static final int K_132_COLUMN_MODE_MASK = 1 << 3;
/**
* DECSCNM - set means reverse video (light background.)
*/
private static final int K_REVERSE_VIDEO_MASK = 1 << 5;
/**
* This mask indicates that origin mode is set. (Cursor addressing is
* relative to the absolute screen size, rather than the currently set top
* and bottom margins.)
*/
private static final int K_ORIGIN_MODE_MASK = 1 << 6;
/**
* This mask indicates that wraparound mode is set. (As opposed to
* stop-at-right-column mode.)
*/
private static final int K_WRAPAROUND_MODE_MASK = 1 << 7;
/**
* This mask indicates that the cursor should be shown. DECTCEM
*/
private static final int K_SHOW_CURSOR_MASK = 1 << 25;
/** This mask is the subset of DecSet bits that are saved / restored by
* the DECSC / DECRC commands
*/
private static final int K_DECSC_DECRC_MASK =
K_ORIGIN_MODE_MASK | K_WRAPAROUND_MODE_MASK;
/**
* Holds multiple DECSET flags. The data is stored this way, rather than in
* separate booleans, to make it easier to implement the save-and-restore
* semantics. The various k*ModeMask masks can be used to extract and modify
* the individual flags current states.
*/
private int mDecFlags;
/**
* Saves away a snapshot of the DECSET flags. Used to implement save and
* restore escape sequences.
*/
private int mSavedDecFlags;
/**
* The current DECSET mouse tracking mode, zero for no mouse tracking.
*/
private int mMouseTrackingMode;
// Modes set with Set Mode / Reset Mode
/**
* True if insert mode (as opposed to replace mode) is active. In insert
* mode new characters are inserted, pushing existing text to the right.
*/
private boolean mInsertMode;
/**
* An array of tab stops. mTabStop[i] is true if there is a tab stop set for
* column i.
*/
private boolean[] mTabStop;
// The margins allow portions of the screen to be locked.
/**
* The top margin of the screen, for scrolling purposes. Ranges from 0 to
* mRows-2.
*/
private int mTopMargin;
/**
* The bottom margin of the screen, for scrolling purposes. Ranges from
* mTopMargin + 2 to mRows. (Defines the first row after the scrolling
* region.
*/
private int mBottomMargin;
/**
* True if the next character to be emitted will be automatically wrapped to
* the next line. Used to disambiguate the case where the cursor is
* positioned on column mColumns-1.
*/
private boolean mAboutToAutoWrap;
/**
* The width of the last emitted spacing character. Used to place
* combining characters into the correct column.
*/
private int mLastEmittedCharWidth = 0;
/**
* True if we just auto-wrapped and no character has been emitted on this
* line yet. Used to ensure combining characters following a character
* at the edge of the screen are stored in the proper place.
*/
private boolean mJustWrapped = false;
/**
* Used for debugging, counts how many chars have been processed.
*/
private int mProcessedCharCount;
/**
* Foreground color, 0..255
*/
private int mForeColor;
private int mDefaultForeColor;
/**
* Background color, 0..255
*/
private int mBackColor;
private int mDefaultBackColor;
/**
* Current TextStyle effect
*/
private int mEffect;
private boolean mbKeypadApplicationMode;
/** false == G0, true == G1 */
private boolean mAlternateCharSet;
private final static int CHAR_SET_UK = 0;
private final static int CHAR_SET_ASCII = 1;
private final static int CHAR_SET_SPECIAL_GRAPHICS = 2;
private final static int CHAR_SET_ALT_STANDARD = 3;
private final static int CHAR_SET_ALT_SPECIAL_GRAPICS = 4;
/** What is the current graphics character set. [0] == G0, [1] == G1 */
private int[] mCharSet = new int[2];
/** Derived from mAlternateCharSet and mCharSet.
* True if we're supposed to be drawing the special graphics.
*/
private boolean mUseAlternateCharSet;
/**
* Special graphics character set
*/
private static final char[] mSpecialGraphicsCharMap = new char[128];
static {
for (char i = 0; i < 128; ++i) {
mSpecialGraphicsCharMap[i] = i;
}
mSpecialGraphicsCharMap['_'] = ' '; // Blank
mSpecialGraphicsCharMap['b'] = 0x2409; // Tab
mSpecialGraphicsCharMap['c'] = 0x240C; // Form feed
mSpecialGraphicsCharMap['d'] = 0x240D; // Carriage return
mSpecialGraphicsCharMap['e'] = 0x240A; // Line feed
mSpecialGraphicsCharMap['h'] = 0x2424; // New line
mSpecialGraphicsCharMap['i'] = 0x240B; // Vertical tab/"lantern"
mSpecialGraphicsCharMap['}'] = 0x00A3; // Pound sterling symbol
mSpecialGraphicsCharMap['f'] = 0x00B0; // Degree symbol
mSpecialGraphicsCharMap['`'] = 0x2B25; // Diamond
mSpecialGraphicsCharMap['~'] = 0x2022; // Bullet point
mSpecialGraphicsCharMap['y'] = 0x2264; // Less-than-or-equals sign (<=)
mSpecialGraphicsCharMap['|'] = 0x2260; // Not equals sign (!=)
mSpecialGraphicsCharMap['z'] = 0x2265; // Greater-than-or-equals sign (>=)
mSpecialGraphicsCharMap['g'] = 0x00B1; // Plus-or-minus sign (+/-)
mSpecialGraphicsCharMap['{'] = 0x03C0; // Lowercase Greek letter pi
mSpecialGraphicsCharMap['.'] = 0x25BC; // Down arrow
mSpecialGraphicsCharMap[','] = 0x25C0; // Left arrow
mSpecialGraphicsCharMap['+'] = 0x25B6; // Right arrow
mSpecialGraphicsCharMap['-'] = 0x25B2; // Up arrow
mSpecialGraphicsCharMap['h'] = '#'; // Board of squares
mSpecialGraphicsCharMap['a'] = 0x2592; // Checkerboard
mSpecialGraphicsCharMap['0'] = 0x2588; // Solid block
mSpecialGraphicsCharMap['q'] = 0x2500; // Horizontal line (box drawing)
mSpecialGraphicsCharMap['x'] = 0x2502; // Vertical line (box drawing)
mSpecialGraphicsCharMap['m'] = 0x2514; // Lower left hand corner (box drawing)
mSpecialGraphicsCharMap['j'] = 0x2518; // Lower right hand corner (box drawing)
mSpecialGraphicsCharMap['l'] = 0x250C; // Upper left hand corner (box drawing)
mSpecialGraphicsCharMap['k'] = 0x2510; // Upper right hand corner (box drawing)
mSpecialGraphicsCharMap['w'] = 0x252C; // T pointing downwards (box drawing)
mSpecialGraphicsCharMap['u'] = 0x2524; // T pointing leftwards (box drawing)
mSpecialGraphicsCharMap['t'] = 0x251C; // T pointing rightwards (box drawing)
mSpecialGraphicsCharMap['v'] = 0x2534; // T pointing upwards (box drawing)
mSpecialGraphicsCharMap['n'] = 0x253C; // Large plus/lines crossing (box drawing)
mSpecialGraphicsCharMap['o'] = 0x23BA; // Horizontal scanline 1
mSpecialGraphicsCharMap['p'] = 0x23BB; // Horizontal scanline 3
mSpecialGraphicsCharMap['r'] = 0x23BC; // Horizontal scanline 7
mSpecialGraphicsCharMap['s'] = 0x23BD; // Horizontal scanline 9
}
/**
* Used for moving selection up along with the scrolling text
*/
private int mScrollCounter = 0;
/**
* UTF-8 support
*/
private static final int UNICODE_REPLACEMENT_CHAR = 0xfffd;
private boolean mDefaultUTF8Mode = false;
private boolean mUTF8Mode = false;
private boolean mUTF8EscapeUsed = false;
private int mUTF8ToFollow = 0;
private ByteBuffer mUTF8ByteBuffer;
private CharBuffer mInputCharBuffer;
private CharsetDecoder mUTF8Decoder;
private UpdateCallback mUTF8ModeNotify;
/** This is not accurate, but it makes the terminal more useful on
* small screens.
*/
private final static boolean DEFAULT_TO_AUTOWRAP_ENABLED = true;
/**
* Construct a terminal emulator that uses the supplied screen
*
* @param session the terminal session the emulator is attached to
* @param screen the screen to render characters into.
* @param columns the number of columns to emulate
* @param rows the number of rows to emulate
* @param scheme the default color scheme of this emulator
*/
public TerminalEmulator(TermSession session, TranscriptScreen screen, int columns, int rows, ColorScheme scheme) {
mSession = session;
mMainBuffer = screen;
mScreen = mMainBuffer;
mAltBuffer = new TranscriptScreen(columns, rows, rows, scheme);
mRows = rows;
mColumns = columns;
mTabStop = new boolean[mColumns];
setColorScheme(scheme);
mUTF8ByteBuffer = ByteBuffer.allocate(4);
mInputCharBuffer = CharBuffer.allocate(2);
mUTF8Decoder = Charset.forName("UTF-8").newDecoder();
mUTF8Decoder.onMalformedInput(CodingErrorAction.REPLACE);
mUTF8Decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
reset();
}
public TranscriptScreen getScreen() {
return mScreen;
}
public void updateSize(int columns, int rows) {
if (mRows == rows && mColumns == columns) {
return;
}
if (columns <= 0) {
throw new IllegalArgumentException("rows:" + columns);
}
if (rows <= 0) {
throw new IllegalArgumentException("rows:" + rows);
}
TranscriptScreen screen = mScreen;
TranscriptScreen altScreen;
if (screen != mMainBuffer) {
altScreen = mMainBuffer;
} else {
altScreen = mAltBuffer;
}
// Try to resize the screen without getting the transcript
int[] cursor = { mCursorCol, mCursorRow };
boolean fastResize = screen.fastResize(columns, rows, cursor);
GrowableIntArray cursorColor = null;
String charAtCursor = null;
GrowableIntArray colors = null;
String transcriptText = null;
if (!fastResize) {
/* Save the character at the cursor (if one exists) and store an
* ASCII ESC character at the cursor's location
* This is an epic hack that lets us restore the cursor later...
*/
cursorColor = new GrowableIntArray(1);
charAtCursor = screen.getSelectedText(cursorColor, mCursorCol, mCursorRow, mCursorCol, mCursorRow);
screen.set(mCursorCol, mCursorRow, 27, 0);
colors = new GrowableIntArray(1024);
transcriptText = screen.getTranscriptText(colors);
screen.resize(columns, rows, getStyle());
}
boolean altFastResize = true;
GrowableIntArray altColors = null;
String altTranscriptText = null;
if (altScreen != null) {
altFastResize = altScreen.fastResize(columns, rows, null);
if (!altFastResize) {
altColors = new GrowableIntArray(1024);
altTranscriptText = altScreen.getTranscriptText(altColors);
altScreen.resize(columns, rows, getStyle());
}
}
if (mRows != rows) {
mRows = rows;
mTopMargin = 0;
mBottomMargin = mRows;
}
if (mColumns != columns) {
int oldColumns = mColumns;
mColumns = columns;
boolean[] oldTabStop = mTabStop;
mTabStop = new boolean[mColumns];
int toTransfer = Math.min(oldColumns, columns);
System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer);
}
if (!altFastResize) {
boolean wasAboutToAutoWrap = mAboutToAutoWrap;
// Restore the contents of the inactive screen's buffer
mScreen = altScreen;
mCursorRow = 0;
mCursorCol = 0;
mAboutToAutoWrap = false;
int end = altTranscriptText.length()-1;
/* Unlike for the main transcript below, don't trim off trailing
* newlines -- the alternate transcript lacks a cursor marking, so
* we might introduce an unwanted vertical shift in the screen
* contents this way */
char c, cLow;
int colorOffset = 0;
for (int i = 0; i <= end; i++) {
c = altTranscriptText.charAt(i);
int style = altColors.at(i-colorOffset);
if (Character.isHighSurrogate(c)) {
cLow = altTranscriptText.charAt(++i);
emit(Character.toCodePoint(c, cLow), style);
++colorOffset;
} else if (c == '\n') {
setCursorCol(0);
doLinefeed();
} else {
emit(c, style);
}
}
mScreen = screen;
mAboutToAutoWrap = wasAboutToAutoWrap;
}
if (fastResize) {
// Only need to make sure the cursor is in the right spot
if (cursor[0] >= 0 && cursor[1] >= 0) {
mCursorCol = cursor[0];
mCursorRow = cursor[1];
} else {
// Cursor scrolled off screen, reset the cursor to top left
mCursorCol = 0;
mCursorRow = 0;
}
return;
}
mCursorRow = 0;
mCursorCol = 0;
mAboutToAutoWrap = false;
int newCursorRow = -1;
int newCursorCol = -1;
int newCursorTranscriptPos = -1;
int end = transcriptText.length()-1;
while ((end >= 0) && transcriptText.charAt(end) == '\n') {
end--;
}
char c, cLow;
int colorOffset = 0;
for(int i = 0; i <= end; i++) {
c = transcriptText.charAt(i);
int style = colors.at(i-colorOffset);
if (Character.isHighSurrogate(c)) {
cLow = transcriptText.charAt(++i);
emit(Character.toCodePoint(c, cLow), style);
++colorOffset;
} else if (c == '\n') {
setCursorCol(0);
doLinefeed();
} else if (c == 27) {
/* We marked the cursor location with ESC earlier, so this
is the place to restore the cursor to */
newCursorRow = mCursorRow;
newCursorCol = mCursorCol;
newCursorTranscriptPos = screen.getActiveRows();
if (charAtCursor != null && charAtCursor.length() > 0) {
// Emit the real character that was in this spot
int encodedCursorColor = cursorColor.at(0);
emit(charAtCursor.toCharArray(), 0, charAtCursor.length(), encodedCursorColor);
}
} else {
emit(c, style);
}
}
// If we marked a cursor location, move the cursor there now
if (newCursorRow != -1 && newCursorCol != -1) {
mCursorRow = newCursorRow;
mCursorCol = newCursorCol;
/* Adjust for any scrolling between the time we marked the cursor
location and now */
int scrollCount = screen.getActiveRows() - newCursorTranscriptPos;
if (scrollCount > 0 && scrollCount <= newCursorRow) {
mCursorRow -= scrollCount;
} else if (scrollCount > newCursorRow) {
// Cursor scrolled off screen -- reset to top left corner
mCursorRow = 0;
mCursorCol = 0;
}
}
}
/**
* Get the cursor's current row.
*
* @return the cursor's current row.
*/
public final int getCursorRow() {
return mCursorRow;
}
/**
* Get the cursor's current column.
*
* @return the cursor's current column.
*/
public final int getCursorCol() {
return mCursorCol;
}
public final boolean getReverseVideo() {
return (mDecFlags & K_REVERSE_VIDEO_MASK) != 0;
}
public final boolean getShowCursor() {
return (mDecFlags & K_SHOW_CURSOR_MASK) != 0;
}
public final boolean getKeypadApplicationMode() {
return mbKeypadApplicationMode;
}
/**
* Get the current DECSET mouse tracking mode, zero for no mouse tracking.
*
* @return the current DECSET mouse tracking mode.
*/
public final int getMouseTrackingMode() {
return mMouseTrackingMode;
}
private void setDefaultTabStops() {
for (int i = 0; i < mColumns; i++) {
mTabStop[i] = (i & 7) == 0 && i != 0;
}
}
/**
* Accept bytes (typically from the pseudo-teletype) and process them.
*
* @param buffer a byte array containing the bytes to be processed
* @param base the first index of the array to process
* @param length the number of bytes in the array to process
*/
public void append(byte[] buffer, int base, int length) {
if (EmulatorDebug.LOG_CHARACTERS_FLAG) {
Log.d(EmulatorDebug.LOG_TAG, "In: '" + EmulatorDebug.bytesToString(buffer, base, length) + "'");
}
for (int i = 0; i < length; i++) {
byte b = buffer[base + i];
try {
process(b);
mProcessedCharCount++;
} catch (Exception e) {
Log.e(EmulatorDebug.LOG_TAG, "Exception while processing character "
+ Integer.toString(mProcessedCharCount) + " code "
+ Integer.toString(b), e);
}
}
}
private void process(byte b) {
process(b, true);
}
private void process(byte b, boolean doUTF8) {
// Let the UTF-8 decoder try to handle it if we're in UTF-8 mode
if (doUTF8 && mUTF8Mode && handleUTF8Sequence(b)) {
return;
}
// Handle C1 control characters
if ((b & 0x80) == 0x80 && (b & 0x7f) <= 0x1f) {
/* ESC ((code & 0x7f) + 0x40) is the two-byte escape sequence
corresponding to a particular C1 code */
process((byte) 27, false);
process((byte) ((b & 0x7f) + 0x40), false);
return;
}
switch (b) {
case 0: // NUL
// Do nothing
break;
case 7: // BEL
/* If in an OSC sequence, BEL may terminate a string; otherwise do
* nothing */
if (mEscapeState == ESC_RIGHT_SQUARE_BRACKET) {
doEscRightSquareBracket(b);
}
break;
case 8: // BS
setCursorCol(Math.max(0, mCursorCol - 1));
break;
case 9: // HT
// Move to next tab stop, but not past edge of screen
setCursorCol(nextTabStop(mCursorCol));
break;
case 13:
setCursorCol(0);
break;
case 10: // CR
case 11: // VT
case 12: // LF
doLinefeed();
break;
case 14: // SO:
setAltCharSet(true);
break;
case 15: // SI:
setAltCharSet(false);
break;
case 24: // CAN
case 26: // SUB
if (mEscapeState != ESC_NONE) {
mEscapeState = ESC_NONE;
emit((byte) 127);
}
break;
case 27: // ESC
// Starts an escape sequence unless we're parsing a string
if (mEscapeState != ESC_RIGHT_SQUARE_BRACKET) {
startEscapeSequence(ESC);
} else {
doEscRightSquareBracket(b);
}
break;
default:
mContinueSequence = false;
switch (mEscapeState) {
case ESC_NONE:
if (b >= 32) {
emit(b);
}
break;
case ESC:
doEsc(b);
break;
case ESC_POUND:
doEscPound(b);
break;
case ESC_SELECT_LEFT_PAREN:
doEscSelectLeftParen(b);
break;
case ESC_SELECT_RIGHT_PAREN:
doEscSelectRightParen(b);
break;
case ESC_LEFT_SQUARE_BRACKET:
doEscLeftSquareBracket(b); // CSI
break;
case ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK:
doEscLSBQuest(b); // CSI ?
break;
case ESC_PERCENT:
doEscPercent(b);
break;
case ESC_RIGHT_SQUARE_BRACKET:
doEscRightSquareBracket(b);
break;
case ESC_RIGHT_SQUARE_BRACKET_ESC:
doEscRightSquareBracketEsc(b);
break;
default:
unknownSequence(b);
break;
}
if (!mContinueSequence) {
mEscapeState = ESC_NONE;
}
break;
}
}
private boolean handleUTF8Sequence(byte b) {
if (mUTF8ToFollow == 0 && (b & 0x80) == 0) {
// ASCII character -- we don't need to handle this
return false;
}
if (mUTF8ToFollow > 0) {
if ((b & 0xc0) != 0x80) {
/* Not a UTF-8 continuation byte (doesn't begin with 0b10)
Replace the entire sequence with the replacement char */
mUTF8ToFollow = 0;
mUTF8ByteBuffer.clear();
emit(UNICODE_REPLACEMENT_CHAR);
/* The Unicode standard (section 3.9, definition D93) requires
* that we now attempt to process this byte as though it were
* the beginning of another possibly-valid sequence */
return handleUTF8Sequence(b);
}
mUTF8ByteBuffer.put(b);
if (--mUTF8ToFollow == 0) {
// Sequence complete -- decode and emit it
ByteBuffer byteBuf = mUTF8ByteBuffer;
CharBuffer charBuf = mInputCharBuffer;
CharsetDecoder decoder = mUTF8Decoder;
byteBuf.rewind();
decoder.reset();
decoder.decode(byteBuf, charBuf, true);
decoder.flush(charBuf);
char[] chars = charBuf.array();
if (chars[0] >= 0x80 && chars[0] <= 0x9f) {
/* Sequence decoded to a C1 control character which needs
to be sent through process() again */
process((byte) chars[0], false);
} else {
emit(chars);
}
byteBuf.clear();
charBuf.clear();
}
} else {
if ((b & 0xe0) == 0xc0) { // 0b110 -- two-byte sequence
mUTF8ToFollow = 1;
} else if ((b & 0xf0) == 0xe0) { // 0b1110 -- three-byte sequence
mUTF8ToFollow = 2;
} else if ((b & 0xf8) == 0xf0) { // 0b11110 -- four-byte sequence
mUTF8ToFollow = 3;
} else {
// Not a valid UTF-8 sequence start -- replace this char
emit(UNICODE_REPLACEMENT_CHAR);
return true;
}
mUTF8ByteBuffer.put(b);
}
return true;
}
private void setAltCharSet(boolean alternateCharSet) {
mAlternateCharSet = alternateCharSet;
computeEffectiveCharSet();
}
private void computeEffectiveCharSet() {
int charSet = mCharSet[mAlternateCharSet ? 1 : 0];
mUseAlternateCharSet = charSet == CHAR_SET_SPECIAL_GRAPHICS;
}
private int nextTabStop(int cursorCol) {
for (int i = cursorCol + 1; i < mColumns; i++) {
if (mTabStop[i]) {
return i;
}
}
return mColumns - 1;
}
private int prevTabStop(int cursorCol) {
for (int i = cursorCol - 1; i >= 0; i--) {
if (mTabStop[i]) {
return i;
}
}
return 0;
}
private void doEscPercent(byte b) {
switch (b) {
case '@': // Esc % @ -- return to ISO 2022 mode
setUTF8Mode(false);
mUTF8EscapeUsed = true;
break;
case 'G': // Esc % G -- UTF-8 mode
setUTF8Mode(true);
mUTF8EscapeUsed = true;
break;
default: // unimplemented character set
break;
}
}
private void doEscLSBQuest(byte b) {
int arg = getArg0(0);
int mask = getDecFlagsMask(arg);
int oldFlags = mDecFlags;
switch (b) {
case 'h': // Esc [ ? Pn h - DECSET
mDecFlags |= mask;
switch (arg) {
case 1:
mKeyListener.setCursorKeysApplicationMode(true);
break;
case 47:
case 1047:
case 1049:
if (mAltBuffer != null) {
mScreen = mAltBuffer;
}
break;
}
if (arg >= 1000 && arg <= 1003) {
mMouseTrackingMode = arg;
}
break;
case 'l': // Esc [ ? Pn l - DECRST
mDecFlags &= ~mask;
switch (arg) {
case 1:
mKeyListener.setCursorKeysApplicationMode(false);
break;
case 47:
case 1047:
case 1049:
mScreen = mMainBuffer;
break;
}
if (arg >= 1000 && arg <= 1003) {
mMouseTrackingMode = 0;
}
break;
case 'r': // Esc [ ? Pn r - restore
mDecFlags = (mDecFlags & ~mask) | (mSavedDecFlags & mask);
break;
case 's': // Esc [ ? Pn s - save
mSavedDecFlags = (mSavedDecFlags & ~mask) | (mDecFlags & mask);
break;
default:
parseArg(b);
break;
}
int newlySetFlags = (~oldFlags) & mDecFlags;
int changedFlags = oldFlags ^ mDecFlags;
// 132 column mode
if ((changedFlags & K_132_COLUMN_MODE_MASK) != 0) {
// We don't actually set/reset 132 cols, but we do want the
// side effect of clearing the screen and homing the cursor.
blockClear(0, 0, mColumns, mRows);
setCursorRowCol(0, 0);
}
// origin mode
if ((newlySetFlags & K_ORIGIN_MODE_MASK) != 0) {
// Home the cursor.
setCursorPosition(0, 0);
}
}
private int getDecFlagsMask(int argument) {
if (argument >= 1 && argument <= 32) {
return (1 << argument);
}
return 0;
}
private void startEscapeSequence(int escapeState) {
mEscapeState = escapeState;
mArgIndex = 0;
for (int j = 0; j < MAX_ESCAPE_PARAMETERS; j++) {
mArgs[j] = -1;
}
}
private void doLinefeed() {
int newCursorRow = mCursorRow + 1;
if (newCursorRow >= mBottomMargin) {
scroll();
newCursorRow = mBottomMargin - 1;
}
setCursorRow(newCursorRow);
}
private void continueSequence() {
mContinueSequence = true;
}
private void continueSequence(int state) {
mEscapeState = state;
mContinueSequence = true;
}
private void doEscSelectLeftParen(byte b) {
doSelectCharSet(0, b);
}
private void doEscSelectRightParen(byte b) {
doSelectCharSet(1, b);
}
private void doSelectCharSet(int charSetIndex, byte b) {
int charSet;
switch (b) {
case 'A': // United Kingdom character set
charSet = CHAR_SET_UK;
break;
case 'B': // ASCII set
charSet = CHAR_SET_ASCII;
break;
case '0': // Special Graphics
charSet = CHAR_SET_SPECIAL_GRAPHICS;
break;
case '1': // Alternate character set
charSet = CHAR_SET_ALT_STANDARD;
break;
case '2':
charSet = CHAR_SET_ALT_SPECIAL_GRAPICS;
break;
default:
unknownSequence(b);
return;
}
mCharSet[charSetIndex] = charSet;
computeEffectiveCharSet();
}
private void doEscPound(byte b) {
switch (b) {
case '8': // Esc # 8 - DECALN alignment test
mScreen.blockSet(0, 0, mColumns, mRows, 'E',
getStyle());
break;
default:
unknownSequence(b);
break;
}
}
private void doEsc(byte b) {
switch (b) {
case '#':
continueSequence(ESC_POUND);
break;
case '(':
continueSequence(ESC_SELECT_LEFT_PAREN);
break;
case ')':
continueSequence(ESC_SELECT_RIGHT_PAREN);
break;
case '7': // DECSC save cursor
mSavedCursorRow = mCursorRow;
mSavedCursorCol = mCursorCol;
mSavedEffect = mEffect;
mSavedDecFlags_DECSC_DECRC = mDecFlags & K_DECSC_DECRC_MASK;
break;
case '8': // DECRC restore cursor
setCursorRowCol(mSavedCursorRow, mSavedCursorCol);
mEffect = mSavedEffect;
mDecFlags = (mDecFlags & ~ K_DECSC_DECRC_MASK)
| mSavedDecFlags_DECSC_DECRC;
break;
case 'D': // INDEX
doLinefeed();
break;
case 'E': // NEL
setCursorCol(0);
doLinefeed();
break;
case 'F': // Cursor to lower-left corner of screen
setCursorRowCol(0, mBottomMargin - 1);
break;
case 'H': // Tab set
mTabStop[mCursorCol] = true;
break;
case 'M': // Reverse index
if (mCursorRow <= mTopMargin) {
mScreen.blockCopy(0, mTopMargin, mColumns, mBottomMargin
- (mTopMargin + 1), 0, mTopMargin + 1);
blockClear(0, mTopMargin, mColumns);
} else {
mCursorRow--;
}
break;
case 'N': // SS2
unimplementedSequence(b);
break;
case '0': // SS3
unimplementedSequence(b);
break;
case 'P': // Device control string
unimplementedSequence(b);
break;
case 'Z': // return terminal ID
sendDeviceAttributes();
break;
case '[':
continueSequence(ESC_LEFT_SQUARE_BRACKET);
break;
case '=': // DECKPAM
mbKeypadApplicationMode = true;
break;
case ']': // OSC
startCollectingOSCArgs();
continueSequence(ESC_RIGHT_SQUARE_BRACKET);
break;
case '>' : // DECKPNM
mbKeypadApplicationMode = false;
break;
default:
unknownSequence(b);
break;
}
}
private void doEscLeftSquareBracket(byte b) {
// CSI
switch (b) {
case '@': // ESC [ Pn @ - ICH Insert Characters
{
int charsAfterCursor = mColumns - mCursorCol;
int charsToInsert = Math.min(getArg0(1), charsAfterCursor);
int charsToMove = charsAfterCursor - charsToInsert;
mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1,
mCursorCol + charsToInsert, mCursorRow);
blockClear(mCursorCol, mCursorRow, charsToInsert);
}
break;
case 'A': // ESC [ Pn A - Cursor Up
setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1)));
break;
case 'B': // ESC [ Pn B - Cursor Down
setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1)));
break;
case 'C': // ESC [ Pn C - Cursor Right
setCursorCol(Math.min(mColumns - 1, mCursorCol + getArg0(1)));
break;
case 'D': // ESC [ Pn D - Cursor Left
setCursorCol(Math.max(0, mCursorCol - getArg0(1)));
break;
case 'G': // ESC [ Pn G - Cursor Horizontal Absolute
setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1);
break;
case 'H': // ESC [ Pn ; H - Cursor Position
setHorizontalVerticalPosition();
break;
case 'J': // ESC [ Pn J - ED - Erase in Display
// ED ignores the scrolling margins.
switch (getArg0(0)) {
case 0: // Clear below
blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
blockClear(0, mCursorRow + 1, mColumns,
mRows - (mCursorRow + 1));
break;
case 1: // Erase from the start of the screen to the cursor.
blockClear(0, 0, mColumns, mCursorRow);
blockClear(0, mCursorRow, mCursorCol + 1);
break;
case 2: // Clear all
blockClear(0, 0, mColumns, mRows);
break;
default:
unknownSequence(b);
break;
}
break;
case 'K': // ESC [ Pn K - Erase in Line
switch (getArg0(0)) {
case 0: // Clear to right
blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
break;
case 1: // Erase start of line to cursor (including cursor)
blockClear(0, mCursorRow, mCursorCol + 1);
break;
case 2: // Clear whole line
blockClear(0, mCursorRow, mColumns);
break;
default:
unknownSequence(b);
break;
}
break;
case 'L': // Insert Lines
{
int linesAfterCursor = mBottomMargin - mCursorRow;
int linesToInsert = Math.min(getArg0(1), linesAfterCursor);
int linesToMove = linesAfterCursor - linesToInsert;
mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0,
mCursorRow + linesToInsert);
blockClear(0, mCursorRow, mColumns, linesToInsert);
}
break;
case 'M': // Delete Lines
{
int linesAfterCursor = mBottomMargin - mCursorRow;
int linesToDelete = Math.min(getArg0(1), linesAfterCursor);
int linesToMove = linesAfterCursor - linesToDelete;
mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns,
linesToMove, 0, mCursorRow);
blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete);
}
break;
case 'P': // Delete Characters
{
int charsAfterCursor = mColumns - mCursorCol;
int charsToDelete = Math.min(getArg0(1), charsAfterCursor);
int charsToMove = charsAfterCursor - charsToDelete;
mScreen.blockCopy(mCursorCol + charsToDelete, mCursorRow,
charsToMove, 1, mCursorCol, mCursorRow);
blockClear(mCursorCol + charsToMove, mCursorRow, charsToDelete);
}
break;
case 'T': // Mouse tracking
unimplementedSequence(b);
break;
case 'X': // Erase characters
blockClear(mCursorCol, mCursorRow, getArg0(0));
break;
case 'Z': // Back tab
setCursorCol(prevTabStop(mCursorCol));
break;
case '?': // Esc [ ? -- start of a private mode set
continueSequence(ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK);
break;
case 'c': // Send device attributes
sendDeviceAttributes();
break;
case 'd': // ESC [ Pn d - Vert Position Absolute
setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1);
break;
case 'f': // Horizontal and Vertical Position
setHorizontalVerticalPosition();
break;
case 'g': // Clear tab stop
switch (getArg0(0)) {
case 0:
mTabStop[mCursorCol] = false;
break;
case 3:
for (int i = 0; i < mColumns; i++) {
mTabStop[i] = false;
}
break;
default:
// Specified to have no effect.
break;
}
break;
case 'h': // Set Mode
doSetMode(true);
break;
case 'l': // Reset Mode
doSetMode(false);
break;
case 'm': // Esc [ Pn m - character attributes.
// (can have up to 16 numerical arguments)
selectGraphicRendition();
break;
case 'n': // Esc [ Pn n - ECMA-48 Status Report Commands
//sendDeviceAttributes()
switch (getArg0(0)) {
case 5: // Device status report (DSR):
// Answer is ESC [ 0 n (Terminal OK).
byte[] dsr = { (byte) 27, (byte) '[', (byte) '0', (byte) 'n' };
mSession.write(dsr, 0, dsr.length);
break;
case 6: // Cursor position report (CPR):
// Answer is ESC [ y ; x R, where x,y is
// the cursor location.
byte[] cpr = String.format(Locale.US, "\033[%d;%dR",
mCursorRow + 1, mCursorCol + 1).getBytes();
mSession.write(cpr, 0, cpr.length);
break;
default:
break;
}
break;
case 'r': // Esc [ Pn ; Pn r - set top and bottom margins
{
// The top margin defaults to 1, the bottom margin
// (unusually for arguments) defaults to mRows.
//
// The escape sequence numbers top 1..23, but we
// number top 0..22.
// The escape sequence numbers bottom 2..24, and
// so do we (because we use a zero based numbering
// scheme, but we store the first line below the
// bottom-most scrolling line.
// As a result, we adjust the top line by -1, but
// we leave the bottom line alone.
//
// Also require that top + 2 <= bottom
int top = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
int bottom = Math.max(top + 2, Math.min(getArg1(mRows), mRows));
mTopMargin = top;
mBottomMargin = bottom;
// The cursor is placed in the home position
setCursorRowCol(mTopMargin, 0);
}
break;
default:
parseArg(b);
break;
}
}
private void selectGraphicRendition() {
// SGR
for (int i = 0; i <= mArgIndex; i++) {
int code = mArgs[i];
if ( code < 0) {
if (mArgIndex > 0) {
continue;
} else {
code = 0;
}
}
// See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
if (code == 0) { // reset
mForeColor = mDefaultForeColor;
mBackColor = mDefaultBackColor;
mEffect = TextStyle.fxNormal;
} else if (code == 1) { // bold
mEffect |= TextStyle.fxBold;
} else if (code == 3) { // italics, but rarely used as such; "standout" (inverse colors) with TERM=screen
mEffect |= TextStyle.fxItalic;
} else if (code == 4) { // underscore
mEffect |= TextStyle.fxUnderline;
} else if (code == 5) { // blink
mEffect |= TextStyle.fxBlink;
} else if (code == 7) { // inverse
mEffect |= TextStyle.fxInverse;
} else if (code == 8) { // invisible
mEffect |= TextStyle.fxInvisible;
} else if (code == 10) { // exit alt charset (TERM=linux)
setAltCharSet(false);
} else if (code == 11) { // enter alt charset (TERM=linux)
setAltCharSet(true);
} else if (code == 22) { // Normal color or intensity, neither bright, bold nor faint
//mEffect &= ~(TextStyle.fxBold | TextStyle.fxFaint);
mEffect &= ~TextStyle.fxBold;
} else if (code == 23) { // not italic, but rarely used as such; clears standout with TERM=screen
mEffect &= ~TextStyle.fxItalic;
} else if (code == 24) { // underline: none
mEffect &= ~TextStyle.fxUnderline;
} else if (code == 25) { // blink: none
mEffect &= ~TextStyle.fxBlink;
} else if (code == 27) { // image: positive
mEffect &= ~TextStyle.fxInverse;
} else if (code == 28) { // invisible
mEffect &= ~TextStyle.fxInvisible;
} else if (code >= 30 && code <= 37) { // foreground color
mForeColor = code - 30;
} else if (code == 38 && i+2 <= mArgIndex && mArgs[i+1] == 5) { // foreground 256 color
int color = mArgs[i+2];
if (checkColor(color)) {
mForeColor = color;
}
i += 2;
} else if (code == 39) { // set default text color
mForeColor = mDefaultForeColor;
} else if (code >= 40 && code <= 47) { // background color
mBackColor = code - 40;
} else if (code == 48 && i+2 <= mArgIndex && mArgs[i+1] == 5) { // background 256 color
mBackColor = mArgs[i+2];
int color = mArgs[i+2];
if (checkColor(color)) {
mBackColor = color;
}
i += 2;
} else if (code == 49) { // set default background color
mBackColor = mDefaultBackColor;
} else if (code >= 90 && code <= 97) { // bright foreground color
mForeColor = code - 90 + 8;
} else if (code >= 100 && code <= 107) { // bright background color
mBackColor = code - 100 + 8;
} else {
if (EmulatorDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
Log.w(EmulatorDebug.LOG_TAG, String.format("SGR unknown code %d", code));
}
}
}
}
private boolean checkColor(int color) {
boolean result = isValidColor(color);
if (!result) {
if (EmulatorDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
Log.w(EmulatorDebug.LOG_TAG,
String.format("Invalid color %d", color));
}
}
return result;
}
private boolean isValidColor(int color) {
return color >= 0 && color < TextStyle.ciColorLength;
}
private void doEscRightSquareBracket(byte b) {
switch (b) {
case 0x7:
doOSC();
break;
case 0x1b: // Esc, probably start of Esc \ sequence
continueSequence(ESC_RIGHT_SQUARE_BRACKET_ESC);
break;
default:
collectOSCArgs(b);
break;
}
}
private void doEscRightSquareBracketEsc(byte b) {
switch (b) {
case '\\':
doOSC();
break;
default:
// The ESC character was not followed by a \, so insert the ESC and
// the current character in arg buffer.
collectOSCArgs((byte) 0x1b);
collectOSCArgs(b);
continueSequence(ESC_RIGHT_SQUARE_BRACKET);
break;
}
}
private void doOSC() { // Operating System Controls
startTokenizingOSC();
int ps = nextOSCInt(';');
switch (ps) {
case 0: // Change icon name and window title to T
case 1: // Change icon name to T
case 2: // Change window title to T
changeTitle(ps, nextOSCString(-1));
break;
default:
unknownParameter(ps);
break;
}
finishSequence();
}
private void changeTitle(int parameter, String title) {
if (parameter == 0 || parameter == 2) {
mSession.setTitle(title);
}
}
private void blockClear(int sx, int sy, int w) {
blockClear(sx, sy, w, 1);
}
private void blockClear(int sx, int sy, int w, int h) {
mScreen.blockSet(sx, sy, w, h, ' ', getStyle());
}
private int getForeColor() {
return mForeColor;
}
private int getBackColor() {
return mBackColor;
}
private int getEffect() {
return mEffect;
}
private int getStyle() {
return TextStyle.encode(getForeColor(), getBackColor(), getEffect());
}
private void doSetMode(boolean newValue) {
int modeBit = getArg0(0);
switch (modeBit) {
case 4:
mInsertMode = newValue;
break;
default:
unknownParameter(modeBit);
break;
}
}
private void setHorizontalVerticalPosition() {
// Parameters are Row ; Column
setCursorPosition(getArg1(1) - 1, getArg0(1) - 1);
}
private void setCursorPosition(int x, int y) {
int effectiveTopMargin = 0;
int effectiveBottomMargin = mRows;
if ((mDecFlags & K_ORIGIN_MODE_MASK) != 0) {
effectiveTopMargin = mTopMargin;
effectiveBottomMargin = mBottomMargin;
}
int newRow =
Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y,
effectiveBottomMargin - 1));
int newCol = Math.max(0, Math.min(x, mColumns - 1));
setCursorRowCol(newRow, newCol);
}
private void sendDeviceAttributes() {
// This identifies us as a DEC vt100 with advanced
// video options. This is what the xterm terminal
// emulator sends.
byte[] attributes =
{
/* VT100 */
(byte) 27, (byte) '[', (byte) '?', (byte) '1',
(byte) ';', (byte) '2', (byte) 'c'
/* VT220
(byte) 27, (byte) '[', (byte) '?', (byte) '6',
(byte) '0', (byte) ';',
(byte) '1', (byte) ';',
(byte) '2', (byte) ';',
(byte) '6', (byte) ';',
(byte) '8', (byte) ';',
(byte) '9', (byte) ';',
(byte) '1', (byte) '5', (byte) ';',
(byte) 'c'
*/
};
mSession.write(attributes, 0, attributes.length);
}
private void scroll() {
//System.out.println("Scroll(): mTopMargin " + mTopMargin + " mBottomMargin " + mBottomMargin);
mScrollCounter ++;
mScreen.scroll(mTopMargin, mBottomMargin, getStyle());
}
/**
* Process the next ASCII character of a parameter.
*
* @param b The next ASCII character of the paramater sequence.
*/
private void parseArg(byte b) {
if (b >= '0' && b <= '9') {
if (mArgIndex < mArgs.length) {
int oldValue = mArgs[mArgIndex];
int thisDigit = b - '0';
int value;
if (oldValue >= 0) {
value = oldValue * 10 + thisDigit;
} else {
value = thisDigit;
}
mArgs[mArgIndex] = value;
}
continueSequence();
} else if (b == ';') {
if (mArgIndex < mArgs.length) {
mArgIndex++;
}
continueSequence();
} else {
unknownSequence(b);
}
}
private int getArg0(int defaultValue) {
return getArg(0, defaultValue, true);
}
private int getArg1(int defaultValue) {
return getArg(1, defaultValue, true);
}
private int getArg(int index, int defaultValue,
boolean treatZeroAsDefault) {
int result = mArgs[index];
if (result < 0 || (result == 0 && treatZeroAsDefault)) {
result = defaultValue;
}
return result;
}
private void startCollectingOSCArgs() {
mOSCArgLength = 0;
}
private void collectOSCArgs(byte b) {
if (mOSCArgLength < MAX_OSC_STRING_LENGTH) {
mOSCArg[mOSCArgLength++] = b;
continueSequence();
} else {
unknownSequence(b);
}
}
private void startTokenizingOSC() {
mOSCArgTokenizerIndex = 0;
}
private String nextOSCString(int delimiter) {
int start = mOSCArgTokenizerIndex;
int end = start;
while (mOSCArgTokenizerIndex < mOSCArgLength) {
byte b = mOSCArg[mOSCArgTokenizerIndex++];
if ((int) b == delimiter) {
break;
}
end++;
}
if (start == end) {
return "";
}
try {
return new String(mOSCArg, start, end-start, "UTF-8");
} catch (UnsupportedEncodingException e) {
return new String(mOSCArg, start, end-start);
}
}
private int nextOSCInt(int delimiter) {
int value = -1;
while (mOSCArgTokenizerIndex < mOSCArgLength) {
byte b = mOSCArg[mOSCArgTokenizerIndex++];
if ((int) b == delimiter) {
break;
} else if (b >= '0' && b <= '9') {
if (value < 0) {
value = 0;
}
value = value * 10 + b - '0';
} else {
unknownSequence(b);
}
}
return value;
}
private void unimplementedSequence(byte b) {
if (EmulatorDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
logError("unimplemented", b);
}
finishSequence();
}
private void unknownSequence(byte b) {
if (EmulatorDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
logError("unknown", b);
}
finishSequence();
}
private void unknownParameter(int parameter) {
if (EmulatorDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
StringBuilder buf = new StringBuilder();
buf.append("Unknown parameter");
buf.append(parameter);
logError(buf.toString());
}
}
private void logError(String errorType, byte b) {
if (EmulatorDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
StringBuilder buf = new StringBuilder();
buf.append(errorType);
buf.append(" sequence ");
buf.append(" EscapeState: ");
buf.append(mEscapeState);
buf.append(" char: '");
buf.append((char) b);
buf.append("' (");
buf.append(b);
buf.append(")");
boolean firstArg = true;
for (int i = 0; i <= mArgIndex; i++) {
int value = mArgs[i];
if (value >= 0) {
if (firstArg) {
firstArg = false;
buf.append("args = ");
}
buf.append(String.format("%d; ", value));
}
}
logError(buf.toString());
}
}
private void logError(String error) {
if (EmulatorDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
Log.e(EmulatorDebug.LOG_TAG, error);
}
finishSequence();
}
private void finishSequence() {
mEscapeState = ESC_NONE;
}
private boolean autoWrapEnabled() {
return (mDecFlags & K_WRAPAROUND_MODE_MASK) != 0;
}
/**
* Send a Unicode code point to the screen.
*
* @param c The code point of the character to display
* @param foreColor The foreground color of the character
* @param backColor The background color of the character
*/
private void emit(int c, int style) {
boolean autoWrap = autoWrapEnabled();
int width = UnicodeTranscript.charWidth(c);
if (autoWrap) {
if (mCursorCol == mColumns - 1 && (mAboutToAutoWrap || width == 2)) {
mScreen.setLineWrap(mCursorRow);
mCursorCol = 0;
mJustWrapped = true;
if (mCursorRow + 1 < mBottomMargin) {
mCursorRow++;
} else {
scroll();
}
}
}
if (mInsertMode & width != 0) { // Move character to right one space
int destCol = mCursorCol + width;
if (destCol < mColumns) {
mScreen.blockCopy(mCursorCol, mCursorRow, mColumns - destCol,
1, destCol, mCursorRow);
}
}
if (width == 0) {
// Combining character -- store along with character it modifies
if (mJustWrapped) {
mScreen.set(mColumns - mLastEmittedCharWidth, mCursorRow - 1, c, style);
} else {
mScreen.set(mCursorCol - mLastEmittedCharWidth, mCursorRow, c, style);
}
} else {
mScreen.set(mCursorCol, mCursorRow, c, style);
mJustWrapped = false;
}
if (autoWrap) {
mAboutToAutoWrap = (mCursorCol == mColumns - 1);
//Force line-wrap flag to trigger even for lines being typed
if(mAboutToAutoWrap)
mScreen.setLineWrap(mCursorRow);
}
mCursorCol = Math.min(mCursorCol + width, mColumns - 1);
if (width > 0) {
mLastEmittedCharWidth = width;
}
}
private void emit(int c) {
emit(c, getStyle());
}
private void emit(byte b) {
if (mUseAlternateCharSet && b < 128) {
emit((int) mSpecialGraphicsCharMap[b]);
} else {
emit((int) b);
}
}
/**
* Send a UTF-16 char or surrogate pair to the screen.
*
* @param c A char[2] containing either a single UTF-16 char or a surrogate pair to be sent to the screen.
*/
private void emit(char[] c) {
if (Character.isHighSurrogate(c[0])) {
emit(Character.toCodePoint(c[0], c[1]));
} else {
emit((int) c[0]);
}
}
/**
* Send an array of UTF-16 chars to the screen.
*
* @param c A char[] array whose contents are to be sent to the screen.
*/
private void emit(char[] c, int offset, int length, int style) {
for (int i = offset; i < length; ++i) {
if (c[i] == 0) {
break;
}
if (Character.isHighSurrogate(c[i])) {
emit(Character.toCodePoint(c[i], c[i+1]), style);
++i;
} else {
emit((int) c[i], style);
}
}
}
private void setCursorRow(int row) {
mCursorRow = row;
mAboutToAutoWrap = false;
}
private void setCursorCol(int col) {
mCursorCol = col;
mAboutToAutoWrap = false;
}
private void setCursorRowCol(int row, int col) {
mCursorRow = Math.min(row, mRows-1);
mCursorCol = Math.min(col, mColumns-1);
mAboutToAutoWrap = false;
}
public int getScrollCounter() {
return mScrollCounter;
}
public void clearScrollCounter() {
mScrollCounter = 0;
}
/**
* Reset the terminal emulator to its initial state.
*/
public void reset() {
mCursorRow = 0;
mCursorCol = 0;
mArgIndex = 0;
mContinueSequence = false;
mEscapeState = ESC_NONE;
mSavedCursorRow = 0;
mSavedCursorCol = 0;
mSavedEffect = 0;
mSavedDecFlags_DECSC_DECRC = 0;
mDecFlags = 0;
if (DEFAULT_TO_AUTOWRAP_ENABLED) {
mDecFlags |= K_WRAPAROUND_MODE_MASK;
}
mDecFlags |= K_SHOW_CURSOR_MASK;
mSavedDecFlags = 0;
mInsertMode = false;
mTopMargin = 0;
mBottomMargin = mRows;
mAboutToAutoWrap = false;
mForeColor = mDefaultForeColor;
mBackColor = mDefaultBackColor;
mbKeypadApplicationMode = false;
mAlternateCharSet = false;
mCharSet[0] = CHAR_SET_ASCII;
mCharSet[1] = CHAR_SET_SPECIAL_GRAPHICS;
computeEffectiveCharSet();
// mProcessedCharCount is preserved unchanged.
setDefaultTabStops();
blockClear(0, 0, mColumns, mRows);
setUTF8Mode(mDefaultUTF8Mode);
mUTF8EscapeUsed = false;
mUTF8ToFollow = 0;
mUTF8ByteBuffer.clear();
mInputCharBuffer.clear();
}
public void setDefaultUTF8Mode(boolean defaultToUTF8Mode) {
mDefaultUTF8Mode = defaultToUTF8Mode;
if (!mUTF8EscapeUsed) {
setUTF8Mode(defaultToUTF8Mode);
}
}
public void setUTF8Mode(boolean utf8Mode) {
if (utf8Mode && !mUTF8Mode) {
mUTF8ToFollow = 0;
mUTF8ByteBuffer.clear();
mInputCharBuffer.clear();
}
mUTF8Mode = utf8Mode;
if (mUTF8ModeNotify != null) {
mUTF8ModeNotify.onUpdate();
}
}
public boolean getUTF8Mode() {
return mUTF8Mode;
}
public void setUTF8ModeUpdateCallback(UpdateCallback utf8ModeNotify) {
mUTF8ModeNotify = utf8ModeNotify;
}
public void setColorScheme(ColorScheme scheme) {
mDefaultForeColor = TextStyle.ciForeground;
mDefaultBackColor = TextStyle.ciBackground;
mMainBuffer.setColorScheme(scheme);
if (mAltBuffer != null) {
mAltBuffer.setColorScheme(scheme);
}
}
public String getSelectedText(int x1, int y1, int x2, int y2) {
return mScreen.getSelectedText(x1, y1, x2, y2);
}
public void finish() {
if (mAltBuffer != null) {
mAltBuffer.finish();
mAltBuffer = null;
}
}
}