/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java
Java | 1572 lines | 1050 code | 228 blank | 294 comment | 209 complexity | a9173e7e1a66ee2a714a253073c9272c MD5 | raw file
- /*
- * 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.nio.ByteBuffer;
- import java.nio.CharBuffer;
- import java.nio.charset.Charset;
- import java.nio.charset.CharsetDecoder;
- import java.nio.charset.CodingErrorAction;
- import android.util.Log;
- /**
- * Renders text into a screen. Contains all the terminal-specific knowlege 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 {
- /**
- * 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 Screen 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];
- // 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;
- /**
- * 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;
- // 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;
- /**
- * 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;
- /**
- * 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;
- // 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;
- /**
- * Automatic newline mode. Configures whether pressing return on the
- * keyboard automatically generates a return as well. Not currently
- * implemented.
- */
- private boolean mAutomaticNewlineMode;
- /**
- * 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..7, mask with 8 for bold
- */
- private int mForeColor;
- private int mDefaultForeColor;
- /**
- * Background color, 0..7, mask with 8 for underline
- */
- private int mBackColor;
- private int mDefaultBackColor;
- private boolean mInverseColors;
- private boolean mbKeypadApplicationMode;
- private boolean mAlternateCharSet;
- /**
- * 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;
- /**
- * 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, Screen screen, int columns, int rows, ColorScheme scheme) {
- mSession = session;
- mScreen = screen;
- 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 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);
- }
- // Try to resize the screen without getting the transcript
- int[] cursor = { mCursorCol, mCursorRow };
- boolean fastResize = mScreen.fastResize(columns, rows, cursor);
- StringBuilder cursorColor = null;
- String charAtCursor = null;
- StringBuilder 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 StringBuilder(1);
- charAtCursor = mScreen.getSelectedText(cursorColor, mCursorCol, mCursorRow, mCursorCol, mCursorRow);
- mScreen.set(mCursorCol, mCursorRow, 27, 0, 0);
- colors = new StringBuilder();
- transcriptText = mScreen.getTranscriptText(colors);
- mScreen.resize(columns, rows, mForeColor, mBackColor);
- }
- 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 (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 foreColor, backColor;
- int colorOffset = 0;
- for(int i = 0; i <= end; i++) {
- c = transcriptText.charAt(i);
- foreColor = (colors.charAt(i-colorOffset) >> 4) & 0xf;
- backColor = colors.charAt(i-colorOffset) & 0xf;
- if (Character.isHighSurrogate(c)) {
- cLow = transcriptText.charAt(++i);
- emit(Character.toCodePoint(c, cLow), foreColor, backColor);
- ++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 = mScreen.getActiveRows();
- if (charAtCursor != null && charAtCursor.length() > 0) {
- // Emit the real character that was in this spot
- foreColor = (cursorColor.charAt(0) >> 4) & 0xf;
- backColor = cursorColor.charAt(0) & 0xf;
- emit(charAtCursor.toCharArray(), 0, charAtCursor.length(), foreColor, backColor);
- }
- } else {
- emit(c, foreColor, backColor);
- }
- }
- // 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 = mScreen.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 getKeypadApplicationMode() {
- return mbKeypadApplicationMode;
- }
- 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) {
- for (int i = 0; i < length; i++) {
- byte b = buffer[base + i];
- try {
- if (EmulatorDebug.LOG_CHARACTERS_FLAG) {
- char printableB = (char) b;
- if (b < 32 || b > 126) {
- printableB = ' ';
- }
- Log.w(EmulatorDebug.LOG_TAG, "'" + Character.toString(printableB)
- + "' (" + Integer.toString(b) + ")");
- }
- 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 */
- startEscapeSequence(ESC);
- process((byte) ((b & 0x7f) + 0x40), false);
- return;
- }
- switch (b) {
- case 0: // NUL
- // Do nothing
- break;
- case 7: // BEL
- // Do nothing
- 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
- // Always starts an escape sequence
- startEscapeSequence(ESC);
- 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);
- break;
- case ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK:
- doEscLSBQuest(b);
- break;
- case ESC_PERCENT:
- doEscPercent(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);
- return true;
- }
- 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;
- }
- 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 mask = getDecFlagsMask(getArg0(0));
- switch (b) {
- case 'h': // Esc [ ? Pn h - DECSET
- mDecFlags |= mask;
- break;
- case 'l': // Esc [ ? Pn l - DECRST
- mDecFlags &= ~mask;
- 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;
- }
- // 132 column mode
- if ((mask & K_132_COLUMN_MODE_MASK) != 0) {
- // We don't actually set 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 ((mask & K_ORIGIN_MODE_MASK) != 0) {
- // Home the cursor.
- setCursorPosition(0, 0);
- }
- }
- private int getDecFlagsMask(int argument) {
- if (argument >= 1 && argument <= 9) {
- 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(true, b);
- }
- private void doEscSelectRightParen(byte b) {
- doSelectCharSet(false, b);
- }
- private void doSelectCharSet(boolean isG0CharSet, byte b) {
- switch (b) {
- case 'A': // United Kingdom character set
- break;
- case 'B': // ASCII set
- break;
- case '0': // Special Graphics
- break;
- case '1': // Alternate character set
- break;
- case '2':
- break;
- default:
- unknownSequence(b);
- }
- }
- private void doEscPound(byte b) {
- switch (b) {
- case '8': // Esc # 8 - DECALN alignment test
- mScreen.blockSet(0, 0, mColumns, mRows, 'E',
- getForeColor(), getBackColor());
- 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;
- break;
- case '8': // DECRC restore cursor
- setCursorRowCol(mSavedCursorRow, mSavedCursorCol);
- 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 '>' : // DECKPNM
- mbKeypadApplicationMode = false;
- break;
- default:
- unknownSequence(b);
- break;
- }
- }
- private void doEscLeftSquareBracket(byte b) {
- 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 - Erase in Display
- switch (getArg0(0)) {
- case 0: // Clear below
- blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
- blockClear(0, mCursorRow + 1, mColumns,
- mBottomMargin - (mCursorRow + 1));
- break;
- case 1: // Erase from the start of the screen to the cursor.
- blockClear(0, mTopMargin, mColumns, mCursorRow - mTopMargin);
- blockClear(0, mCursorRow, mCursorCol + 1);
- break;
- case 2: // Clear all
- blockClear(0, mTopMargin, mColumns, mBottomMargin - mTopMargin);
- 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.
- selectGraphicRendition();
- 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() {
- 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
- mInverseColors = false;
- mForeColor = mDefaultForeColor;
- mBackColor = mDefaultBackColor;
- } else if (code == 1) { // bold
- mForeColor |= 0x8;
- } else if (code == 3) { // italics, but rarely used as such; "standout" (inverse colors) with TERM=screen
- mInverseColors = true;
- } else if (code == 4) { // underscore
- mBackColor |= 0x8;
- } else if (code == 7) { // inverse
- mInverseColors = true;
- } 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
- mForeColor &= 0x7;
- } else if (code == 23) { // not italic, but rarely used as such; clears standout with TERM=screen
- mInverseColors = false;
- } else if (code == 24) { // underline: none
- mBackColor &= 0x7;
- } else if (code == 27) { // image: positive
- mInverseColors = false;
- } else if (code >= 30 && code <= 37) { // foreground color
- mForeColor = (mForeColor & 0x8) | (code - 30);
- } else if (code == 39) { // set default text color
- mForeColor = mDefaultForeColor | (mForeColor & 0x8); // preserve bold
- mBackColor = mBackColor & 0x7; // no underline
- } else if (code >= 40 && code <= 47) { // background color
- mBackColor = (mBackColor & 0x8) | (code - 40);
- } else if (code == 49) { // set default background color
- mBackColor = mDefaultBackColor | (mBackColor & 0x8); // preserve underscore.
- } else {
- if (EmulatorDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
- Log.w(EmulatorDebug.LOG_TAG, String.format("SGR unknown code %d", code));
- }
- }
- }
- }
- 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, ' ', getForeColor(), getBackColor());
- }
- private int getForeColor() {
- return mInverseColors ?
- ((mBackColor & 0x7) | (mForeColor & 0x8)) : mForeColor;
- }
- private int getBackColor() {
- return mInverseColors ?
- ((mForeColor & 0x7) | (mBackColor & 0x8)) : mBackColor;
- }
- private void doSetMode(boolean newValue) {
- int modeBit = getArg0(0);
- switch (modeBit) {
- case 4:
- mInsertMode = newValue;
- break;
- case 20:
- mAutomaticNewlineMode = 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);
- }
- /**
- * 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);
- }
- private int getArg1(int defaultValue) {
- return getArg(1, defaultValue);
- }
- private int getArg(int index, int defaultValue) {
- int result = mArgs[index];
- if (result < 0) {
- result = defaultValue;
- }
- return result;
- }
- 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() {
- // Always enable auto wrap, because it's useful on a small screen
- return true;
- // 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 foreColor, int backColor) {
- 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, foreColor, backColor);
- } else {
- mScreen.set(mCursorCol - mLastEmittedCharWidth, mCursorRow, c, foreColor, backColor);
- }
- } else {
- mScreen.set(mCursorCol, mCursorRow, c, foreColor, backColor);
- mJustWrapped = false;
- }
- if (autoWrap) {
- mAboutToAutoWrap = (mCursorCol == mColumns - 1);
- }
- mCursorCol = Math.min(mCursorCol + width, mColumns - 1);
- if (width > 0) {
- mLastEmittedCharWidth = width;
- }
- }
- private void emit(int c) {
- emit(c, getForeColor(), getBackColor());
- }
- private void emit(byte b) {
- if (mAlternateCharSet && 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 foreColor, int backColor) {
- 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]), foreColor, backColor);
- ++i;
- } else {
- emit((int) c[i], foreColor, backColor);
- }
- }
- }
- private void emit(char[] c, int offset, int length) {
- emit(c, offset, length, getForeColor(), getBackColor());
- }
- 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;
- mDecFlags = 0;
- mSavedDecFlags = 0;
- mInsertMode = false;
- mAutomaticNewlineMode = false;
- mTopMargin = 0;
- mBottomMargin = mRows;
- mAboutToAutoWrap = false;
- mForeColor = mDefaultForeColor;
- mBackColor = mDefaultBackColor;
- mInverseColors = false;
- mbKeypadApplicationMode = false;
- mAlternateCharSet = false;
- // 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();
- mInp