mirror of https://github.com/termux/termux-app
2508 lines
122 KiB
Java
2508 lines
122 KiB
Java
package com.termux.terminal;
|
||
|
||
import android.util.Base64;
|
||
|
||
import java.nio.charset.StandardCharsets;
|
||
import java.util.Arrays;
|
||
import java.util.Locale;
|
||
import java.util.Objects;
|
||
import java.util.Stack;
|
||
|
||
/**
|
||
* 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.
|
||
* <p>
|
||
* References:
|
||
* <ul>
|
||
* <li>http://invisible-island.net/xterm/ctlseqs/ctlseqs.html</li>
|
||
* <li>http://en.wikipedia.org/wiki/ANSI_escape_code</li>
|
||
* <li>http://man.he.net/man4/console_codes</li>
|
||
* <li>http://bazaar.launchpad.net/~leonerd/libvterm/trunk/view/head:/src/state.c</li>
|
||
* <li>http://www.columbia.edu/~kermit/k95manual/iso2022.html</li>
|
||
* <li>http://www.vt100.net/docs/vt510-rm/chapter4</li>
|
||
* <li>http://en.wikipedia.org/wiki/ISO/IEC_2022 - for 7-bit and 8-bit GL GR explanation</li>
|
||
* <li>http://bjh21.me.uk/all-escapes/all-escapes.txt - extensive!</li>
|
||
* <li>http://woldlab.caltech.edu/~diane/kde4.10/workingdir/kubuntu/konsole/doc/developer/old-documents/VT100/techref.
|
||
* html - document for konsole - accessible!</li>
|
||
* </ul>
|
||
*/
|
||
public final class TerminalEmulator {
|
||
|
||
/** Log unknown or unimplemented escape sequences received from the shell process. */
|
||
private static final boolean LOG_ESCAPE_SEQUENCES = false;
|
||
|
||
public static final int MOUSE_LEFT_BUTTON = 0;
|
||
|
||
/** Mouse moving while having left mouse button pressed. */
|
||
public static final int MOUSE_LEFT_BUTTON_MOVED = 32;
|
||
public static final int MOUSE_WHEELUP_BUTTON = 64;
|
||
public static final int MOUSE_WHEELDOWN_BUTTON = 65;
|
||
|
||
/** Used for invalid data - http://en.wikipedia.org/wiki/Replacement_character#Replacement_character */
|
||
public static final int UNICODE_REPLACEMENT_CHAR = 0xFFFD;
|
||
|
||
/** Escape processing: Not currently in an escape sequence. */
|
||
private static final int ESC_NONE = 0;
|
||
/** Escape processing: Have seen an ESC character - proceed to {@link #doEsc(int)} */
|
||
private static final int ESC = 1;
|
||
/** Escape processing: Have seen ESC POUND */
|
||
private static final int ESC_POUND = 2;
|
||
/** Escape processing: Have seen ESC and a character-set-select ( char */
|
||
private static final int ESC_SELECT_LEFT_PAREN = 3;
|
||
/** Escape processing: Have seen ESC and a character-set-select ) char */
|
||
private static final int ESC_SELECT_RIGHT_PAREN = 4;
|
||
/** Escape processing: "ESC [" or CSI (Control Sequence Introducer). */
|
||
private static final int ESC_CSI = 6;
|
||
/** Escape processing: ESC [ ? */
|
||
private static final int ESC_CSI_QUESTIONMARK = 7;
|
||
/** Escape processing: ESC [ $ */
|
||
private static final int ESC_CSI_DOLLAR = 8;
|
||
/** Escape processing: ESC % */
|
||
private static final int ESC_PERCENT = 9;
|
||
/** Escape processing: ESC ] (AKA OSC - Operating System Controls) */
|
||
private static final int ESC_OSC = 10;
|
||
/** Escape processing: ESC ] (AKA OSC - Operating System Controls) ESC */
|
||
private static final int ESC_OSC_ESC = 11;
|
||
/** Escape processing: ESC [ > */
|
||
private static final int ESC_CSI_BIGGERTHAN = 12;
|
||
/** Escape procession: "ESC P" or Device Control String (DCS) */
|
||
private static final int ESC_P = 13;
|
||
/** Escape processing: CSI > */
|
||
private static final int ESC_CSI_QUESTIONMARK_ARG_DOLLAR = 14;
|
||
/** Escape processing: CSI $ARGS ' ' */
|
||
private static final int ESC_CSI_ARGS_SPACE = 15;
|
||
/** Escape processing: CSI $ARGS '*' */
|
||
private static final int ESC_CSI_ARGS_ASTERIX = 16;
|
||
/** Escape processing: CSI " */
|
||
private static final int ESC_CSI_DOUBLE_QUOTE = 17;
|
||
/** Escape processing: CSI ' */
|
||
private static final int ESC_CSI_SINGLE_QUOTE = 18;
|
||
/** Escape processing: CSI ! */
|
||
private static final int ESC_CSI_EXCLAMATION = 19;
|
||
|
||
/** The number of parameter arguments. This name comes from the ANSI standard for terminal escape codes. */
|
||
private static final int MAX_ESCAPE_PARAMETERS = 16;
|
||
|
||
/** Needs to be large enough to contain reasonable OSC 52 pastes. */
|
||
private static final int MAX_OSC_STRING_LENGTH = 8192;
|
||
|
||
/** DECSET 1 - application cursor keys. */
|
||
private static final int DECSET_BIT_APPLICATION_CURSOR_KEYS = 1;
|
||
private static final int DECSET_BIT_REVERSE_VIDEO = 1 << 1;
|
||
/**
|
||
* http://www.vt100.net/docs/vt510-rm/DECOM: "When DECOM is set, the home cursor position is at the upper-left
|
||
* corner of the screen, within the margins. The starting point for line numbers depends on the current top margin
|
||
* setting. The cursor cannot move outside of the margins. When DECOM is reset, the home cursor position is at the
|
||
* upper-left corner of the screen. The starting point for line numbers is independent of the margins. The cursor
|
||
* can move outside of the margins."
|
||
*/
|
||
private static final int DECSET_BIT_ORIGIN_MODE = 1 << 2;
|
||
/**
|
||
* http://www.vt100.net/docs/vt510-rm/DECAWM: "If the DECAWM function is set, then graphic characters received when
|
||
* the cursor is at the right border of the page appear at the beginning of the next line. Any text on the page
|
||
* scrolls up if the cursor is at the end of the scrolling region. If the DECAWM function is reset, then graphic
|
||
* characters received when the cursor is at the right border of the page replace characters already on the page."
|
||
*/
|
||
private static final int DECSET_BIT_AUTOWRAP = 1 << 3;
|
||
/** DECSET 25 - if the cursor should be enabled, {@link #isCursorEnabled()}. */
|
||
private static final int DECSET_BIT_CURSOR_ENABLED = 1 << 4;
|
||
private static final int DECSET_BIT_APPLICATION_KEYPAD = 1 << 5;
|
||
/** DECSET 1000 - if to report mouse press&release events. */
|
||
private static final int DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE = 1 << 6;
|
||
/** DECSET 1002 - like 1000, but report moving mouse while pressed. */
|
||
private static final int DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT = 1 << 7;
|
||
/** DECSET 1004 - NOT implemented. */
|
||
private static final int DECSET_BIT_SEND_FOCUS_EVENTS = 1 << 8;
|
||
/** DECSET 1006 - SGR-like mouse protocol (the modern sane choice). */
|
||
private static final int DECSET_BIT_MOUSE_PROTOCOL_SGR = 1 << 9;
|
||
/** DECSET 2004 - see {@link #paste(String)} */
|
||
private static final int DECSET_BIT_BRACKETED_PASTE_MODE = 1 << 10;
|
||
/** Toggled with DECLRMM - http://www.vt100.net/docs/vt510-rm/DECLRMM */
|
||
private static final int DECSET_BIT_LEFTRIGHT_MARGIN_MODE = 1 << 11;
|
||
/** Not really DECSET bit... - http://www.vt100.net/docs/vt510-rm/DECSACE */
|
||
private static final int DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE = 1 << 12;
|
||
|
||
|
||
private String mTitle;
|
||
private final Stack<String> mTitleStack = new Stack<>();
|
||
|
||
/** If processing first character of first parameter of {@link #ESC_CSI}. */
|
||
private boolean mIsCSIStart;
|
||
/** The last character processed of a parameter of {@link #ESC_CSI}. */
|
||
private Integer mLastCSIArg;
|
||
|
||
/** The cursor position. Between (0,0) and (mRows-1, mColumns-1). */
|
||
private int mCursorRow, mCursorCol;
|
||
|
||
/** The number of character rows and columns in the terminal screen. */
|
||
public int mRows, mColumns;
|
||
|
||
/** The number of terminal transcript rows that can be scrolled back to. */
|
||
public static final int TERMINAL_TRANSCRIPT_ROWS_MIN = 100;
|
||
public static final int TERMINAL_TRANSCRIPT_ROWS_MAX = 50000;
|
||
public static final int DEFAULT_TERMINAL_TRANSCRIPT_ROWS = 2000;
|
||
|
||
|
||
/* The supported terminal cursor styles. */
|
||
|
||
public static final int TERMINAL_CURSOR_STYLE_BLOCK = 0;
|
||
public static final int TERMINAL_CURSOR_STYLE_UNDERLINE = 1;
|
||
public static final int TERMINAL_CURSOR_STYLE_BAR = 2;
|
||
public static final int DEFAULT_TERMINAL_CURSOR_STYLE = TERMINAL_CURSOR_STYLE_BLOCK;
|
||
public static final Integer[] TERMINAL_CURSOR_STYLES_LIST = new Integer[]{TERMINAL_CURSOR_STYLE_BLOCK, TERMINAL_CURSOR_STYLE_UNDERLINE, TERMINAL_CURSOR_STYLE_BAR};
|
||
|
||
/** The terminal cursor styles. */
|
||
private int mCursorStyle = DEFAULT_TERMINAL_CURSOR_STYLE;
|
||
|
||
|
||
/** The normal screen buffer. Stores the characters that appear on the screen of the emulated terminal. */
|
||
private final TerminalBuffer mMainBuffer;
|
||
/**
|
||
* The alternate screen buffer, exactly as large as the display and contains no additional saved lines (so that when
|
||
* the alternate screen buffer is active, you cannot scroll back to view saved lines).
|
||
* <p>
|
||
* See http://www.xfree86.org/current/ctlseqs.html#The%20Alternate%20Screen%20Buffer
|
||
*/
|
||
final TerminalBuffer mAltBuffer;
|
||
/** The current screen buffer, pointing at either {@link #mMainBuffer} or {@link #mAltBuffer}. */
|
||
private TerminalBuffer mScreen;
|
||
|
||
/** The terminal session this emulator is bound to. */
|
||
private final TerminalOutput mSession;
|
||
|
||
TerminalSessionClient mClient;
|
||
|
||
/** Keeps track of the current argument of the current escape sequence. Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. */
|
||
private int mArgIndex;
|
||
/** Holds the arguments of the current escape sequence. */
|
||
private final int[] mArgs = new int[MAX_ESCAPE_PARAMETERS];
|
||
|
||
/** Holds OSC and device control arguments, which can be strings. */
|
||
private final StringBuilder mOSCOrDeviceControlArgs = new StringBuilder();
|
||
|
||
/**
|
||
* 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. One of the ESC_* constants. */
|
||
private int mEscapeState;
|
||
|
||
private final SavedScreenState mSavedStateMain = new SavedScreenState();
|
||
private final SavedScreenState mSavedStateAlt = new SavedScreenState();
|
||
|
||
/** http://www.vt100.net/docs/vt102-ug/table5-15.html */
|
||
private boolean mUseLineDrawingG0, mUseLineDrawingG1, mUseLineDrawingUsesG0 = true;
|
||
|
||
/**
|
||
* @see TerminalEmulator#mapDecSetBitToInternalBit(int)
|
||
*/
|
||
private int mCurrentDecSetFlags, mSavedDecSetFlags;
|
||
|
||
/**
|
||
* If insert mode (as opposed to replace mode) is active. In insert mode new characters are inserted, pushing
|
||
* existing text to the right. Characters moved past the right margin are lost.
|
||
*/
|
||
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;
|
||
|
||
/**
|
||
* Top margin of screen for scrolling ranges from 0 to mRows-2. Bottom margin ranges from mTopMargin + 2 to mRows
|
||
* (Defines the first row after the scrolling region). Left/right margin in [0, mColumns].
|
||
*/
|
||
private int mTopMargin, mBottomMargin, mLeftMargin, mRightMargin;
|
||
|
||
/**
|
||
* 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 the last column (mColumns-1). When standing there, a written character will be
|
||
* output in the last column, the cursor not moving but this flag will be set. When outputting another character
|
||
* this will move to the next line.
|
||
*/
|
||
private boolean mAboutToAutoWrap;
|
||
|
||
/**
|
||
* If the cursor blinking is enabled. It requires cursor itself to be enabled, which is controlled
|
||
* byt whether {@link #DECSET_BIT_CURSOR_ENABLED} bit is set or not.
|
||
*/
|
||
private boolean mCursorBlinkingEnabled;
|
||
|
||
/**
|
||
* If currently cursor should be in a visible state or not if {@link #mCursorBlinkingEnabled}
|
||
* is {@code true}.
|
||
*/
|
||
private boolean mCursorBlinkState;
|
||
|
||
/**
|
||
* Current foreground and background colors. Can either be a color index in [0,259] or a truecolor (24-bit) value.
|
||
* For a 24-bit value the top byte (0xff000000) is set.
|
||
*
|
||
* @see TextStyle
|
||
*/
|
||
int mForeColor, mBackColor;
|
||
|
||
/** Current {@link TextStyle} effect. */
|
||
private int mEffect;
|
||
|
||
/**
|
||
* The number of scrolled lines since last calling {@link #clearScrollCounter()}. Used for moving selection up along
|
||
* with the scrolling text.
|
||
*/
|
||
private int mScrollCounter = 0;
|
||
|
||
/** If automatic scrolling of terminal is disabled */
|
||
private boolean mAutoScrollDisabled;
|
||
|
||
private byte mUtf8ToFollow, mUtf8Index;
|
||
private final byte[] mUtf8InputBuffer = new byte[4];
|
||
private int mLastEmittedCodePoint = -1;
|
||
|
||
public final TerminalColors mColors = new TerminalColors();
|
||
|
||
private static final String LOG_TAG = "TerminalEmulator";
|
||
|
||
private boolean isDecsetInternalBitSet(int bit) {
|
||
return (mCurrentDecSetFlags & bit) != 0;
|
||
}
|
||
|
||
private void setDecsetinternalBit(int internalBit, boolean set) {
|
||
if (set) {
|
||
// The mouse modes are mutually exclusive.
|
||
if (internalBit == DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE) {
|
||
setDecsetinternalBit(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT, false);
|
||
} else if (internalBit == DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT) {
|
||
setDecsetinternalBit(DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE, false);
|
||
}
|
||
}
|
||
if (set) {
|
||
mCurrentDecSetFlags |= internalBit;
|
||
} else {
|
||
mCurrentDecSetFlags &= ~internalBit;
|
||
}
|
||
}
|
||
|
||
static int mapDecSetBitToInternalBit(int decsetBit) {
|
||
switch (decsetBit) {
|
||
case 1:
|
||
return DECSET_BIT_APPLICATION_CURSOR_KEYS;
|
||
case 5:
|
||
return DECSET_BIT_REVERSE_VIDEO;
|
||
case 6:
|
||
return DECSET_BIT_ORIGIN_MODE;
|
||
case 7:
|
||
return DECSET_BIT_AUTOWRAP;
|
||
case 25:
|
||
return DECSET_BIT_CURSOR_ENABLED;
|
||
case 66:
|
||
return DECSET_BIT_APPLICATION_KEYPAD;
|
||
case 69:
|
||
return DECSET_BIT_LEFTRIGHT_MARGIN_MODE;
|
||
case 1000:
|
||
return DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE;
|
||
case 1002:
|
||
return DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT;
|
||
case 1004:
|
||
return DECSET_BIT_SEND_FOCUS_EVENTS;
|
||
case 1006:
|
||
return DECSET_BIT_MOUSE_PROTOCOL_SGR;
|
||
case 2004:
|
||
return DECSET_BIT_BRACKETED_PASTE_MODE;
|
||
default:
|
||
return -1;
|
||
// throw new IllegalArgumentException("Unsupported decset: " + decsetBit);
|
||
}
|
||
}
|
||
|
||
public TerminalEmulator(TerminalOutput session, int columns, int rows, Integer transcriptRows, TerminalSessionClient client) {
|
||
mSession = session;
|
||
mScreen = mMainBuffer = new TerminalBuffer(columns, getTerminalTranscriptRows(transcriptRows), rows);
|
||
mAltBuffer = new TerminalBuffer(columns, rows, rows);
|
||
mClient = client;
|
||
mRows = rows;
|
||
mColumns = columns;
|
||
mTabStop = new boolean[mColumns];
|
||
reset();
|
||
}
|
||
|
||
public void updateTerminalSessionClient(TerminalSessionClient client) {
|
||
mClient = client;
|
||
setCursorStyle();
|
||
setCursorBlinkState(true);
|
||
}
|
||
|
||
public TerminalBuffer getScreen() {
|
||
return mScreen;
|
||
}
|
||
|
||
public boolean isAlternateBufferActive() {
|
||
return mScreen == mAltBuffer;
|
||
}
|
||
|
||
private int getTerminalTranscriptRows(Integer transcriptRows) {
|
||
if (transcriptRows == null || transcriptRows < TERMINAL_TRANSCRIPT_ROWS_MIN || transcriptRows > TERMINAL_TRANSCRIPT_ROWS_MAX)
|
||
return DEFAULT_TERMINAL_TRANSCRIPT_ROWS;
|
||
else
|
||
return transcriptRows;
|
||
}
|
||
|
||
/**
|
||
* @param mouseButton one of the MOUSE_* constants of this class.
|
||
*/
|
||
public void sendMouseEvent(int mouseButton, int column, int row, boolean pressed) {
|
||
if (column < 1) column = 1;
|
||
if (column > mColumns) column = mColumns;
|
||
if (row < 1) row = 1;
|
||
if (row > mRows) row = mRows;
|
||
|
||
if (mouseButton == MOUSE_LEFT_BUTTON_MOVED && !isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT)) {
|
||
// Do not send tracking.
|
||
} else if (isDecsetInternalBitSet(DECSET_BIT_MOUSE_PROTOCOL_SGR)) {
|
||
mSession.write(String.format("\033[<%d;%d;%d" + (pressed ? 'M' : 'm'), mouseButton, column, row));
|
||
} else {
|
||
mouseButton = pressed ? mouseButton : 3; // 3 for release of all buttons.
|
||
// Clip to screen, and clip to the limits of 8-bit data.
|
||
boolean out_of_bounds = column > 255 - 32 || row > 255 - 32;
|
||
if (!out_of_bounds) {
|
||
byte[] data = {'\033', '[', 'M', (byte) (32 + mouseButton), (byte) (32 + column), (byte) (32 + row)};
|
||
mSession.write(data, 0, data.length);
|
||
}
|
||
}
|
||
}
|
||
|
||
public void resize(int columns, int rows) {
|
||
if (mRows == rows && mColumns == columns) {
|
||
return;
|
||
} else if (columns < 2 || rows < 2) {
|
||
throw new IllegalArgumentException("rows=" + rows + ", columns=" + columns);
|
||
}
|
||
|
||
if (mRows != rows) {
|
||
mRows = rows;
|
||
mTopMargin = 0;
|
||
mBottomMargin = mRows;
|
||
}
|
||
if (mColumns != columns) {
|
||
int oldColumns = mColumns;
|
||
mColumns = columns;
|
||
boolean[] oldTabStop = mTabStop;
|
||
mTabStop = new boolean[mColumns];
|
||
setDefaultTabStops();
|
||
int toTransfer = Math.min(oldColumns, columns);
|
||
System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer);
|
||
mLeftMargin = 0;
|
||
mRightMargin = mColumns;
|
||
}
|
||
|
||
resizeScreen();
|
||
}
|
||
|
||
private void resizeScreen() {
|
||
final int[] cursor = {mCursorCol, mCursorRow};
|
||
int newTotalRows = (mScreen == mAltBuffer) ? mRows : mMainBuffer.mTotalRows;
|
||
mScreen.resize(mColumns, mRows, newTotalRows, cursor, getStyle(), isAlternateBufferActive());
|
||
mCursorCol = cursor[0];
|
||
mCursorRow = cursor[1];
|
||
}
|
||
|
||
public int getCursorRow() {
|
||
return mCursorRow;
|
||
}
|
||
|
||
public int getCursorCol() {
|
||
return mCursorCol;
|
||
}
|
||
|
||
/** Get the terminal cursor style. It will be one of {@link #TERMINAL_CURSOR_STYLES_LIST} */
|
||
public int getCursorStyle() {
|
||
return mCursorStyle;
|
||
}
|
||
|
||
/** Set the terminal cursor style. */
|
||
public void setCursorStyle() {
|
||
Integer cursorStyle = null;
|
||
|
||
if (mClient != null)
|
||
cursorStyle = mClient.getTerminalCursorStyle();
|
||
|
||
if (cursorStyle == null || !Arrays.asList(TERMINAL_CURSOR_STYLES_LIST).contains(cursorStyle))
|
||
mCursorStyle = DEFAULT_TERMINAL_CURSOR_STYLE;
|
||
else
|
||
mCursorStyle = cursorStyle;
|
||
}
|
||
|
||
public boolean isReverseVideo() {
|
||
return isDecsetInternalBitSet(DECSET_BIT_REVERSE_VIDEO);
|
||
}
|
||
|
||
|
||
|
||
public boolean isCursorEnabled() {
|
||
return isDecsetInternalBitSet(DECSET_BIT_CURSOR_ENABLED);
|
||
}
|
||
public boolean shouldCursorBeVisible() {
|
||
if (!isCursorEnabled())
|
||
return false;
|
||
else
|
||
return mCursorBlinkingEnabled ? mCursorBlinkState : true;
|
||
}
|
||
|
||
public void setCursorBlinkingEnabled(boolean cursorBlinkingEnabled) {
|
||
this.mCursorBlinkingEnabled = cursorBlinkingEnabled;
|
||
}
|
||
|
||
public void setCursorBlinkState(boolean cursorBlinkState) {
|
||
this.mCursorBlinkState = cursorBlinkState;
|
||
}
|
||
|
||
|
||
|
||
public boolean isKeypadApplicationMode() {
|
||
return isDecsetInternalBitSet(DECSET_BIT_APPLICATION_KEYPAD);
|
||
}
|
||
|
||
public boolean isCursorKeysApplicationMode() {
|
||
return isDecsetInternalBitSet(DECSET_BIT_APPLICATION_CURSOR_KEYS);
|
||
}
|
||
|
||
/** If mouse events are being sent as escape codes to the terminal. */
|
||
public boolean isMouseTrackingActive() {
|
||
return isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE) || isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT);
|
||
}
|
||
|
||
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 length the number of bytes in the array to process
|
||
*/
|
||
public void append(byte[] buffer, int length) {
|
||
for (int i = 0; i < length; i++)
|
||
processByte(buffer[i]);
|
||
}
|
||
|
||
private void processByte(byte byteToProcess) {
|
||
if (mUtf8ToFollow > 0) {
|
||
if ((byteToProcess & 0b11000000) == 0b10000000) {
|
||
// 10xxxxxx, a continuation byte.
|
||
mUtf8InputBuffer[mUtf8Index++] = byteToProcess;
|
||
if (--mUtf8ToFollow == 0) {
|
||
byte firstByteMask = (byte) (mUtf8Index == 2 ? 0b00011111 : (mUtf8Index == 3 ? 0b00001111 : 0b00000111));
|
||
int codePoint = (mUtf8InputBuffer[0] & firstByteMask);
|
||
for (int i = 1; i < mUtf8Index; i++)
|
||
codePoint = ((codePoint << 6) | (mUtf8InputBuffer[i] & 0b00111111));
|
||
if (((codePoint <= 0b1111111) && mUtf8Index > 1) || (codePoint < 0b11111111111 && mUtf8Index > 2)
|
||
|| (codePoint < 0b1111111111111111 && mUtf8Index > 3)) {
|
||
// Overlong encoding.
|
||
codePoint = UNICODE_REPLACEMENT_CHAR;
|
||
}
|
||
|
||
mUtf8Index = mUtf8ToFollow = 0;
|
||
|
||
if (codePoint >= 0x80 && codePoint <= 0x9F) {
|
||
// Sequence decoded to a C1 control character which we ignore. They are
|
||
// not used nowadays and increases the risk of messing up the terminal state
|
||
// on binary input. XTerm does not allow them in utf-8:
|
||
// "It is not possible to use a C1 control obtained from decoding the
|
||
// UTF-8 text" - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||
} else {
|
||
switch (Character.getType(codePoint)) {
|
||
case Character.UNASSIGNED:
|
||
case Character.SURROGATE:
|
||
codePoint = UNICODE_REPLACEMENT_CHAR;
|
||
}
|
||
processCodePoint(codePoint);
|
||
}
|
||
}
|
||
} else {
|
||
// Not a UTF-8 continuation byte so replace the entire sequence up to now with the replacement char:
|
||
mUtf8Index = mUtf8ToFollow = 0;
|
||
emitCodePoint(UNICODE_REPLACEMENT_CHAR);
|
||
// The Unicode Standard Version 6.2 – Core Specification
|
||
// (http://www.unicode.org/versions/Unicode6.2.0/ch03.pdf):
|
||
// "If the converter encounters an ill-formed UTF-8 code unit sequence which starts with a valid first
|
||
// byte, but which does not continue with valid successor bytes (see Table 3-7), it must not consume the
|
||
// successor bytes as part of the ill-formed subsequence
|
||
// whenever those successor bytes themselves constitute part of a well-formed UTF-8 code unit
|
||
// subsequence."
|
||
processByte(byteToProcess);
|
||
}
|
||
} else {
|
||
if ((byteToProcess & 0b10000000) == 0) { // The leading bit is not set so it is a 7-bit ASCII character.
|
||
processCodePoint(byteToProcess);
|
||
return;
|
||
} else if ((byteToProcess & 0b11100000) == 0b11000000) { // 110xxxxx, a two-byte sequence.
|
||
mUtf8ToFollow = 1;
|
||
} else if ((byteToProcess & 0b11110000) == 0b11100000) { // 1110xxxx, a three-byte sequence.
|
||
mUtf8ToFollow = 2;
|
||
} else if ((byteToProcess & 0b11111000) == 0b11110000) { // 11110xxx, a four-byte sequence.
|
||
mUtf8ToFollow = 3;
|
||
} else {
|
||
// Not a valid UTF-8 sequence start, signal invalid data:
|
||
processCodePoint(UNICODE_REPLACEMENT_CHAR);
|
||
return;
|
||
}
|
||
mUtf8InputBuffer[mUtf8Index++] = byteToProcess;
|
||
}
|
||
}
|
||
|
||
public void processCodePoint(int b) {
|
||
switch (b) {
|
||
case 0: // Null character (NUL, ^@). Do nothing.
|
||
break;
|
||
case 7: // Bell (BEL, ^G, \a). If in an OSC sequence, BEL may terminate a string; otherwise signal bell.
|
||
if (mEscapeState == ESC_OSC)
|
||
doOsc(b);
|
||
else
|
||
mSession.onBell();
|
||
break;
|
||
case 8: // Backspace (BS, ^H).
|
||
if (mLeftMargin == mCursorCol) {
|
||
// Jump to previous line if it was auto-wrapped.
|
||
int previousRow = mCursorRow - 1;
|
||
if (previousRow >= 0 && mScreen.getLineWrap(previousRow)) {
|
||
mScreen.clearLineWrap(previousRow);
|
||
setCursorRowCol(previousRow, mRightMargin - 1);
|
||
}
|
||
} else {
|
||
setCursorCol(mCursorCol - 1);
|
||
}
|
||
break;
|
||
case 9: // Horizontal tab (HT, \t) - move to next tab stop, but not past edge of screen
|
||
// XXX: Should perhaps use color if writing to new cells. Try with
|
||
// printf "\033[41m\tXX\033[0m\n"
|
||
// The OSX Terminal.app colors the spaces from the tab red, but xterm does not.
|
||
// Note that Terminal.app only colors on new cells, in e.g.
|
||
// printf "\033[41m\t\r\033[42m\tXX\033[0m\n"
|
||
// the first cells are created with a red background, but when tabbing over
|
||
// them again with a green background they are not overwritten.
|
||
mCursorCol = nextTabStop(1);
|
||
break;
|
||
case 10: // Line feed (LF, \n).
|
||
case 11: // Vertical tab (VT, \v).
|
||
case 12: // Form feed (FF, \f).
|
||
doLinefeed();
|
||
break;
|
||
case 13: // Carriage return (CR, \r).
|
||
setCursorCol(mLeftMargin);
|
||
break;
|
||
case 14: // Shift Out (Ctrl-N, SO) → Switch to Alternate Character Set. This invokes the G1 character set.
|
||
mUseLineDrawingUsesG0 = false;
|
||
break;
|
||
case 15: // Shift In (Ctrl-O, SI) → Switch to Standard Character Set. This invokes the G0 character set.
|
||
mUseLineDrawingUsesG0 = true;
|
||
break;
|
||
case 24: // CAN.
|
||
case 26: // SUB.
|
||
if (mEscapeState != ESC_NONE) {
|
||
// FIXME: What is this??
|
||
mEscapeState = ESC_NONE;
|
||
emitCodePoint(127);
|
||
}
|
||
break;
|
||
case 27: // ESC
|
||
// Starts an escape sequence unless we're parsing a string
|
||
if (mEscapeState == ESC_P) {
|
||
// XXX: Ignore escape when reading device control sequence, since it may be part of string terminator.
|
||
return;
|
||
} else if (mEscapeState != ESC_OSC) {
|
||
startEscapeSequence();
|
||
} else {
|
||
doOsc(b);
|
||
}
|
||
break;
|
||
default:
|
||
mContinueSequence = false;
|
||
switch (mEscapeState) {
|
||
case ESC_NONE:
|
||
if (b >= 32) emitCodePoint(b);
|
||
break;
|
||
case ESC:
|
||
doEsc(b);
|
||
break;
|
||
case ESC_POUND:
|
||
doEscPound(b);
|
||
break;
|
||
case ESC_SELECT_LEFT_PAREN: // Designate G0 Character Set (ISO 2022, VT100).
|
||
mUseLineDrawingG0 = (b == '0');
|
||
break;
|
||
case ESC_SELECT_RIGHT_PAREN: // Designate G1 Character Set (ISO 2022, VT100).
|
||
mUseLineDrawingG1 = (b == '0');
|
||
break;
|
||
case ESC_CSI:
|
||
doCsi(b);
|
||
break;
|
||
case ESC_CSI_EXCLAMATION:
|
||
if (b == 'p') { // Soft terminal reset (DECSTR, http://vt100.net/docs/vt510-rm/DECSTR).
|
||
reset();
|
||
} else {
|
||
unknownSequence(b);
|
||
}
|
||
break;
|
||
case ESC_CSI_QUESTIONMARK:
|
||
doCsiQuestionMark(b);
|
||
break;
|
||
case ESC_CSI_BIGGERTHAN:
|
||
doCsiBiggerThan(b);
|
||
break;
|
||
case ESC_CSI_DOLLAR:
|
||
boolean originMode = isDecsetInternalBitSet(DECSET_BIT_ORIGIN_MODE);
|
||
int effectiveTopMargin = originMode ? mTopMargin : 0;
|
||
int effectiveBottomMargin = originMode ? mBottomMargin : mRows;
|
||
int effectiveLeftMargin = originMode ? mLeftMargin : 0;
|
||
int effectiveRightMargin = originMode ? mRightMargin : mColumns;
|
||
switch (b) {
|
||
case 'v': // ${CSI}${SRC_TOP}${SRC_LEFT}${SRC_BOTTOM}${SRC_RIGHT}${SRC_PAGE}${DST_TOP}${DST_LEFT}${DST_PAGE}$v"
|
||
// Copy rectangular area (DECCRA - http://vt100.net/docs/vt510-rm/DECCRA):
|
||
// "If Pbs is greater than Pts, or Pls is greater than Prs, the terminal ignores DECCRA.
|
||
// The coordinates of the rectangular area are affected by the setting of origin mode (DECOM).
|
||
// DECCRA is not affected by the page margins.
|
||
// The copied text takes on the line attributes of the destination area.
|
||
// If the value of Pt, Pl, Pb, or Pr exceeds the width or height of the active page, then the value
|
||
// is treated as the width or height of that page.
|
||
// If the destination area is partially off the page, then DECCRA clips the off-page data.
|
||
// DECCRA does not change the active cursor position."
|
||
int topSource = Math.min(getArg(0, 1, true) - 1 + effectiveTopMargin, mRows);
|
||
int leftSource = Math.min(getArg(1, 1, true) - 1 + effectiveLeftMargin, mColumns);
|
||
// Inclusive, so do not subtract one:
|
||
int bottomSource = Math.min(Math.max(getArg(2, mRows, true) + effectiveTopMargin, topSource), mRows);
|
||
int rightSource = Math.min(Math.max(getArg(3, mColumns, true) + effectiveLeftMargin, leftSource), mColumns);
|
||
// int sourcePage = getArg(4, 1, true);
|
||
int destionationTop = Math.min(getArg(5, 1, true) - 1 + effectiveTopMargin, mRows);
|
||
int destinationLeft = Math.min(getArg(6, 1, true) - 1 + effectiveLeftMargin, mColumns);
|
||
// int destinationPage = getArg(7, 1, true);
|
||
int heightToCopy = Math.min(mRows - destionationTop, bottomSource - topSource);
|
||
int widthToCopy = Math.min(mColumns - destinationLeft, rightSource - leftSource);
|
||
mScreen.blockCopy(leftSource, topSource, widthToCopy, heightToCopy, destinationLeft, destionationTop);
|
||
break;
|
||
case '{': // ${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${"
|
||
// Selective erase rectangular area (DECSERA - http://www.vt100.net/docs/vt510-rm/DECSERA).
|
||
case 'x': // ${CSI}${CHAR};${TOP}${LEFT}${BOTTOM}${RIGHT}$x"
|
||
// Fill rectangular area (DECFRA - http://www.vt100.net/docs/vt510-rm/DECFRA).
|
||
case 'z': // ${CSI}$${TOP}${LEFT}${BOTTOM}${RIGHT}$z"
|
||
// Erase rectangular area (DECERA - http://www.vt100.net/docs/vt510-rm/DECERA).
|
||
boolean erase = b != 'x';
|
||
boolean selective = b == '{';
|
||
// Only DECSERA keeps visual attributes, DECERA does not:
|
||
boolean keepVisualAttributes = erase && selective;
|
||
int argIndex = 0;
|
||
int fillChar = erase ? ' ' : getArg(argIndex++, -1, true);
|
||
// "Pch can be any value from 32 to 126 or from 160 to 255. If Pch is not in this range, then the
|
||
// terminal ignores the DECFRA command":
|
||
if ((fillChar >= 32 && fillChar <= 126) || (fillChar >= 160 && fillChar <= 255)) {
|
||
// "If the value of Pt, Pl, Pb, or Pr exceeds the width or height of the active page, the value
|
||
// is treated as the width or height of that page."
|
||
int top = Math.min(getArg(argIndex++, 1, true) + effectiveTopMargin, effectiveBottomMargin + 1);
|
||
int left = Math.min(getArg(argIndex++, 1, true) + effectiveLeftMargin, effectiveRightMargin + 1);
|
||
int bottom = Math.min(getArg(argIndex++, mRows, true) + effectiveTopMargin, effectiveBottomMargin);
|
||
int right = Math.min(getArg(argIndex, mColumns, true) + effectiveLeftMargin, effectiveRightMargin);
|
||
long style = getStyle();
|
||
for (int row = top - 1; row < bottom; row++)
|
||
for (int col = left - 1; col < right; col++)
|
||
if (!selective || (TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0)
|
||
mScreen.setChar(col, row, fillChar, keepVisualAttributes ? mScreen.getStyleAt(row, col) : style);
|
||
}
|
||
break;
|
||
case 'r': // "${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${ATTRIBUTES}$r"
|
||
// Change attributes in rectangular area (DECCARA - http://vt100.net/docs/vt510-rm/DECCARA).
|
||
case 't': // "${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${ATTRIBUTES}$t"
|
||
// Reverse attributes in rectangular area (DECRARA - http://www.vt100.net/docs/vt510-rm/DECRARA).
|
||
boolean reverse = b == 't';
|
||
// FIXME: "coordinates of the rectangular area are affected by the setting of origin mode (DECOM)".
|
||
int top = Math.min(getArg(0, 1, true) - 1, effectiveBottomMargin) + effectiveTopMargin;
|
||
int left = Math.min(getArg(1, 1, true) - 1, effectiveRightMargin) + effectiveLeftMargin;
|
||
int bottom = Math.min(getArg(2, mRows, true) + 1, effectiveBottomMargin - 1) + effectiveTopMargin;
|
||
int right = Math.min(getArg(3, mColumns, true) + 1, effectiveRightMargin - 1) + effectiveLeftMargin;
|
||
if (mArgIndex >= 4) {
|
||
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||
for (int i = 4; i <= mArgIndex; i++) {
|
||
int bits = 0;
|
||
boolean setOrClear = true; // True if setting, false if clearing.
|
||
switch (getArg(i, 0, false)) {
|
||
case 0: // Attributes off (no bold, no underline, no blink, positive image).
|
||
bits = (TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE | TextStyle.CHARACTER_ATTRIBUTE_BLINK
|
||
| TextStyle.CHARACTER_ATTRIBUTE_INVERSE);
|
||
if (!reverse) setOrClear = false;
|
||
break;
|
||
case 1: // Bold.
|
||
bits = TextStyle.CHARACTER_ATTRIBUTE_BOLD;
|
||
break;
|
||
case 4: // Underline.
|
||
bits = TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE;
|
||
break;
|
||
case 5: // Blink.
|
||
bits = TextStyle.CHARACTER_ATTRIBUTE_BLINK;
|
||
break;
|
||
case 7: // Negative image.
|
||
bits = TextStyle.CHARACTER_ATTRIBUTE_INVERSE;
|
||
break;
|
||
case 22: // No bold.
|
||
bits = TextStyle.CHARACTER_ATTRIBUTE_BOLD;
|
||
setOrClear = false;
|
||
break;
|
||
case 24: // No underline.
|
||
bits = TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE;
|
||
setOrClear = false;
|
||
break;
|
||
case 25: // No blink.
|
||
bits = TextStyle.CHARACTER_ATTRIBUTE_BLINK;
|
||
setOrClear = false;
|
||
break;
|
||
case 27: // Positive image.
|
||
bits = TextStyle.CHARACTER_ATTRIBUTE_INVERSE;
|
||
setOrClear = false;
|
||
break;
|
||
}
|
||
if (reverse && !setOrClear) {
|
||
// Reverse attributes in rectangular area ignores non-(1,4,5,7) bits.
|
||
} else {
|
||
mScreen.setOrClearEffect(bits, setOrClear, reverse, isDecsetInternalBitSet(DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE),
|
||
effectiveLeftMargin, effectiveRightMargin, top, left, bottom, right);
|
||
}
|
||
}
|
||
} else {
|
||
// Do nothing.
|
||
}
|
||
break;
|
||
default:
|
||
unknownSequence(b);
|
||
}
|
||
break;
|
||
case ESC_CSI_DOUBLE_QUOTE:
|
||
if (b == 'q') {
|
||
// http://www.vt100.net/docs/vt510-rm/DECSCA
|
||
int arg = getArg0(0);
|
||
if (arg == 0 || arg == 2) {
|
||
// DECSED and DECSEL can erase characters.
|
||
mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_PROTECTED;
|
||
} else if (arg == 1) {
|
||
// DECSED and DECSEL cannot erase characters.
|
||
mEffect |= TextStyle.CHARACTER_ATTRIBUTE_PROTECTED;
|
||
} else {
|
||
unknownSequence(b);
|
||
}
|
||
} else {
|
||
unknownSequence(b);
|
||
}
|
||
break;
|
||
case ESC_CSI_SINGLE_QUOTE:
|
||
if (b == '}') { // Insert Ps Column(s) (default = 1) (DECIC), VT420 and up.
|
||
int columnsAfterCursor = mRightMargin - mCursorCol;
|
||
int columnsToInsert = Math.min(getArg0(1), columnsAfterCursor);
|
||
int columnsToMove = columnsAfterCursor - columnsToInsert;
|
||
mScreen.blockCopy(mCursorCol, 0, columnsToMove, mRows, mCursorCol + columnsToInsert, 0);
|
||
blockClear(mCursorCol, 0, columnsToInsert, mRows);
|
||
} else if (b == '~') { // Delete Ps Column(s) (default = 1) (DECDC), VT420 and up.
|
||
int columnsAfterCursor = mRightMargin - mCursorCol;
|
||
int columnsToDelete = Math.min(getArg0(1), columnsAfterCursor);
|
||
int columnsToMove = columnsAfterCursor - columnsToDelete;
|
||
mScreen.blockCopy(mCursorCol + columnsToDelete, 0, columnsToMove, mRows, mCursorCol, 0);
|
||
} else {
|
||
unknownSequence(b);
|
||
}
|
||
break;
|
||
case ESC_PERCENT:
|
||
break;
|
||
case ESC_OSC:
|
||
doOsc(b);
|
||
break;
|
||
case ESC_OSC_ESC:
|
||
doOscEsc(b);
|
||
break;
|
||
case ESC_P:
|
||
doDeviceControl(b);
|
||
break;
|
||
case ESC_CSI_QUESTIONMARK_ARG_DOLLAR:
|
||
if (b == 'p') {
|
||
// Request DEC private mode (DECRQM).
|
||
int mode = getArg0(0);
|
||
int value;
|
||
if (mode == 47 || mode == 1047 || mode == 1049) {
|
||
// This state is carried by mScreen pointer.
|
||
value = (mScreen == mAltBuffer) ? 1 : 2;
|
||
} else {
|
||
int internalBit = mapDecSetBitToInternalBit(mode);
|
||
if (internalBit != -1) {
|
||
value = isDecsetInternalBitSet(internalBit) ? 1 : 2; // 1=set, 2=reset.
|
||
} else {
|
||
Logger.logError(mClient, LOG_TAG, "Got DECRQM for unrecognized private DEC mode=" + mode);
|
||
value = 0; // 0=not recognized, 3=permanently set, 4=permanently reset
|
||
}
|
||
}
|
||
mSession.write(String.format(Locale.US, "\033[?%d;%d$y", mode, value));
|
||
} else {
|
||
unknownSequence(b);
|
||
}
|
||
break;
|
||
case ESC_CSI_ARGS_SPACE:
|
||
int arg = getArg0(0);
|
||
switch (b) {
|
||
case 'q': // "${CSI}${STYLE} q" - set cursor style (http://www.vt100.net/docs/vt510-rm/DECSCUSR).
|
||
switch (arg) {
|
||
case 0: // Blinking block.
|
||
case 1: // Blinking block.
|
||
case 2: // Steady block.
|
||
mCursorStyle = TERMINAL_CURSOR_STYLE_BLOCK;
|
||
break;
|
||
case 3: // Blinking underline.
|
||
case 4: // Steady underline.
|
||
mCursorStyle = TERMINAL_CURSOR_STYLE_UNDERLINE;
|
||
break;
|
||
case 5: // Blinking bar (xterm addition).
|
||
case 6: // Steady bar (xterm addition).
|
||
mCursorStyle = TERMINAL_CURSOR_STYLE_BAR;
|
||
break;
|
||
}
|
||
break;
|
||
case 't':
|
||
case 'u':
|
||
// Set margin-bell volume - ignore.
|
||
break;
|
||
default:
|
||
unknownSequence(b);
|
||
}
|
||
break;
|
||
case ESC_CSI_ARGS_ASTERIX:
|
||
int attributeChangeExtent = getArg0(0);
|
||
if (b == 'x' && (attributeChangeExtent >= 0 && attributeChangeExtent <= 2)) {
|
||
// Select attribute change extent (DECSACE - http://www.vt100.net/docs/vt510-rm/DECSACE).
|
||
setDecsetinternalBit(DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE, attributeChangeExtent == 2);
|
||
} else {
|
||
unknownSequence(b);
|
||
}
|
||
break;
|
||
default:
|
||
unknownSequence(b);
|
||
break;
|
||
}
|
||
if (!mContinueSequence) mEscapeState = ESC_NONE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/** When in {@link #ESC_P} ("device control") sequence. */
|
||
private void doDeviceControl(int b) {
|
||
switch (b) {
|
||
case (byte) '\\': // End of ESC \ string Terminator
|
||
{
|
||
String dcs = mOSCOrDeviceControlArgs.toString();
|
||
// DCS $ q P t ST. Request Status String (DECRQSS)
|
||
if (dcs.startsWith("$q")) {
|
||
if (dcs.equals("$q\"p")) {
|
||
// DECSCL, conformance level, http://www.vt100.net/docs/vt510-rm/DECSCL:
|
||
String csiString = "64;1\"p";
|
||
mSession.write("\033P1$r" + csiString + "\033\\");
|
||
} else {
|
||
finishSequenceAndLogError("Unrecognized DECRQSS string: '" + dcs + "'");
|
||
}
|
||
} else if (dcs.startsWith("+q")) {
|
||
// Request Termcap/Terminfo String. The string following the "q" is a list of names encoded in
|
||
// hexadecimal (2 digits per character) separated by ; which correspond to termcap or terminfo key
|
||
// names.
|
||
// Two special features are also recognized, which are not key names: Co for termcap colors (or colors
|
||
// for terminfo colors), and TN for termcap name (or name for terminfo name).
|
||
// xterm responds with DCS 1 + r P t ST for valid requests, adding to P t an = , and the value of the
|
||
// corresponding string that xterm would send, or DCS 0 + r P t ST for invalid requests. The strings are
|
||
// encoded in hexadecimal (2 digits per character).
|
||
// Example:
|
||
// :kr=\EOC: ks=\E[?1h\E=: ku=\EOA: le=^H:mb=\E[5m:md=\E[1m:\
|
||
// where
|
||
// kd=down-arrow key
|
||
// kl=left-arrow key
|
||
// kr=right-arrow key
|
||
// ku=up-arrow key
|
||
// #2=key_shome, "shifted home"
|
||
// #4=key_sleft, "shift arrow left"
|
||
// %i=key_sright, "shift arrow right"
|
||
// *7=key_send, "shifted end"
|
||
// k1=F1 function key
|
||
|
||
// Example: Request for ku is "ESC P + q 6 b 7 5 ESC \", where 6b7d=ku in hexadecimal.
|
||
// Xterm response in normal cursor mode:
|
||
// "<27> P 1 + r 6 b 7 5 = 1 B 5 B 4 1" where 0x1B 0x5B 0x41 = 27 91 65 = ESC [ A
|
||
// Xterm response in application cursor mode:
|
||
// "<27> P 1 + r 6 b 7 5 = 1 B 5 B 4 1" where 0x1B 0x4F 0x41 = 27 91 65 = ESC 0 A
|
||
|
||
// #4 is "shift arrow left":
|
||
// *** Device Control (DCS) for '#4'- 'ESC P + q 23 34 ESC \'
|
||
// Response: <27> P 1 + r 2 3 3 4 = 1 B 5 B 3 1 3 B 3 2 4 4 <27> \
|
||
// where 0x1B 0x5B 0x31 0x3B 0x32 0x44 = ESC [ 1 ; 2 D
|
||
// which we find in: TermKeyListener.java: KEY_MAP.put(KEYMOD_SHIFT | KEYCODE_DPAD_LEFT, "\033[1;2D");
|
||
|
||
// See http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V40G_HTML/MAN/MAN4/0178____.HTM for what to
|
||
// respond, as well as http://www.freebsd.org/cgi/man.cgi?query=termcap&sektion=5#CAPABILITIES for
|
||
// the meaning of e.g. "ku", "kd", "kr", "kl"
|
||
|
||
for (String part : dcs.substring(2).split(";")) {
|
||
if (part.length() % 2 == 0) {
|
||
StringBuilder transBuffer = new StringBuilder();
|
||
char c;
|
||
for (int i = 0; i < part.length(); i += 2) {
|
||
try {
|
||
c = (char) Long.decode("0x" + part.charAt(i) + "" + part.charAt(i + 1)).longValue();
|
||
} catch (NumberFormatException e) {
|
||
Logger.logStackTraceWithMessage(mClient, LOG_TAG, "Invalid device termcap/terminfo encoded name \"" + part + "\"", e);
|
||
continue;
|
||
}
|
||
transBuffer.append(c);
|
||
}
|
||
|
||
String trans = transBuffer.toString();
|
||
String responseValue;
|
||
switch (trans) {
|
||
case "Co":
|
||
case "colors":
|
||
responseValue = "256"; // Number of colors.
|
||
break;
|
||
case "TN":
|
||
case "name":
|
||
responseValue = "xterm";
|
||
break;
|
||
default:
|
||
responseValue = KeyHandler.getCodeFromTermcap(trans, isDecsetInternalBitSet(DECSET_BIT_APPLICATION_CURSOR_KEYS),
|
||
isDecsetInternalBitSet(DECSET_BIT_APPLICATION_KEYPAD));
|
||
break;
|
||
}
|
||
if (responseValue == null) {
|
||
switch (trans) {
|
||
case "%1": // Help key - ignore
|
||
case "&8": // Undo key - ignore.
|
||
break;
|
||
default:
|
||
Logger.logWarn(mClient, LOG_TAG, "Unhandled termcap/terminfo name: '" + trans + "'");
|
||
}
|
||
// Respond with invalid request:
|
||
mSession.write("\033P0+r" + part + "\033\\");
|
||
} else {
|
||
StringBuilder hexEncoded = new StringBuilder();
|
||
for (int j = 0; j < responseValue.length(); j++) {
|
||
hexEncoded.append(String.format("%02X", (int) responseValue.charAt(j)));
|
||
}
|
||
mSession.write("\033P1+r" + part + "=" + hexEncoded + "\033\\");
|
||
}
|
||
} else {
|
||
Logger.logError(mClient, LOG_TAG, "Invalid device termcap/terminfo name of odd length: " + part);
|
||
}
|
||
}
|
||
} else {
|
||
if (LOG_ESCAPE_SEQUENCES)
|
||
Logger.logError(mClient, LOG_TAG, "Unrecognized device control string: " + dcs);
|
||
}
|
||
finishSequence();
|
||
}
|
||
break;
|
||
default:
|
||
if (mOSCOrDeviceControlArgs.length() > MAX_OSC_STRING_LENGTH) {
|
||
// Too long.
|
||
mOSCOrDeviceControlArgs.setLength(0);
|
||
finishSequence();
|
||
} else {
|
||
mOSCOrDeviceControlArgs.appendCodePoint(b);
|
||
continueSequence(mEscapeState);
|
||
}
|
||
}
|
||
}
|
||
|
||
private int nextTabStop(int numTabs) {
|
||
for (int i = mCursorCol + 1; i < mColumns; i++)
|
||
if (mTabStop[i] && --numTabs == 0) return Math.min(i, mRightMargin);
|
||
return mRightMargin - 1;
|
||
}
|
||
|
||
/** Process byte while in the {@link #ESC_CSI_QUESTIONMARK} escape state. */
|
||
private void doCsiQuestionMark(int b) {
|
||
switch (b) {
|
||
case 'J': // Selective erase in display (DECSED) - http://www.vt100.net/docs/vt510-rm/DECSED.
|
||
case 'K': // Selective erase in line (DECSEL) - http://vt100.net/docs/vt510-rm/DECSEL.
|
||
mAboutToAutoWrap = false;
|
||
int fillChar = ' ';
|
||
int startCol = -1;
|
||
int startRow = -1;
|
||
int endCol = -1;
|
||
int endRow = -1;
|
||
boolean justRow = (b == 'K');
|
||
switch (getArg0(0)) {
|
||
case 0: // Erase from the active position to the end, inclusive (default).
|
||
startCol = mCursorCol;
|
||
startRow = mCursorRow;
|
||
endCol = mColumns;
|
||
endRow = justRow ? (mCursorRow + 1) : mRows;
|
||
break;
|
||
case 1: // Erase from start to the active position, inclusive.
|
||
startCol = 0;
|
||
startRow = justRow ? mCursorRow : 0;
|
||
endCol = mCursorCol + 1;
|
||
endRow = mCursorRow + 1;
|
||
break;
|
||
case 2: // Erase all of the display/line.
|
||
startCol = 0;
|
||
startRow = justRow ? mCursorRow : 0;
|
||
endCol = mColumns;
|
||
endRow = justRow ? (mCursorRow + 1) : mRows;
|
||
break;
|
||
default:
|
||
unknownSequence(b);
|
||
break;
|
||
}
|
||
long style = getStyle();
|
||
for (int row = startRow; row < endRow; row++) {
|
||
for (int col = startCol; col < endCol; col++) {
|
||
if ((TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0)
|
||
mScreen.setChar(col, row, fillChar, style);
|
||
}
|
||
}
|
||
break;
|
||
case 'h':
|
||
case 'l':
|
||
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||
for (int i = 0; i <= mArgIndex; i++)
|
||
doDecSetOrReset(b == 'h', mArgs[i]);
|
||
break;
|
||
case 'n': // Device Status Report (DSR, DEC-specific).
|
||
switch (getArg0(-1)) {
|
||
case 6:
|
||
// Extended Cursor Position (DECXCPR - http://www.vt100.net/docs/vt510-rm/DECXCPR). Page=1.
|
||
mSession.write(String.format(Locale.US, "\033[?%d;%d;1R", mCursorRow + 1, mCursorCol + 1));
|
||
break;
|
||
default:
|
||
finishSequence();
|
||
return;
|
||
}
|
||
break;
|
||
case 'r':
|
||
case 's':
|
||
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||
for (int i = 0; i <= mArgIndex; i++) {
|
||
int externalBit = mArgs[i];
|
||
int internalBit = mapDecSetBitToInternalBit(externalBit);
|
||
if (internalBit == -1) {
|
||
Logger.logWarn(mClient, LOG_TAG, "Ignoring request to save/recall decset bit=" + externalBit);
|
||
} else {
|
||
if (b == 's') {
|
||
mSavedDecSetFlags |= internalBit;
|
||
} else {
|
||
doDecSetOrReset((mSavedDecSetFlags & internalBit) != 0, externalBit);
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
case '$':
|
||
continueSequence(ESC_CSI_QUESTIONMARK_ARG_DOLLAR);
|
||
return;
|
||
default:
|
||
parseArg(b);
|
||
}
|
||
}
|
||
|
||
public void doDecSetOrReset(boolean setting, int externalBit) {
|
||
int internalBit = mapDecSetBitToInternalBit(externalBit);
|
||
if (internalBit != -1) {
|
||
setDecsetinternalBit(internalBit, setting);
|
||
}
|
||
switch (externalBit) {
|
||
case 1: // Application Cursor Keys (DECCKM).
|
||
break;
|
||
case 3: // Set: 132 column mode (. Reset: 80 column mode. ANSI name: DECCOLM.
|
||
// We don't actually set/reset 132 cols, but we do want the side effects
|
||
// (FIXME: Should only do this if the 95 DECSET bit (DECNCSM) is set, and if changing value?):
|
||
// Sets the left, right, top and bottom scrolling margins to their default positions, which is important for
|
||
// the "reset" utility to really reset the terminal:
|
||
mLeftMargin = mTopMargin = 0;
|
||
mBottomMargin = mRows;
|
||
mRightMargin = mColumns;
|
||
// "DECCOLM resets vertical split screen mode (DECLRMM) to unavailable":
|
||
setDecsetinternalBit(DECSET_BIT_LEFTRIGHT_MARGIN_MODE, false);
|
||
// "Erases all data in page memory":
|
||
blockClear(0, 0, mColumns, mRows);
|
||
setCursorRowCol(0, 0);
|
||
break;
|
||
case 4: // DECSCLM-Scrolling Mode. Ignore.
|
||
break;
|
||
case 5: // Reverse video. No action.
|
||
break;
|
||
case 6: // Set: Origin Mode. Reset: Normal Cursor Mode. Ansi name: DECOM.
|
||
if (setting) setCursorPosition(0, 0);
|
||
break;
|
||
case 7: // Wrap-around bit, not specific action.
|
||
case 8: // Auto-repeat Keys (DECARM). Do not implement.
|
||
case 9: // X10 mouse reporting - outdated. Do not implement.
|
||
case 12: // Control cursor blinking - ignore.
|
||
case 25: // Hide/show cursor - no action needed, renderer will check with shouldCursorBeVisible().
|
||
if (mClient != null)
|
||
mClient.onTerminalCursorStateChange(setting);
|
||
break;
|
||
case 40: // Allow 80 => 132 Mode, ignore.
|
||
case 45: // TODO: Reverse wrap-around. Implement???
|
||
case 66: // Application keypad (DECNKM).
|
||
break;
|
||
case 69: // Left and right margin mode (DECLRMM).
|
||
if (!setting) {
|
||
mLeftMargin = 0;
|
||
mRightMargin = mColumns;
|
||
}
|
||
break;
|
||
case 1000:
|
||
case 1001:
|
||
case 1002:
|
||
case 1003:
|
||
case 1004:
|
||
case 1005: // UTF-8 mouse mode, ignore.
|
||
case 1006: // SGR Mouse Mode
|
||
case 1015:
|
||
case 1034: // Interpret "meta" key, sets eighth bit.
|
||
break;
|
||
case 1048: // Set: Save cursor as in DECSC. Reset: Restore cursor as in DECRC.
|
||
if (setting)
|
||
saveCursor();
|
||
else
|
||
restoreCursor();
|
||
break;
|
||
case 47:
|
||
case 1047:
|
||
case 1049: {
|
||
// Set: Save cursor as in DECSC and use Alternate Screen Buffer, clearing it first.
|
||
// Reset: Use Normal Screen Buffer and restore cursor as in DECRC.
|
||
TerminalBuffer newScreen = setting ? mAltBuffer : mMainBuffer;
|
||
if (newScreen != mScreen) {
|
||
boolean resized = !(newScreen.mColumns == mColumns && newScreen.mScreenRows == mRows);
|
||
if (setting) saveCursor();
|
||
mScreen = newScreen;
|
||
if (!setting) {
|
||
int col = mSavedStateMain.mSavedCursorCol;
|
||
int row = mSavedStateMain.mSavedCursorRow;
|
||
restoreCursor();
|
||
if (resized) {
|
||
// Restore cursor position _not_ clipped to current screen (let resizeScreen() handle that):
|
||
mCursorCol = col;
|
||
mCursorRow = row;
|
||
}
|
||
}
|
||
// Check if buffer size needs to be updated:
|
||
if (resized) resizeScreen();
|
||
// Clear new screen if alt buffer:
|
||
if (newScreen == mAltBuffer)
|
||
newScreen.blockSet(0, 0, mColumns, mRows, ' ', getStyle());
|
||
}
|
||
break;
|
||
}
|
||
case 2004:
|
||
// Bracketed paste mode - setting bit is enough.
|
||
break;
|
||
default:
|
||
unknownParameter(externalBit);
|
||
break;
|
||
}
|
||
}
|
||
|
||
private void doCsiBiggerThan(int b) {
|
||
switch (b) {
|
||
case 'c': // "${CSI}>c" or "${CSI}>c". Secondary Device Attributes (DA2).
|
||
// Originally this was used for the terminal to respond with "identification code, firmware version level,
|
||
// and hardware options" (http://vt100.net/docs/vt510-rm/DA2), with the first "41" meaning the VT420
|
||
// terminal type. This is not used anymore, but the second version level field has been changed by xterm
|
||
// to mean it's release number ("patch numbers" listed at http://invisible-island.net/xterm/xterm.log.html),
|
||
// and some applications use it as a feature check:
|
||
// * tmux used to have a "xterm won't reach version 500 for a while so set that as the upper limit" check,
|
||
// and then check "xterm_version > 270" if rectangular area operations such as DECCRA could be used.
|
||
// * vim checks xterm version number >140 for "Request termcap/terminfo string" functionality >276 for SGR
|
||
// mouse report.
|
||
// The third number is a keyboard identifier not used nowadays.
|
||
mSession.write("\033[>41;320;0c");
|
||
break;
|
||
case 'm':
|
||
// https://bugs.launchpad.net/gnome-terminal/+bug/96676/comments/25
|
||
// Depending on the first number parameter, this can set one of the xterm resources
|
||
// modifyKeyboard, modifyCursorKeys, modifyFunctionKeys and modifyOtherKeys.
|
||
// http://invisible-island.net/xterm/manpage/xterm.html#RESOURCES
|
||
|
||
// * modifyKeyboard (parameter=1):
|
||
// Normally xterm makes a special case regarding modifiers (shift, control, etc.) to handle special keyboard
|
||
// layouts (legacy and vt220). This is done to provide compatible keyboards for DEC VT220 and related
|
||
// terminals that implement user-defined keys (UDK).
|
||
// The bits of the resource value selectively enable modification of the given category when these keyboards
|
||
// are selected. The default is "0":
|
||
// (0) The legacy/vt220 keyboards interpret only the Control-modifier when constructing numbered
|
||
// function-keys. Other special keys are not modified.
|
||
// (1) allows modification of the numeric keypad
|
||
// (2) allows modification of the editing keypad
|
||
// (4) allows modification of function-keys, overrides use of Shift-modifier for UDK.
|
||
// (8) allows modification of other special keys
|
||
|
||
// * modifyCursorKeys (parameter=2):
|
||
// Tells how to handle the special case where Control-, Shift-, Alt- or Meta-modifiers are used to add a
|
||
// parameter to the escape sequence returned by a cursor-key. The default is "2".
|
||
// - Set it to -1 to disable it.
|
||
// - Set it to 0 to use the old/obsolete behavior.
|
||
// - Set it to 1 to prefix modified sequences with CSI.
|
||
// - Set it to 2 to force the modifier to be the second parameter if it would otherwise be the first.
|
||
// - Set it to 3 to mark the sequence with a ">" to hint that it is private.
|
||
|
||
// * modifyFunctionKeys (parameter=3):
|
||
// Tells how to handle the special case where Control-, Shift-, Alt- or Meta-modifiers are used to add a
|
||
// parameter to the escape sequence returned by a (numbered) function-
|
||
// key. The default is "2". The resource values are similar to modifyCursorKeys:
|
||
// Set it to -1 to permit the user to use shift- and control-modifiers to construct function-key strings
|
||
// using the normal encoding scheme.
|
||
// - Set it to 0 to use the old/obsolete behavior.
|
||
// - Set it to 1 to prefix modified sequences with CSI.
|
||
// - Set it to 2 to force the modifier to be the second parameter if it would otherwise be the first.
|
||
// - Set it to 3 to mark the sequence with a ">" to hint that it is private.
|
||
// If modifyFunctionKeys is zero, xterm uses Control- and Shift-modifiers to allow the user to construct
|
||
// numbered function-keys beyond the set provided by the keyboard:
|
||
// (Control) adds the value given by the ctrlFKeys resource.
|
||
// (Shift) adds twice the value given by the ctrlFKeys resource.
|
||
// (Control/Shift) adds three times the value given by the ctrlFKeys resource.
|
||
//
|
||
// As a special case, legacy (when oldFunctionKeys is true) or vt220 (when sunKeyboard is true)
|
||
// keyboards interpret only the Control-modifier when constructing numbered function-keys.
|
||
// This is done to provide compatible keyboards for DEC VT220 and related terminals that
|
||
// implement user-defined keys (UDK).
|
||
|
||
// * modifyOtherKeys (parameter=4):
|
||
// Like modifyCursorKeys, tells xterm to construct an escape sequence for other keys (such as "2") when
|
||
// modified by Control-, Alt- or Meta-modifiers. This feature does not apply to function keys and
|
||
// well-defined keys such as ESC or the control keys. The default is "0".
|
||
// (0) disables this feature.
|
||
// (1) enables this feature for keys except for those with well-known behavior, e.g., Tab, Backarrow and
|
||
// some special control character cases, e.g., Control-Space to make a NUL.
|
||
// (2) enables this feature for keys including the exceptions listed.
|
||
Logger.logError(mClient, LOG_TAG, "(ignored) CSI > MODIFY RESOURCE: " + getArg0(-1) + " to " + getArg1(-1));
|
||
break;
|
||
default:
|
||
parseArg(b);
|
||
break;
|
||
}
|
||
}
|
||
|
||
private void startEscapeSequence() {
|
||
mEscapeState = ESC;
|
||
mArgIndex = 0;
|
||
Arrays.fill(mArgs, -1);
|
||
}
|
||
|
||
private void doLinefeed() {
|
||
boolean belowScrollingRegion = mCursorRow >= mBottomMargin;
|
||
int newCursorRow = mCursorRow + 1;
|
||
if (belowScrollingRegion) {
|
||
// Move down (but not scroll) as long as we are above the last row.
|
||
if (mCursorRow != mRows - 1) {
|
||
setCursorRow(newCursorRow);
|
||
}
|
||
} else {
|
||
if (newCursorRow == mBottomMargin) {
|
||
scrollDownOneLine();
|
||
newCursorRow = mBottomMargin - 1;
|
||
}
|
||
setCursorRow(newCursorRow);
|
||
}
|
||
}
|
||
|
||
private void continueSequence(int state) {
|
||
mEscapeState = state;
|
||
mContinueSequence = true;
|
||
}
|
||
|
||
private void doEscPound(int b) {
|
||
switch (b) {
|
||
case '8': // Esc # 8 - DEC screen alignment test - fill screen with E's.
|
||
mScreen.blockSet(0, 0, mColumns, mRows, 'E', getStyle());
|
||
break;
|
||
default:
|
||
unknownSequence(b);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/** Encountering a character in the {@link #ESC} state. */
|
||
private void doEsc(int b) {
|
||
switch (b) {
|
||
case '#':
|
||
continueSequence(ESC_POUND);
|
||
break;
|
||
case '(':
|
||
continueSequence(ESC_SELECT_LEFT_PAREN);
|
||
break;
|
||
case ')':
|
||
continueSequence(ESC_SELECT_RIGHT_PAREN);
|
||
break;
|
||
case '6': // Back index (http://www.vt100.net/docs/vt510-rm/DECBI). Move left, insert blank column if start.
|
||
if (mCursorCol > mLeftMargin) {
|
||
mCursorCol--;
|
||
} else {
|
||
int rows = mBottomMargin - mTopMargin;
|
||
mScreen.blockCopy(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin - 1, rows, mLeftMargin + 1, mTopMargin);
|
||
mScreen.blockSet(mLeftMargin, mTopMargin, 1, rows, ' ', TextStyle.encode(mForeColor, mBackColor, 0));
|
||
}
|
||
break;
|
||
case '7': // DECSC save cursor - http://www.vt100.net/docs/vt510-rm/DECSC
|
||
saveCursor();
|
||
break;
|
||
case '8': // DECRC restore cursor - http://www.vt100.net/docs/vt510-rm/DECRC
|
||
restoreCursor();
|
||
break;
|
||
case '9': // Forward Index (http://www.vt100.net/docs/vt510-rm/DECFI). Move right, insert blank column if end.
|
||
if (mCursorCol < mRightMargin - 1) {
|
||
mCursorCol++;
|
||
} else {
|
||
int rows = mBottomMargin - mTopMargin;
|
||
mScreen.blockCopy(mLeftMargin + 1, mTopMargin, mRightMargin - mLeftMargin - 1, rows, mLeftMargin, mTopMargin);
|
||
mScreen.blockSet(mRightMargin - 1, mTopMargin, 1, rows, ' ', TextStyle.encode(mForeColor, mBackColor, 0));
|
||
}
|
||
break;
|
||
case 'c': // RIS - Reset to Initial State (http://vt100.net/docs/vt510-rm/RIS).
|
||
reset();
|
||
mMainBuffer.clearTranscript();
|
||
blockClear(0, 0, mColumns, mRows);
|
||
setCursorPosition(0, 0);
|
||
break;
|
||
case 'D': // INDEX
|
||
doLinefeed();
|
||
break;
|
||
case 'E': // Next line (http://www.vt100.net/docs/vt510-rm/NEL).
|
||
setCursorCol(isDecsetInternalBitSet(DECSET_BIT_ORIGIN_MODE) ? mLeftMargin : 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': // "${ESC}M" - reverse index (RI).
|
||
// http://www.vt100.net/docs/vt100-ug/chapter3.html: "Move the active position to the same horizontal
|
||
// position on the preceding line. If the active position is at the top margin, a scroll down is performed".
|
||
if (mCursorRow <= mTopMargin) {
|
||
mScreen.blockCopy(0, mTopMargin, mColumns, mBottomMargin - (mTopMargin + 1), 0, mTopMargin + 1);
|
||
blockClear(0, mTopMargin, mColumns);
|
||
} else {
|
||
mCursorRow--;
|
||
}
|
||
break;
|
||
case 'N': // SS2, ignore.
|
||
case '0': // SS3, ignore.
|
||
break;
|
||
case 'P': // Device control string
|
||
mOSCOrDeviceControlArgs.setLength(0);
|
||
continueSequence(ESC_P);
|
||
break;
|
||
case '[':
|
||
continueSequence(ESC_CSI);
|
||
mIsCSIStart = true;
|
||
mLastCSIArg = null;
|
||
break;
|
||
case '=': // DECKPAM
|
||
setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, true);
|
||
break;
|
||
case ']': // OSC
|
||
mOSCOrDeviceControlArgs.setLength(0);
|
||
continueSequence(ESC_OSC);
|
||
break;
|
||
case '>': // DECKPNM
|
||
setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, false);
|
||
break;
|
||
default:
|
||
unknownSequence(b);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/** DECSC save cursor - http://www.vt100.net/docs/vt510-rm/DECSC . See {@link #restoreCursor()}. */
|
||
private void saveCursor() {
|
||
SavedScreenState state = (mScreen == mMainBuffer) ? mSavedStateMain : mSavedStateAlt;
|
||
state.mSavedCursorRow = mCursorRow;
|
||
state.mSavedCursorCol = mCursorCol;
|
||
state.mSavedEffect = mEffect;
|
||
state.mSavedForeColor = mForeColor;
|
||
state.mSavedBackColor = mBackColor;
|
||
state.mSavedDecFlags = mCurrentDecSetFlags;
|
||
state.mUseLineDrawingG0 = mUseLineDrawingG0;
|
||
state.mUseLineDrawingG1 = mUseLineDrawingG1;
|
||
state.mUseLineDrawingUsesG0 = mUseLineDrawingUsesG0;
|
||
}
|
||
|
||
/** DECRS restore cursor - http://www.vt100.net/docs/vt510-rm/DECRC. See {@link #saveCursor()}. */
|
||
private void restoreCursor() {
|
||
SavedScreenState state = (mScreen == mMainBuffer) ? mSavedStateMain : mSavedStateAlt;
|
||
setCursorRowCol(state.mSavedCursorRow, state.mSavedCursorCol);
|
||
mEffect = state.mSavedEffect;
|
||
mForeColor = state.mSavedForeColor;
|
||
mBackColor = state.mSavedBackColor;
|
||
int mask = (DECSET_BIT_AUTOWRAP | DECSET_BIT_ORIGIN_MODE);
|
||
mCurrentDecSetFlags = (mCurrentDecSetFlags & ~mask) | (state.mSavedDecFlags & mask);
|
||
mUseLineDrawingG0 = state.mUseLineDrawingG0;
|
||
mUseLineDrawingG1 = state.mUseLineDrawingG1;
|
||
mUseLineDrawingUsesG0 = state.mUseLineDrawingUsesG0;
|
||
}
|
||
|
||
/** Following a CSI - Control Sequence Introducer, "\033[". {@link #ESC_CSI}. */
|
||
private void doCsi(int b) {
|
||
switch (b) {
|
||
case '!':
|
||
continueSequence(ESC_CSI_EXCLAMATION);
|
||
break;
|
||
case '"':
|
||
continueSequence(ESC_CSI_DOUBLE_QUOTE);
|
||
break;
|
||
case '\'':
|
||
continueSequence(ESC_CSI_SINGLE_QUOTE);
|
||
break;
|
||
case '$':
|
||
continueSequence(ESC_CSI_DOLLAR);
|
||
break;
|
||
case '*':
|
||
continueSequence(ESC_CSI_ARGS_ASTERIX);
|
||
break;
|
||
case '@': {
|
||
// "CSI{n}@" - Insert ${n} space characters (ICH) - http://www.vt100.net/docs/vt510-rm/ICH.
|
||
mAboutToAutoWrap = false;
|
||
int columnsAfterCursor = mColumns - mCursorCol;
|
||
int spacesToInsert = Math.min(getArg0(1), columnsAfterCursor);
|
||
int charsToMove = columnsAfterCursor - spacesToInsert;
|
||
mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1, mCursorCol + spacesToInsert, mCursorRow);
|
||
blockClear(mCursorCol, mCursorRow, spacesToInsert);
|
||
}
|
||
break;
|
||
case 'A': // "CSI${n}A" - Cursor up (CUU) ${n} rows.
|
||
setCursorRow(Math.max(0, mCursorRow - getArg0(1)));
|
||
break;
|
||
case 'B': // "CSI${n}B" - Cursor down (CUD) ${n} rows.
|
||
setCursorRow(Math.min(mRows - 1, mCursorRow + getArg0(1)));
|
||
break;
|
||
case 'C': // "CSI${n}C" - Cursor forward (CUF).
|
||
case 'a': // "CSI${n}a" - Horizontal position relative (HPR). From ISO-6428/ECMA-48.
|
||
setCursorCol(Math.min(mRightMargin - 1, mCursorCol + getArg0(1)));
|
||
break;
|
||
case 'D': // "CSI${n}D" - Cursor backward (CUB) ${n} columns.
|
||
setCursorCol(Math.max(mLeftMargin, mCursorCol - getArg0(1)));
|
||
break;
|
||
case 'E': // "CSI{n}E - Cursor Next Line (CNL). From ISO-6428/ECMA-48.
|
||
setCursorPosition(0, mCursorRow + getArg0(1));
|
||
break;
|
||
case 'F': // "CSI{n}F - Cursor Previous Line (CPL). From ISO-6428/ECMA-48.
|
||
setCursorPosition(0, mCursorRow - getArg0(1));
|
||
break;
|
||
case 'G': // "CSI${n}G" - Cursor horizontal absolute (CHA) to column ${n}.
|
||
setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1);
|
||
break;
|
||
case 'H': // "${CSI}${ROW};${COLUMN}H" - Cursor position (CUP).
|
||
case 'f': // "${CSI}${ROW};${COLUMN}f" - Horizontal and Vertical Position (HVP).
|
||
setCursorPosition(getArg1(1) - 1, getArg0(1) - 1);
|
||
break;
|
||
case 'I': // Cursor Horizontal Forward Tabulation (CHT). Move the active position n tabs forward.
|
||
setCursorCol(nextTabStop(getArg0(1)));
|
||
break;
|
||
case 'J': // "${CSI}${0,1,2,3}J" - Erase in Display (ED)
|
||
// ED ignores the scrolling margins.
|
||
switch (getArg0(0)) {
|
||
case 0: // Erase from the active position to the end of the screen, inclusive (default).
|
||
blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
|
||
blockClear(0, mCursorRow + 1, mColumns, mRows - (mCursorRow + 1));
|
||
break;
|
||
case 1: // Erase from start of the screen to the active position, inclusive.
|
||
blockClear(0, 0, mColumns, mCursorRow);
|
||
blockClear(0, mCursorRow, mCursorCol + 1);
|
||
break;
|
||
case 2: // Erase all of the display - all lines are erased, changed to single-width, and the cursor does not
|
||
// move..
|
||
blockClear(0, 0, mColumns, mRows);
|
||
break;
|
||
case 3: // Delete all lines saved in the scrollback buffer (xterm etc)
|
||
mMainBuffer.clearTranscript();
|
||
break;
|
||
default:
|
||
unknownSequence(b);
|
||
return;
|
||
}
|
||
mAboutToAutoWrap = false;
|
||
break;
|
||
case 'K': // "CSI{n}K" - Erase in line (EL).
|
||
switch (getArg0(0)) {
|
||
case 0: // Erase from the cursor to the end of the line, inclusive (default)
|
||
blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
|
||
break;
|
||
case 1: // Erase from the start of the screen to the cursor, inclusive.
|
||
blockClear(0, mCursorRow, mCursorCol + 1);
|
||
break;
|
||
case 2: // Erase all of the line.
|
||
blockClear(0, mCursorRow, mColumns);
|
||
break;
|
||
default:
|
||
unknownSequence(b);
|
||
return;
|
||
}
|
||
mAboutToAutoWrap = false;
|
||
break;
|
||
case 'L': // "${CSI}{N}L" - insert ${N} lines (IL).
|
||
{
|
||
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': // "${CSI}${N}M" - delete N lines (DL).
|
||
{
|
||
mAboutToAutoWrap = false;
|
||
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': // "${CSI}{N}P" - delete ${N} characters (DCH).
|
||
{
|
||
// http://www.vt100.net/docs/vt510-rm/DCH: "If ${N} is greater than the number of characters between the
|
||
// cursor and the right margin, then DCH only deletes the remaining characters.
|
||
// As characters are deleted, the remaining characters between the cursor and right margin move to the left.
|
||
// Character attributes move with the characters. The terminal adds blank spaces with no visual character
|
||
// attributes at the right margin. DCH has no effect outside the scrolling margins."
|
||
mAboutToAutoWrap = false;
|
||
int cellsAfterCursor = mColumns - mCursorCol;
|
||
int cellsToDelete = Math.min(getArg0(1), cellsAfterCursor);
|
||
int cellsToMove = cellsAfterCursor - cellsToDelete;
|
||
mScreen.blockCopy(mCursorCol + cellsToDelete, mCursorRow, cellsToMove, 1, mCursorCol, mCursorRow);
|
||
blockClear(mCursorCol + cellsToMove, mCursorRow, cellsToDelete);
|
||
}
|
||
break;
|
||
case 'S': { // "${CSI}${N}S" - scroll up ${N} lines (default = 1) (SU).
|
||
final int linesToScroll = getArg0(1);
|
||
for (int i = 0; i < linesToScroll; i++)
|
||
scrollDownOneLine();
|
||
break;
|
||
}
|
||
case 'T':
|
||
if (mArgIndex == 0) {
|
||
// "${CSI}${N}T" - Scroll down N lines (default = 1) (SD).
|
||
// http://vt100.net/docs/vt510-rm/SD: "N is the number of lines to move the user window up in page
|
||
// memory. N new lines appear at the top of the display. N old lines disappear at the bottom of the
|
||
// display. You cannot pan past the top margin of the current page".
|
||
final int linesToScrollArg = getArg0(1);
|
||
final int linesBetweenTopAndBottomMargins = mBottomMargin - mTopMargin;
|
||
final int linesToScroll = Math.min(linesBetweenTopAndBottomMargins, linesToScrollArg);
|
||
mScreen.blockCopy(0, mTopMargin, mColumns, linesBetweenTopAndBottomMargins - linesToScroll, 0, mTopMargin + linesToScroll);
|
||
blockClear(0, mTopMargin, mColumns, linesToScroll);
|
||
} else {
|
||
// "${CSI}${func};${startx};${starty};${firstrow};${lastrow}T" - initiate highlight mouse tracking.
|
||
unimplementedSequence(b);
|
||
}
|
||
break;
|
||
case 'X': // "${CSI}${N}X" - Erase ${N:=1} character(s) (ECH). FIXME: Clears character attributes?
|
||
mAboutToAutoWrap = false;
|
||
mScreen.blockSet(mCursorCol, mCursorRow, Math.min(getArg0(1), mColumns - mCursorCol), 1, ' ', getStyle());
|
||
break;
|
||
case 'Z': // Cursor Backward Tabulation (CBT). Move the active position n tabs backward.
|
||
int numberOfTabs = getArg0(1);
|
||
int newCol = mLeftMargin;
|
||
for (int i = mCursorCol - 1; i >= 0; i--)
|
||
if (mTabStop[i]) {
|
||
if (--numberOfTabs == 0) {
|
||
newCol = Math.max(i, mLeftMargin);
|
||
break;
|
||
}
|
||
}
|
||
mCursorCol = newCol;
|
||
break;
|
||
case '?': // Esc [ ? -- start of a private mode set
|
||
continueSequence(ESC_CSI_QUESTIONMARK);
|
||
break;
|
||
case '>': // "Esc [ >" --
|
||
continueSequence(ESC_CSI_BIGGERTHAN);
|
||
break;
|
||
case '`': // Horizontal position absolute (HPA - http://www.vt100.net/docs/vt510-rm/HPA).
|
||
setCursorColRespectingOriginMode(getArg0(1) - 1);
|
||
break;
|
||
case 'b': // Repeat the preceding graphic character Ps times (REP).
|
||
if (mLastEmittedCodePoint == -1) break;
|
||
final int numRepeat = getArg0(1);
|
||
for (int i = 0; i < numRepeat; i++) emitCodePoint(mLastEmittedCodePoint);
|
||
break;
|
||
case 'c': // Primary Device Attributes (http://www.vt100.net/docs/vt510-rm/DA1) if argument is missing or zero.
|
||
// The important part that may still be used by some (tmux stores this value but does not currently use it)
|
||
// is the first response parameter identifying the terminal service class, where we send 64 for "vt420".
|
||
// This is followed by a list of attributes which is probably unused by applications. Send like xterm.
|
||
if (getArg0(0) == 0) mSession.write("\033[?64;1;2;6;9;15;18;21;22c");
|
||
break;
|
||
case 'd': // ESC [ Pn d - Vert Position Absolute
|
||
setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1);
|
||
break;
|
||
case 'e': // Vertical Position Relative (VPR). From ISO-6429 (ECMA-48).
|
||
setCursorPosition(mCursorCol, mCursorRow + getArg0(1));
|
||
break;
|
||
// case 'f': "${CSI}${ROW};${COLUMN}f" - Horizontal and Vertical Position (HVP). Grouped with case 'H'.
|
||
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.
|
||
mSession.write(String.format(Locale.US, "\033[%d;%dR", mCursorRow + 1, mCursorCol + 1));
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
break;
|
||
case 'r': // "CSI${top};${bottom}r" - set top and bottom Margins (DECSTBM).
|
||
{
|
||
// https://vt100.net/docs/vt510-rm/DECSTBM.html
|
||
// The top margin defaults to 1, the bottom margin 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.
|
||
mTopMargin = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
|
||
mBottomMargin = Math.max(mTopMargin + 2, Math.min(getArg1(mRows), mRows));
|
||
|
||
// DECSTBM moves the cursor to column 1, line 1 of the page respecting origin mode.
|
||
setCursorPosition(0, 0);
|
||
}
|
||
break;
|
||
case 's':
|
||
if (isDecsetInternalBitSet(DECSET_BIT_LEFTRIGHT_MARGIN_MODE)) {
|
||
// Set left and right margins (DECSLRM - http://www.vt100.net/docs/vt510-rm/DECSLRM).
|
||
mLeftMargin = Math.min(getArg0(1) - 1, mColumns - 2);
|
||
mRightMargin = Math.max(mLeftMargin + 1, Math.min(getArg1(mColumns), mColumns));
|
||
// DECSLRM moves the cursor to column 1, line 1 of the page.
|
||
setCursorPosition(0, 0);
|
||
} else {
|
||
// Save cursor (ANSI.SYS), available only when DECLRMM is disabled.
|
||
saveCursor();
|
||
}
|
||
break;
|
||
case 't': // Window manipulation (from dtterm, as well as extensions)
|
||
switch (getArg0(0)) {
|
||
case 11: // Report xterm window state. If the xterm window is open (non-iconified), it returns CSI 1 t .
|
||
mSession.write("\033[1t");
|
||
break;
|
||
case 13: // Report xterm window position. Result is CSI 3 ; x ; y t
|
||
mSession.write("\033[3;0;0t");
|
||
break;
|
||
case 14: // Report xterm window in pixels. Result is CSI 4 ; height ; width t
|
||
// We just report characters time 12 here.
|
||
mSession.write(String.format(Locale.US, "\033[4;%d;%dt", mRows * 12, mColumns * 12));
|
||
break;
|
||
case 18: // Report the size of the text area in characters. Result is CSI 8 ; height ; width t
|
||
mSession.write(String.format(Locale.US, "\033[8;%d;%dt", mRows, mColumns));
|
||
break;
|
||
case 19: // Report the size of the screen in characters. Result is CSI 9 ; height ; width t
|
||
// We report the same size as the view, since it's the view really isn't resizable from the shell.
|
||
mSession.write(String.format(Locale.US, "\033[9;%d;%dt", mRows, mColumns));
|
||
break;
|
||
case 20: // Report xterm windows icon label. Result is OSC L label ST. Disabled due to security concerns:
|
||
mSession.write("\033]LIconLabel\033\\");
|
||
break;
|
||
case 21: // Report xterm windows title. Result is OSC l label ST. Disabled due to security concerns:
|
||
mSession.write("\033]l\033\\");
|
||
break;
|
||
case 22:
|
||
// 22;0 -> Save xterm icon and window title on stack.
|
||
// 22;1 -> Save xterm icon title on stack.
|
||
// 22;2 -> Save xterm window title on stack.
|
||
mTitleStack.push(mTitle);
|
||
if (mTitleStack.size() > 20) {
|
||
// Limit size
|
||
mTitleStack.remove(0);
|
||
}
|
||
break;
|
||
case 23: // Like 22 above but restore from stack.
|
||
if (!mTitleStack.isEmpty()) setTitle(mTitleStack.pop());
|
||
break;
|
||
default:
|
||
// Ignore window manipulation.
|
||
break;
|
||
}
|
||
break;
|
||
case 'u': // Restore cursor (ANSI.SYS).
|
||
restoreCursor();
|
||
break;
|
||
case ' ':
|
||
continueSequence(ESC_CSI_ARGS_SPACE);
|
||
break;
|
||
default:
|
||
parseArg(b);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/** Select Graphic Rendition (SGR) - see http://en.wikipedia.org/wiki/ANSI_escape_code#graphics. */
|
||
private void selectGraphicRendition() {
|
||
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||
for (int i = 0; i <= mArgIndex; i++) {
|
||
int code = mArgs[i];
|
||
if (code < 0) {
|
||
if (mArgIndex > 0) {
|
||
continue;
|
||
} else {
|
||
code = 0;
|
||
}
|
||
}
|
||
if (code == 0) { // reset
|
||
mForeColor = TextStyle.COLOR_INDEX_FOREGROUND;
|
||
mBackColor = TextStyle.COLOR_INDEX_BACKGROUND;
|
||
mEffect = 0;
|
||
} else if (code == 1) {
|
||
mEffect |= TextStyle.CHARACTER_ATTRIBUTE_BOLD;
|
||
} else if (code == 2) {
|
||
mEffect |= TextStyle.CHARACTER_ATTRIBUTE_DIM;
|
||
} else if (code == 3) {
|
||
mEffect |= TextStyle.CHARACTER_ATTRIBUTE_ITALIC;
|
||
} else if (code == 4) {
|
||
mEffect |= TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE;
|
||
} else if (code == 5) {
|
||
mEffect |= TextStyle.CHARACTER_ATTRIBUTE_BLINK;
|
||
} else if (code == 7) {
|
||
mEffect |= TextStyle.CHARACTER_ATTRIBUTE_INVERSE;
|
||
} else if (code == 8) {
|
||
mEffect |= TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE;
|
||
} else if (code == 9) {
|
||
mEffect |= TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH;
|
||
} else if (code == 10) {
|
||
// Exit alt charset (TERM=linux) - ignore.
|
||
} else if (code == 11) {
|
||
// Enter alt charset (TERM=linux) - ignore.
|
||
} else if (code == 22) { // Normal color or intensity, neither bright, bold nor faint.
|
||
mEffect &= ~(TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_DIM);
|
||
} else if (code == 23) { // not italic, but rarely used as such; clears standout with TERM=screen
|
||
mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_ITALIC;
|
||
} else if (code == 24) { // underline: none
|
||
mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE;
|
||
} else if (code == 25) { // blink: none
|
||
mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_BLINK;
|
||
} else if (code == 27) { // image: positive
|
||
mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_INVERSE;
|
||
} else if (code == 28) {
|
||
mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE;
|
||
} else if (code == 29) {
|
||
mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH;
|
||
} else if (code >= 30 && code <= 37) {
|
||
mForeColor = code - 30;
|
||
} else if (code == 38 || code == 48) {
|
||
// Extended set foreground(38)/background (48) color.
|
||
// This is followed by either "2;$R;$G;$B" to set a 24-bit color or
|
||
// "5;$INDEX" to set an indexed color.
|
||
if (i + 2 > mArgIndex) continue;
|
||
int firstArg = mArgs[i + 1];
|
||
if (firstArg == 2) {
|
||
if (i + 4 > mArgIndex) {
|
||
Logger.logWarn(mClient, LOG_TAG, "Too few CSI" + code + ";2 RGB arguments");
|
||
} else {
|
||
int red = mArgs[i + 2], green = mArgs[i + 3], blue = mArgs[i + 4];
|
||
if (red < 0 || green < 0 || blue < 0 || red > 255 || green > 255 || blue > 255) {
|
||
finishSequenceAndLogError("Invalid RGB: " + red + "," + green + "," + blue);
|
||
} else {
|
||
int argbColor = 0xff000000 | (red << 16) | (green << 8) | blue;
|
||
if (code == 38) {
|
||
mForeColor = argbColor;
|
||
} else {
|
||
mBackColor = argbColor;
|
||
}
|
||
}
|
||
i += 4; // "2;P_r;P_g;P_r"
|
||
}
|
||
} else if (firstArg == 5) {
|
||
int color = mArgs[i + 2];
|
||
i += 2; // "5;P_s"
|
||
if (color >= 0 && color < TextStyle.NUM_INDEXED_COLORS) {
|
||
if (code == 38) {
|
||
mForeColor = color;
|
||
} else {
|
||
mBackColor = color;
|
||
}
|
||
} else {
|
||
if (LOG_ESCAPE_SEQUENCES) Logger.logWarn(mClient, LOG_TAG, "Invalid color index: " + color);
|
||
}
|
||
} else {
|
||
finishSequenceAndLogError("Invalid ISO-8613-3 SGR first argument: " + firstArg);
|
||
}
|
||
} else if (code == 39) { // Set default foreground color.
|
||
mForeColor = TextStyle.COLOR_INDEX_FOREGROUND;
|
||
} else if (code >= 40 && code <= 47) { // Set background color.
|
||
mBackColor = code - 40;
|
||
} else if (code == 49) { // Set default background color.
|
||
mBackColor = TextStyle.COLOR_INDEX_BACKGROUND;
|
||
} else if (code >= 90 && code <= 97) { // Bright foreground colors (aixterm codes).
|
||
mForeColor = code - 90 + 8;
|
||
} else if (code >= 100 && code <= 107) { // Bright background color (aixterm codes).
|
||
mBackColor = code - 100 + 8;
|
||
} else {
|
||
if (LOG_ESCAPE_SEQUENCES)
|
||
Logger.logWarn(mClient, LOG_TAG, String.format("SGR unknown code %d", code));
|
||
}
|
||
}
|
||
}
|
||
|
||
private void doOsc(int b) {
|
||
switch (b) {
|
||
case 7: // Bell.
|
||
doOscSetTextParameters("\007");
|
||
break;
|
||
case 27: // Escape.
|
||
continueSequence(ESC_OSC_ESC);
|
||
break;
|
||
default:
|
||
collectOSCArgs(b);
|
||
break;
|
||
}
|
||
}
|
||
|
||
private void doOscEsc(int b) {
|
||
switch (b) {
|
||
case '\\':
|
||
doOscSetTextParameters("\033\\");
|
||
break;
|
||
default:
|
||
// The ESC character was not followed by a \, so insert the ESC and
|
||
// the current character in arg buffer.
|
||
collectOSCArgs(27);
|
||
collectOSCArgs(b);
|
||
continueSequence(ESC_OSC);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/** An Operating System Controls (OSC) Set Text Parameters. May come here from BEL or ST. */
|
||
private void doOscSetTextParameters(String bellOrStringTerminator) {
|
||
int value = -1;
|
||
String textParameter = "";
|
||
// Extract initial $value from initial "$value;..." string.
|
||
for (int mOSCArgTokenizerIndex = 0; mOSCArgTokenizerIndex < mOSCOrDeviceControlArgs.length(); mOSCArgTokenizerIndex++) {
|
||
char b = mOSCOrDeviceControlArgs.charAt(mOSCArgTokenizerIndex);
|
||
if (b == ';') {
|
||
textParameter = mOSCOrDeviceControlArgs.substring(mOSCArgTokenizerIndex + 1);
|
||
break;
|
||
} else if (b >= '0' && b <= '9') {
|
||
value = ((value < 0) ? 0 : value * 10) + (b - '0');
|
||
} else {
|
||
unknownSequence(b);
|
||
return;
|
||
}
|
||
}
|
||
|
||
switch (value) {
|
||
case 0: // Change icon name and window title to T.
|
||
case 1: // Change icon name to T.
|
||
case 2: // Change window title to T.
|
||
setTitle(textParameter);
|
||
break;
|
||
case 4:
|
||
// P s = 4 ; c ; spec → Change Color Number c to the color specified by spec. This can be a name or RGB
|
||
// specification as per XParseColor. Any number of c name pairs may be given. The color numbers correspond
|
||
// to the ANSI colors 0-7, their bright versions 8-15, and if supported, the remainder of the 88-color or
|
||
// 256-color table.
|
||
// If a "?" is given rather than a name or RGB specification, xterm replies with a control sequence of the
|
||
// same form which can be used to set the corresponding color. Because more than one pair of color number
|
||
// and specification can be given in one control sequence, xterm can make more than one reply.
|
||
int colorIndex = -1;
|
||
int parsingPairStart = -1;
|
||
for (int i = 0; ; i++) {
|
||
boolean endOfInput = i == textParameter.length();
|
||
char b = endOfInput ? ';' : textParameter.charAt(i);
|
||
if (b == ';') {
|
||
if (parsingPairStart < 0) {
|
||
parsingPairStart = i + 1;
|
||
} else {
|
||
if (colorIndex < 0 || colorIndex > 255) {
|
||
unknownSequence(b);
|
||
return;
|
||
} else {
|
||
mColors.tryParseColor(colorIndex, textParameter.substring(parsingPairStart, i));
|
||
mSession.onColorsChanged();
|
||
colorIndex = -1;
|
||
parsingPairStart = -1;
|
||
}
|
||
}
|
||
} else if (parsingPairStart >= 0) {
|
||
// We have passed a color index and are now going through color spec.
|
||
} else if (parsingPairStart < 0 && (b >= '0' && b <= '9')) {
|
||
colorIndex = ((colorIndex < 0) ? 0 : colorIndex * 10) + (b - '0');
|
||
} else {
|
||
unknownSequence(b);
|
||
return;
|
||
}
|
||
if (endOfInput) break;
|
||
}
|
||
break;
|
||
case 10: // Set foreground color.
|
||
case 11: // Set background color.
|
||
case 12: // Set cursor color.
|
||
int specialIndex = TextStyle.COLOR_INDEX_FOREGROUND + (value - 10);
|
||
int lastSemiIndex = 0;
|
||
for (int charIndex = 0; ; charIndex++) {
|
||
boolean endOfInput = charIndex == textParameter.length();
|
||
if (endOfInput || textParameter.charAt(charIndex) == ';') {
|
||
try {
|
||
String colorSpec = textParameter.substring(lastSemiIndex, charIndex);
|
||
if ("?".equals(colorSpec)) {
|
||
// Report current color in the same format xterm and gnome-terminal does.
|
||
int rgb = mColors.mCurrentColors[specialIndex];
|
||
int r = (65535 * ((rgb & 0x00FF0000) >> 16)) / 255;
|
||
int g = (65535 * ((rgb & 0x0000FF00) >> 8)) / 255;
|
||
int b = (65535 * ((rgb & 0x000000FF))) / 255;
|
||
mSession.write("\033]" + value + ";rgb:" + String.format(Locale.US, "%04x", r) + "/" + String.format(Locale.US, "%04x", g) + "/"
|
||
+ String.format(Locale.US, "%04x", b) + bellOrStringTerminator);
|
||
} else {
|
||
mColors.tryParseColor(specialIndex, colorSpec);
|
||
mSession.onColorsChanged();
|
||
}
|
||
specialIndex++;
|
||
if (endOfInput || (specialIndex > TextStyle.COLOR_INDEX_CURSOR) || ++charIndex >= textParameter.length())
|
||
break;
|
||
lastSemiIndex = charIndex;
|
||
} catch (NumberFormatException e) {
|
||
// Ignore.
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
case 52: // Manipulate Selection Data. Skip the optional first selection parameter(s).
|
||
int startIndex = textParameter.indexOf(";") + 1;
|
||
try {
|
||
String clipboardText = new String(Base64.decode(textParameter.substring(startIndex), 0), StandardCharsets.UTF_8);
|
||
mSession.onCopyTextToClipboard(clipboardText);
|
||
} catch (Exception e) {
|
||
Logger.logError(mClient, LOG_TAG, "OSC Manipulate selection, invalid string '" + textParameter + "");
|
||
}
|
||
break;
|
||
case 104:
|
||
// "104;$c" → Reset Color Number $c. It is reset to the color specified by the corresponding X
|
||
// resource. Any number of c parameters may be given. These parameters correspond to the ANSI colors 0-7,
|
||
// their bright versions 8-15, and if supported, the remainder of the 88-color or 256-color table. If no
|
||
// parameters are given, the entire table will be reset.
|
||
if (textParameter.isEmpty()) {
|
||
mColors.reset();
|
||
mSession.onColorsChanged();
|
||
} else {
|
||
int lastIndex = 0;
|
||
for (int charIndex = 0; ; charIndex++) {
|
||
boolean endOfInput = charIndex == textParameter.length();
|
||
if (endOfInput || textParameter.charAt(charIndex) == ';') {
|
||
try {
|
||
int colorToReset = Integer.parseInt(textParameter.substring(lastIndex, charIndex));
|
||
mColors.reset(colorToReset);
|
||
mSession.onColorsChanged();
|
||
if (endOfInput) break;
|
||
charIndex++;
|
||
lastIndex = charIndex;
|
||
} catch (NumberFormatException e) {
|
||
// Ignore.
|
||
}
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
case 110: // Reset foreground color.
|
||
case 111: // Reset background color.
|
||
case 112: // Reset cursor color.
|
||
mColors.reset(TextStyle.COLOR_INDEX_FOREGROUND + (value - 110));
|
||
mSession.onColorsChanged();
|
||
break;
|
||
case 119: // Reset highlight color.
|
||
break;
|
||
default:
|
||
unknownParameter(value);
|
||
break;
|
||
}
|
||
finishSequence();
|
||
}
|
||
|
||
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 long getStyle() {
|
||
return TextStyle.encode(mForeColor, mBackColor, mEffect);
|
||
}
|
||
|
||
/** "CSI P_m h" for set or "CSI P_m l" for reset ANSI mode. */
|
||
private void doSetMode(boolean newValue) {
|
||
int modeBit = getArg0(0);
|
||
switch (modeBit) {
|
||
case 4: // Set="Insert Mode". Reset="Replace Mode". (IRM).
|
||
mInsertMode = newValue;
|
||
break;
|
||
case 20: // Normal Linefeed (LNM).
|
||
unknownParameter(modeBit);
|
||
// http://www.vt100.net/docs/vt510-rm/LNM
|
||
break;
|
||
case 34:
|
||
// Normal cursor visibility - when using TERM=screen, see
|
||
// http://www.gnu.org/software/screen/manual/html_node/Control-Sequences.html
|
||
break;
|
||
default:
|
||
unknownParameter(modeBit);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* NOTE: The parameters of this function respect the {@link #DECSET_BIT_ORIGIN_MODE}. Use
|
||
* {@link #setCursorRowCol(int, int)} for absolute pos.
|
||
*/
|
||
private void setCursorPosition(int x, int y) {
|
||
boolean originMode = isDecsetInternalBitSet(DECSET_BIT_ORIGIN_MODE);
|
||
int effectiveTopMargin = originMode ? mTopMargin : 0;
|
||
int effectiveBottomMargin = originMode ? mBottomMargin : mRows;
|
||
int effectiveLeftMargin = originMode ? mLeftMargin : 0;
|
||
int effectiveRightMargin = originMode ? mRightMargin : mColumns;
|
||
int newRow = Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y, effectiveBottomMargin - 1));
|
||
int newCol = Math.max(effectiveLeftMargin, Math.min(effectiveLeftMargin + x, effectiveRightMargin - 1));
|
||
setCursorRowCol(newRow, newCol);
|
||
}
|
||
|
||
private void scrollDownOneLine() {
|
||
mScrollCounter++;
|
||
if (mLeftMargin != 0 || mRightMargin != mColumns) {
|
||
// Horizontal margin: Do not put anything into scroll history, just non-margin part of screen up.
|
||
mScreen.blockCopy(mLeftMargin, mTopMargin + 1, mRightMargin - mLeftMargin, mBottomMargin - mTopMargin - 1, mLeftMargin, mTopMargin);
|
||
// .. and blank bottom row between margins:
|
||
mScreen.blockSet(mLeftMargin, mBottomMargin - 1, mRightMargin - mLeftMargin, 1, ' ', mEffect);
|
||
} else {
|
||
mScreen.scrollDownOneLine(mTopMargin, mBottomMargin, getStyle());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Process the next ASCII character of a parameter.
|
||
*
|
||
* Parameter characters modify the action or interpretation of the sequence. You can use up to
|
||
* 16 parameters per sequence. You must use the ; character to separate parameters.
|
||
* All parameters are unsigned, positive decimal integers, with the most significant
|
||
* digit sent first. Any parameter greater than 9999 (decimal) is set to 9999
|
||
* (decimal). If you do not specify a value, a 0 value is assumed. A 0 value
|
||
* or omitted parameter indicates a default value for the sequence. For most
|
||
* sequences, the default value is 1.
|
||
*
|
||
* https://vt100.net/docs/vt510-rm/chapter4.html#S4.3.3
|
||
* */
|
||
private void parseArg(int inputByte) {
|
||
int[] bytes = new int[]{inputByte};
|
||
// Only doing this for ESC_CSI and not for other ESC_CSI_* since they seem to be using their
|
||
// own defaults with getArg*() calls, but there may be missed cases
|
||
if (mEscapeState == ESC_CSI) {
|
||
if ((mIsCSIStart && inputByte == ';') || // If sequence starts with a ; character, like \033[;m
|
||
(!mIsCSIStart && mLastCSIArg != null && mLastCSIArg == ';' && inputByte == ';')) { // If sequence contains sequential ; characters, like \033[;;m
|
||
bytes = new int[]{'0', ';'}; // Assume 0 was passed
|
||
}
|
||
}
|
||
|
||
mIsCSIStart = false;
|
||
|
||
for (int b : bytes) {
|
||
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;
|
||
}
|
||
if (value > 9999)
|
||
value = 9999;
|
||
mArgs[mArgIndex] = value;
|
||
}
|
||
continueSequence(mEscapeState);
|
||
} else if (b == ';') {
|
||
if (mArgIndex < mArgs.length) {
|
||
mArgIndex++;
|
||
}
|
||
continueSequence(mEscapeState);
|
||
} else {
|
||
unknownSequence(b);
|
||
}
|
||
mLastCSIArg = 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 collectOSCArgs(int b) {
|
||
if (mOSCOrDeviceControlArgs.length() < MAX_OSC_STRING_LENGTH) {
|
||
mOSCOrDeviceControlArgs.appendCodePoint(b);
|
||
continueSequence(mEscapeState);
|
||
} else {
|
||
unknownSequence(b);
|
||
}
|
||
}
|
||
|
||
private void unimplementedSequence(int b) {
|
||
logError("Unimplemented sequence char '" + (char) b + "' (U+" + String.format("%04x", b) + ")");
|
||
finishSequence();
|
||
}
|
||
|
||
private void unknownSequence(int b) {
|
||
logError("Unknown sequence char '" + (char) b + "' (numeric value=" + b + ")");
|
||
finishSequence();
|
||
}
|
||
|
||
private void unknownParameter(int parameter) {
|
||
logError("Unknown parameter: " + parameter);
|
||
finishSequence();
|
||
}
|
||
|
||
private void logError(String errorType) {
|
||
if (LOG_ESCAPE_SEQUENCES) {
|
||
StringBuilder buf = new StringBuilder();
|
||
buf.append(errorType);
|
||
buf.append(", escapeState=");
|
||
buf.append(mEscapeState);
|
||
boolean firstArg = true;
|
||
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||
for (int i = 0; i <= mArgIndex; i++) {
|
||
int value = mArgs[i];
|
||
if (value >= 0) {
|
||
if (firstArg) {
|
||
firstArg = false;
|
||
buf.append(", args={");
|
||
} else {
|
||
buf.append(',');
|
||
}
|
||
buf.append(value);
|
||
}
|
||
}
|
||
if (!firstArg) buf.append('}');
|
||
finishSequenceAndLogError(buf.toString());
|
||
}
|
||
}
|
||
|
||
private void finishSequenceAndLogError(String error) {
|
||
if (LOG_ESCAPE_SEQUENCES) Logger.logWarn(mClient, LOG_TAG, error);
|
||
finishSequence();
|
||
}
|
||
|
||
private void finishSequence() {
|
||
mEscapeState = ESC_NONE;
|
||
}
|
||
|
||
/**
|
||
* Send a Unicode code point to the screen.
|
||
*
|
||
* @param codePoint The code point of the character to display
|
||
*/
|
||
private void emitCodePoint(int codePoint) {
|
||
mLastEmittedCodePoint = codePoint;
|
||
if (mUseLineDrawingUsesG0 ? mUseLineDrawingG0 : mUseLineDrawingG1) {
|
||
// http://www.vt100.net/docs/vt102-ug/table5-15.html.
|
||
switch (codePoint) {
|
||
case '_':
|
||
codePoint = ' '; // Blank.
|
||
break;
|
||
case '`':
|
||
codePoint = '◆'; // Diamond.
|
||
break;
|
||
case '0':
|
||
codePoint = '█'; // Solid block;
|
||
break;
|
||
case 'a':
|
||
codePoint = '▒'; // Checker board.
|
||
break;
|
||
case 'b':
|
||
codePoint = '␉'; // Horizontal tab.
|
||
break;
|
||
case 'c':
|
||
codePoint = '␌'; // Form feed.
|
||
break;
|
||
case 'd':
|
||
codePoint = '\r'; // Carriage return.
|
||
break;
|
||
case 'e':
|
||
codePoint = '␊'; // Linefeed.
|
||
break;
|
||
case 'f':
|
||
codePoint = '°'; // Degree.
|
||
break;
|
||
case 'g':
|
||
codePoint = '±'; // Plus-minus.
|
||
break;
|
||
case 'h':
|
||
codePoint = '\n'; // Newline.
|
||
break;
|
||
case 'i':
|
||
codePoint = '␋'; // Vertical tab.
|
||
break;
|
||
case 'j':
|
||
codePoint = '┘'; // Lower right corner.
|
||
break;
|
||
case 'k':
|
||
codePoint = '┐'; // Upper right corner.
|
||
break;
|
||
case 'l':
|
||
codePoint = '┌'; // Upper left corner.
|
||
break;
|
||
case 'm':
|
||
codePoint = '└'; // Left left corner.
|
||
break;
|
||
case 'n':
|
||
codePoint = '┼'; // Crossing lines.
|
||
break;
|
||
case 'o':
|
||
codePoint = '⎺'; // Horizontal line - scan 1.
|
||
break;
|
||
case 'p':
|
||
codePoint = '⎻'; // Horizontal line - scan 3.
|
||
break;
|
||
case 'q':
|
||
codePoint = '─'; // Horizontal line - scan 5.
|
||
break;
|
||
case 'r':
|
||
codePoint = '⎼'; // Horizontal line - scan 7.
|
||
break;
|
||
case 's':
|
||
codePoint = '⎽'; // Horizontal line - scan 9.
|
||
break;
|
||
case 't':
|
||
codePoint = '├'; // T facing rightwards.
|
||
break;
|
||
case 'u':
|
||
codePoint = '┤'; // T facing leftwards.
|
||
break;
|
||
case 'v':
|
||
codePoint = '┴'; // T facing upwards.
|
||
break;
|
||
case 'w':
|
||
codePoint = '┬'; // T facing downwards.
|
||
break;
|
||
case 'x':
|
||
codePoint = '│'; // Vertical line.
|
||
break;
|
||
case 'y':
|
||
codePoint = '≤'; // Less than or equal to.
|
||
break;
|
||
case 'z':
|
||
codePoint = '≥'; // Greater than or equal to.
|
||
break;
|
||
case '{':
|
||
codePoint = 'π'; // Pi.
|
||
break;
|
||
case '|':
|
||
codePoint = '≠'; // Not equal to.
|
||
break;
|
||
case '}':
|
||
codePoint = '£'; // UK pound.
|
||
break;
|
||
case '~':
|
||
codePoint = '·'; // Centered dot.
|
||
break;
|
||
}
|
||
}
|
||
|
||
final boolean autoWrap = isDecsetInternalBitSet(DECSET_BIT_AUTOWRAP);
|
||
final int displayWidth = WcWidth.width(codePoint);
|
||
final boolean cursorInLastColumn = mCursorCol == mRightMargin - 1;
|
||
|
||
if (autoWrap) {
|
||
if (cursorInLastColumn && ((mAboutToAutoWrap && displayWidth == 1) || displayWidth == 2)) {
|
||
mScreen.setLineWrap(mCursorRow);
|
||
mCursorCol = mLeftMargin;
|
||
if (mCursorRow + 1 < mBottomMargin) {
|
||
mCursorRow++;
|
||
} else {
|
||
scrollDownOneLine();
|
||
}
|
||
}
|
||
} else if (cursorInLastColumn && displayWidth == 2) {
|
||
// The behaviour when a wide character is output with cursor in the last column when
|
||
// autowrap is disabled is not obvious - it's ignored here.
|
||
return;
|
||
}
|
||
|
||
if (mInsertMode && displayWidth > 0) {
|
||
// Move character to right one space.
|
||
int destCol = mCursorCol + displayWidth;
|
||
if (destCol < mRightMargin)
|
||
mScreen.blockCopy(mCursorCol, mCursorRow, mRightMargin - destCol, 1, destCol, mCursorRow);
|
||
}
|
||
|
||
int offsetDueToCombiningChar = ((displayWidth <= 0 && mCursorCol > 0 && !mAboutToAutoWrap) ? 1 : 0);
|
||
int column = mCursorCol - offsetDueToCombiningChar;
|
||
|
||
// Fix TerminalRow.setChar() ArrayIndexOutOfBoundsException index=-1 exception reported
|
||
// The offsetDueToCombiningChar would never be 1 if mCursorCol was 0 to get column/index=-1,
|
||
// so was mCursorCol changed after the offsetDueToCombiningChar conditional by another thread?
|
||
// TODO: Check if there are thread synchronization issues with mCursorCol and mCursorRow, possibly causing others bugs too.
|
||
if (column < 0) column = 0;
|
||
mScreen.setChar(column, mCursorRow, codePoint, getStyle());
|
||
|
||
if (autoWrap && displayWidth > 0)
|
||
mAboutToAutoWrap = (mCursorCol == mRightMargin - displayWidth);
|
||
|
||
mCursorCol = Math.min(mCursorCol + displayWidth, mRightMargin - 1);
|
||
}
|
||
|
||
private void setCursorRow(int row) {
|
||
mCursorRow = row;
|
||
mAboutToAutoWrap = false;
|
||
}
|
||
|
||
private void setCursorCol(int col) {
|
||
mCursorCol = col;
|
||
mAboutToAutoWrap = false;
|
||
}
|
||
|
||
/** Set the cursor mode, but limit it to margins if {@link #DECSET_BIT_ORIGIN_MODE} is enabled. */
|
||
private void setCursorColRespectingOriginMode(int col) {
|
||
setCursorPosition(col, mCursorRow);
|
||
}
|
||
|
||
/** TODO: Better name, distinguished from {@link #setCursorPosition(int, int)} by not regarding origin mode. */
|
||
private void setCursorRowCol(int row, int col) {
|
||
mCursorRow = Math.max(0, Math.min(row, mRows - 1));
|
||
mCursorCol = Math.max(0, Math.min(col, mColumns - 1));
|
||
mAboutToAutoWrap = false;
|
||
}
|
||
|
||
public int getScrollCounter() {
|
||
return mScrollCounter;
|
||
}
|
||
|
||
public void clearScrollCounter() {
|
||
mScrollCounter = 0;
|
||
}
|
||
|
||
public boolean isAutoScrollDisabled() {
|
||
return mAutoScrollDisabled;
|
||
}
|
||
|
||
public void toggleAutoScrollDisabled() {
|
||
mAutoScrollDisabled = !mAutoScrollDisabled;
|
||
}
|
||
|
||
|
||
/** Reset terminal state so user can interact with it regardless of present state. */
|
||
public void reset() {
|
||
setCursorStyle();
|
||
mArgIndex = 0;
|
||
mContinueSequence = false;
|
||
mEscapeState = ESC_NONE;
|
||
mInsertMode = false;
|
||
mTopMargin = mLeftMargin = 0;
|
||
mBottomMargin = mRows;
|
||
mRightMargin = mColumns;
|
||
mAboutToAutoWrap = false;
|
||
mForeColor = mSavedStateMain.mSavedForeColor = mSavedStateAlt.mSavedForeColor = TextStyle.COLOR_INDEX_FOREGROUND;
|
||
mBackColor = mSavedStateMain.mSavedBackColor = mSavedStateAlt.mSavedBackColor = TextStyle.COLOR_INDEX_BACKGROUND;
|
||
setDefaultTabStops();
|
||
|
||
mUseLineDrawingG0 = mUseLineDrawingG1 = false;
|
||
mUseLineDrawingUsesG0 = true;
|
||
|
||
mSavedStateMain.mSavedCursorRow = mSavedStateMain.mSavedCursorCol = mSavedStateMain.mSavedEffect = mSavedStateMain.mSavedDecFlags = 0;
|
||
mSavedStateAlt.mSavedCursorRow = mSavedStateAlt.mSavedCursorCol = mSavedStateAlt.mSavedEffect = mSavedStateAlt.mSavedDecFlags = 0;
|
||
mCurrentDecSetFlags = 0;
|
||
// Initial wrap-around is not accurate but makes terminal more useful, especially on a small screen:
|
||
setDecsetinternalBit(DECSET_BIT_AUTOWRAP, true);
|
||
setDecsetinternalBit(DECSET_BIT_CURSOR_ENABLED, true);
|
||
mSavedDecSetFlags = mSavedStateMain.mSavedDecFlags = mSavedStateAlt.mSavedDecFlags = mCurrentDecSetFlags;
|
||
|
||
// XXX: Should we set terminal driver back to IUTF8 with termios?
|
||
mUtf8Index = mUtf8ToFollow = 0;
|
||
|
||
mColors.reset();
|
||
mSession.onColorsChanged();
|
||
}
|
||
|
||
public String getSelectedText(int x1, int y1, int x2, int y2) {
|
||
return mScreen.getSelectedText(x1, y1, x2, y2);
|
||
}
|
||
|
||
/** Get the terminal session's title (null if not set). */
|
||
public String getTitle() {
|
||
return mTitle;
|
||
}
|
||
|
||
/** Change the terminal session's title. */
|
||
private void setTitle(String newTitle) {
|
||
String oldTitle = mTitle;
|
||
mTitle = newTitle;
|
||
if (!Objects.equals(oldTitle, newTitle)) {
|
||
mSession.titleChanged(oldTitle, newTitle);
|
||
}
|
||
}
|
||
|
||
/** If DECSET 2004 is set, prefix paste with "\033[200~" and suffix with "\033[201~". */
|
||
public void paste(String text) {
|
||
// First: Always remove escape key and C1 control characters [0x80,0x9F]:
|
||
text = text.replaceAll("(\u001B|[\u0080-\u009F])", "");
|
||
// Second: Replace all newlines (\n) or CRLF (\r\n) with carriage returns (\r).
|
||
text = text.replaceAll("\r?\n", "\r");
|
||
|
||
// Then: Implement bracketed paste mode if enabled:
|
||
boolean bracketed = isDecsetInternalBitSet(DECSET_BIT_BRACKETED_PASTE_MODE);
|
||
if (bracketed) mSession.write("\033[200~");
|
||
mSession.write(text);
|
||
if (bracketed) mSession.write("\033[201~");
|
||
}
|
||
|
||
/** http://www.vt100.net/docs/vt510-rm/DECSC */
|
||
static final class SavedScreenState {
|
||
/** Saved state of the cursor position, Used to implement the save/restore cursor position escape sequences. */
|
||
int mSavedCursorRow, mSavedCursorCol;
|
||
int mSavedEffect, mSavedForeColor, mSavedBackColor;
|
||
int mSavedDecFlags;
|
||
boolean mUseLineDrawingG0, mUseLineDrawingG1, mUseLineDrawingUsesG0 = true;
|
||
}
|
||
|
||
@Override
|
||
public String toString() {
|
||
return "TerminalEmulator[size=" + mScreen.mColumns + "x" + mScreen.mScreenRows + ", margins={" + mTopMargin + "," + mRightMargin + "," + mBottomMargin
|
||
+ "," + mLeftMargin + "}]";
|
||
}
|
||
|
||
}
|