package org.gscript.terminal; import java.io.OutputStream; import java.util.ArrayList; import java.util.Iterator; import org.gscript.jni.JNIReference.JNIReferenceException; import org.gscript.settings.ShellProfile; import android.util.Log; public class TerminalEmulator { static final byte[] VT100_ATTRIBUTES = { 27, '[', '?', '1', ';', '2', 'c' }; static final String LOG_TAG = "TerminalEmulator"; static final boolean DEBUG = true; static int LAST_ROWS = 24; static int LAST_COLS = 80; static int LAST_WIDTH = 720; static int LAST_HEIGHT = 1280; private OutputStream mOutputStream; private final ShellProfile mProfile; private final OnEmulatorEventListener mListener; private ScreenBuffer mScreenBuffer; private volatile EscapeSequence mEscapeSequence; int mScreenWidth; int mScreenHeight; Object mInputMutex = new Object(); public TerminalEmulator(ShellProfile profile, OnEmulatorEventListener listener) { /* just some defaults */ mScreenWidth = LAST_WIDTH; mScreenHeight = LAST_HEIGHT; mProfile = profile; mListener = listener; try { int backColor = mProfile.backcolor; int textColor = mProfile.textcolor; /* allocate a new screen buffer */ mScreenBuffer = new ScreenBuffer(LAST_ROWS, LAST_COLS, textColor, backColor); } catch (JNIReferenceException e) { } } public void setOutputStream(OutputStream out) { mOutputStream = out; } public void processInput(byte[] input, int offset, int length) { synchronized(mInputMutex) { for (int index = offset; index < (offset + length); ++index) { processInput(input[index]); } } } void processInput(byte input) { switch (input) { case ControlCharacter.NUL: break; case ControlCharacter.BEL: break; case ControlCharacter.BS: mScreenBuffer.moveCursor(0, -1); break; case ControlCharacter.HT: break; case ControlCharacter.LF: case ControlCharacter.VT: case ControlCharacter.FF: mScreenBuffer.lineFeed(); break; case ControlCharacter.CR: mScreenBuffer.setCursorCol(0); break; case ControlCharacter.SO: break; case ControlCharacter.SI: break; case ControlCharacter.CAN: case ControlCharacter.SUB: mEscapeSequence = null; break; case ControlCharacter.DEL: break; case ControlCharacter.ESC: case ControlCharacter.CSI: mEscapeSequence = new EscapeSequence(input); break; default: if (mEscapeSequence != null && !mEscapeSequence.isFinished()) { if (mEscapeSequence.append(input)) { /* finished sequence */ processEscapeSequence(mEscapeSequence); mEscapeSequence = null; } } else { /* handle normal character */ mScreenBuffer.append(input); } } dispatchEvent(TerminalEvent.SCREEN_UPDATE); } void processEscapeSequence(EscapeSequence s) { switch (s.getType()) { case EscapeSequence.SEQUENCE_TYPE_EL0: case EscapeSequence.SEQUENCE_TYPE_EL1: case EscapeSequence.SEQUENCE_TYPE_EL2: /* erase in line */ mScreenBuffer.eraseInLine( s.getArgumentOrDefault(0) ); break; case EscapeSequence.SEQUENCE_TYPE_ED0: case EscapeSequence.SEQUENCE_TYPE_ED1: case EscapeSequence.SEQUENCE_TYPE_ED2: /* erase in display/screen */ mScreenBuffer.eraseInScreen( s.getArgumentOrDefault(0) ); break; case EscapeSequence.SEQUENCE_TYPE_CUU: /* cursor up */ mScreenBuffer.moveCursor(-s.getArgumentOrDefault(0, 1), 0); break; case EscapeSequence.SEQUENCE_TYPE_CUD: /* cursor down */ mScreenBuffer.moveCursor(s.getArgumentOrDefault(0, 1), 0); break; case EscapeSequence.SEQUENCE_TYPE_CUF: /* cursor forward */ mScreenBuffer.moveCursor(0, s.getArgumentOrDefault(0, 1)); break; case EscapeSequence.SEQUENCE_TYPE_CUB: /* cursor backward */ mScreenBuffer.moveCursor(0, -s.getArgumentOrDefault(0, 1)); break; case EscapeSequence.SEQUENCE_TYPE_CNL: /* cursor next line */ mScreenBuffer.moveCursor(s.getArgumentOrDefault(0, 1), 0); mScreenBuffer.setCursorCol(0); break; case EscapeSequence.SEQUENCE_TYPE_CPL: /* cursor previous line */ mScreenBuffer.moveCursor(-s.getArgumentOrDefault(0, 1), 0); mScreenBuffer.setCursorCol(0); break; case EscapeSequence.SEQUENCE_TYPE_CHA: /* cursor horizontal absolute */ int cha = s.getArgumentOrDefault(0, 0); cha = (cha > 0) ? (cha - 1) : 0; mScreenBuffer.setCursorCol(cha); break; case EscapeSequence.SEQUENCE_TYPE_CUP: /* cursor position */ int cupRow = s.getArgumentOrDefault(0, 0); int cupCol = s.getArgumentOrDefault(1, 0); /* cursor position to internal zero-based indexing */ cupRow = (cupRow > 0) ? (cupRow-1) : 0; cupCol = (cupCol > 0) ? (cupCol-1) : 0; mScreenBuffer.setCursor(cupRow, cupCol); break; case EscapeSequence.SEQUENCE_TYPE_SGR: /* graphics rendition */ final ArrayList<Integer> args = s.getArguments(); int[] params = new int[args.size()]; Iterator<Integer> iterator = args.iterator(); for (int i = 0; i < params.length; ++i) { int val = iterator.next().intValue(); params[i] = (val != EscapeSequence.ARGUMENT_DEFAULT) ? val : 0; } mScreenBuffer.setGraphicsRendition(params); break; default: if (DEBUG) Log.d(LOG_TAG, "Unhandled escape sequence: " + s.toString()); } } public void processOutput(byte[] output, int offset, int length) { try { mOutputStream.write(output, offset, length); mOutputStream.flush(); } catch (Exception e) { } } void dispatchEvent(int event) { mListener.OnEmulatorEvent(event); } public void resizeWindow(int rows, int cols, int width, int height) { mScreenWidth = width; mScreenHeight = height; LAST_COLS = cols; LAST_ROWS = rows; LAST_WIDTH = width; LAST_HEIGHT = height; synchronized(mInputMutex) { mScreenBuffer.resize(rows, cols); } dispatchEvent(TerminalEvent.SCREEN_RESIZE); } public ScreenBuffer getScreenBuffer() { return mScreenBuffer; } public int getScreenRows() { return mScreenBuffer.getScreenRows(); } public int getScreenCols() { return mScreenBuffer.getScreenCols(); } public int getScreenWidth() { return mScreenWidth; } public int getScreenHeight() { return mScreenHeight; } public interface OnEmulatorEventListener { public void OnEmulatorEvent(int event); } }