--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jackpal.androidterm;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.ClipboardManager;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+
+import jackpal.androidterm.model.TextRenderer;
+import jackpal.androidterm.session.TerminalEmulator;
+import jackpal.androidterm.session.TranscriptScreen;
+import jackpal.androidterm.util.ByteQueue;
+
+/**
+ * A view on a transcript and a terminal emulator. Displays the text of the
+ * transcript and the current cursor position of the terminal emulator.
+ */
+public class EmulatorView extends View implements GestureDetector.OnGestureListener {
+
+ private final String TAG = "EmulatorView";
+ private final boolean LOG_KEY_EVENTS = Term.DEBUG && false;
+
+ private Term mTerm;
+
+ /**
+ * We defer some initialization until we have been layed out in the view
+ * hierarchy. The boolean tracks when we know what our size is.
+ */
+ private boolean mKnownSize;
+
+ private int mVisibleWidth;
+ private int mVisibleHeight;
+ private Rect mVisibleRect = new Rect();
+
+ /**
+ * Our transcript. Contains the screen and the transcript.
+ */
+ private TranscriptScreen mTranscriptScreen;
+
+ /**
+ * Number of rows in the transcript.
+ */
+ private static final int TRANSCRIPT_ROWS = 10000;
+
+ /**
+ * Total width of each character, in pixels
+ */
+ private int mCharacterWidth;
+
+ /**
+ * Total height of each character, in pixels
+ */
+ private int mCharacterHeight;
+
+ /**
+ * Used to render text
+ */
+ private TextRenderer mTextRenderer;
+
+ /**
+ * Text size. Zero means 4 x 8 font.
+ */
+ private int mTextSize;
+
+ private int mCursorStyle;
+ private int mCursorBlink;
+
+ /**
+ * Foreground color.
+ */
+ private int mForeground;
+
+ /**
+ * Background color.
+ */
+ private int mBackground;
+
+ /**
+ * Used to paint the cursor
+ */
+ private Paint mCursorPaint;
+
+ private Paint mBackgroundPaint;
+
+ private boolean mUseCookedIme;
+
+ /**
+ * Our terminal emulator. We use this to get the current cursor position.
+ */
+ private TerminalEmulator mEmulator;
+
+ /**
+ * The number of rows of text to display.
+ */
+ private int mRows;
+
+ /**
+ * The number of columns of text to display.
+ */
+ private int mColumns;
+
+ /**
+ * The number of columns that are visible on the display.
+ */
+
+ private int mVisibleColumns;
+
+ /**
+ * The top row of text to display. Ranges from -activeTranscriptRows to 0
+ */
+ private int mTopRow;
+
+ private int mLeftColumn;
+
+ private FileDescriptor mTermFd;
+ /**
+ * Used to receive data from the remote process.
+ */
+ private FileInputStream mTermIn;
+
+ private FileOutputStream mTermOut;
+
+ private ByteQueue mByteQueue;
+
+ /**
+ * Used to temporarily hold data received from the remote process. Allocated
+ * once and used permanently to minimize heap thrashing.
+ */
+ private byte[] mReceiveBuffer;
+
+ /**
+ * Our private message id, which we use to receive new input from the
+ * remote process.
+ */
+ private static final int UPDATE = 1;
+
+ private static final int SCREEN_CHECK_PERIOD = 1000;
+ private static final int CURSOR_BLINK_PERIOD = 1000;
+
+ private boolean mCursorVisible = true;
+
+ private boolean mIsSelectingText = false;
+
+
+ private float mScaledDensity;
+ private static final int SELECT_TEXT_OFFSET_Y = -40;
+ private int mSelXAnchor = -1;
+ private int mSelYAnchor = -1;
+ private int mSelX1 = -1;
+ private int mSelY1 = -1;
+ private int mSelX2 = -1;
+ private int mSelY2 = -1;
+
+ /**
+ * Used to poll if the view has changed size. Wish there was a better way to do this.
+ */
+ private Runnable mCheckSize = new Runnable() {
+
+ public void run() {
+ updateSize(false);
+ mHandler.postDelayed(this, SCREEN_CHECK_PERIOD);
+ }
+ };
+
+ private Runnable mBlinkCursor = new Runnable() {
+ public void run() {
+ if (mCursorBlink != 0) {
+ mCursorVisible = ! mCursorVisible;
+ mHandler.postDelayed(this, CURSOR_BLINK_PERIOD);
+ } else {
+ mCursorVisible = true;
+ }
+ // Perhaps just invalidate the character with the cursor.
+ invalidate();
+ }
+ };
+
+ /**
+ * Thread that polls for input from the remote process
+ */
+
+ private Thread mPollingThread;
+
+ private GestureDetector mGestureDetector;
+ private float mScrollRemainder;
+ private TermKeyListener mKeyListener;
+
+ private String mImeBuffer = "";
+
+ /**
+ * Our message handler class. Implements a periodic callback.
+ */
+ private final Handler mHandler = new Handler() {
+ /**
+ * Handle the callback message. Call our enclosing class's update
+ * method.
+ *
+ * @param msg The callback message.
+ */
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == UPDATE) {
+ update();
+ }
+ }
+ };
+
+ public EmulatorView(Context context) {
+ super(context);
+ commonConstructor();
+ }
+
+ public void setScaledDensity(float scaledDensity) {
+ mScaledDensity = scaledDensity;
+ }
+
+ public void onResume() {
+ updateSize(false);
+ mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD);
+ if (mCursorBlink != 0) {
+ mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD);
+ }
+ }
+
+ public void onPause() {
+ mHandler.removeCallbacks(mCheckSize);
+ if (mCursorBlink != 0) {
+ mHandler.removeCallbacks(mBlinkCursor);
+ }
+ }
+
+ public void register(Term term, TermKeyListener listener) {
+ mTerm = term;
+ mKeyListener = listener;
+ }
+
+ public void setColors(int foreground, int background) {
+ mForeground = foreground;
+ mBackground = background;
+ updateText();
+ }
+
+ public String getTranscriptText() {
+ return mEmulator.getTranscriptText();
+ }
+
+ public void resetTerminal() {
+ mEmulator.reset();
+ invalidate();
+ }
+
+ @Override
+ public boolean onCheckIsTextEditor() {
+ return true;
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ outAttrs.inputType = mUseCookedIme ?
+ EditorInfo.TYPE_CLASS_TEXT :
+ EditorInfo.TYPE_NULL;
+ return new InputConnection() {
+ private boolean mInBatchEdit;
+ /**
+ * Used to handle composing text requests
+ */
+ private int mCursor;
+ private int mComposingTextStart;
+ private int mComposingTextEnd;
+ private int mSelectedTextStart;
+ private int mSelectedTextEnd;
+
+ private void sendChar(int c) {
+ try {
+ mapAndSend(c);
+ } catch (IOException ex) {
+
+ }
+ }
+
+ private void sendText(CharSequence text) {
+ int n = text.length();
+ try {
+ for(int i = 0; i < n; i++) {
+ char c = text.charAt(i);
+ mapAndSend(c);
+ }
+ mTermOut.flush();
+ } catch (IOException e) {
+ Log.e(TAG, "error writing ", e);
+ }
+ }
+
+ private void mapAndSend(int c) throws IOException {
+ int result = mKeyListener.mapControlChar(c);
+ if (result < TermKeyListener.KEYCODE_OFFSET) {
+ mTermOut.write(result);
+ } else {
+ mKeyListener.handleKeyCode(result - TermKeyListener.KEYCODE_OFFSET, mTermOut, getKeypadApplicationMode());
+ }
+ }
+
+ public boolean beginBatchEdit() {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "beginBatchEdit");
+ }
+ setImeBuffer("");
+ mCursor = 0;
+ mComposingTextStart = 0;
+ mComposingTextEnd = 0;
+ mInBatchEdit = true;
+ return true;
+ }
+
+ public boolean clearMetaKeyStates(int arg0) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "clearMetaKeyStates " + arg0);
+ }
+ return false;
+ }
+
+ public boolean commitCompletion(CompletionInfo arg0) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "commitCompletion " + arg0);
+ }
+ return false;
+ }
+
+ public boolean endBatchEdit() {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "endBatchEdit");
+ }
+ mInBatchEdit = false;
+ return true;
+ }
+
+ public boolean finishComposingText() {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "finishComposingText");
+ }
+ sendText(mImeBuffer);
+ setImeBuffer("");
+ mComposingTextStart = 0;
+ mComposingTextEnd = 0;
+ mCursor = 0;
+ return true;
+ }
+
+ public int getCursorCapsMode(int arg0) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "getCursorCapsMode(" + arg0 + ")");
+ }
+ return 0;
+ }
+
+ public ExtractedText getExtractedText(ExtractedTextRequest arg0,
+ int arg1) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "getExtractedText" + arg0 + "," + arg1);
+ }
+ return null;
+ }
+
+ public CharSequence getTextAfterCursor(int n, int flags) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "getTextAfterCursor(" + n + "," + flags + ")");
+ }
+ int len = Math.min(n, mImeBuffer.length() - mCursor);
+ if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) {
+ return "";
+ }
+ return mImeBuffer.substring(mCursor, mCursor + len);
+ }
+
+ public CharSequence getTextBeforeCursor(int n, int flags) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "getTextBeforeCursor(" + n + "," + flags + ")");
+ }
+ int len = Math.min(n, mCursor);
+ if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) {
+ return "";
+ }
+ return mImeBuffer.substring(mCursor-len, mCursor);
+ }
+
+ public boolean performContextMenuAction(int arg0) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "performContextMenuAction" + arg0);
+ }
+ return true;
+ }
+
+ public boolean performPrivateCommand(String arg0, Bundle arg1) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "performPrivateCommand" + arg0 + "," + arg1);
+ }
+ return true;
+ }
+
+ public boolean reportFullscreenMode(boolean arg0) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "reportFullscreenMode" + arg0);
+ }
+ return true;
+ }
+
+ public boolean commitCorrection (CorrectionInfo correctionInfo) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "commitCorrection");
+ }
+ return true;
+ }
+
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "commitText(\"" + text + "\", " + newCursorPosition + ")");
+ }
+ clearComposingText();
+ sendText(text);
+ setImeBuffer("");
+ mCursor = 0;
+ return true;
+ }
+
+ private void clearComposingText() {
+ setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) +
+ mImeBuffer.substring(mComposingTextEnd));
+ if (mCursor < mComposingTextStart) {
+ // do nothing
+ } else if (mCursor < mComposingTextEnd) {
+ mCursor = mComposingTextStart;
+ } else {
+ mCursor -= mComposingTextEnd - mComposingTextStart;
+ }
+ mComposingTextEnd = mComposingTextStart = 0;
+ }
+
+ public boolean deleteSurroundingText(int leftLength, int rightLength) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "deleteSurroundingText(" + leftLength +
+ "," + rightLength + ")");
+ }
+ if (leftLength > 0) {
+ for (int i = 0; i < leftLength; i++) {
+ sendKeyEvent(
+ new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
+ }
+ } else if ((leftLength == 0) && (rightLength == 0)) {
+ // Delete key held down / repeating
+ sendKeyEvent(
+ new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
+ }
+ // TODO: handle forward deletes.
+ return true;
+ }
+
+ public boolean performEditorAction(int actionCode) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "performEditorAction(" + actionCode + ")");
+ }
+ if (actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) {
+ // The "return" key has been pressed on the IME.
+ sendText("\n");
+ }
+ return true;
+ }
+
+ public boolean sendKeyEvent(KeyEvent event) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "sendKeyEvent(" + event + ")");
+ }
+ // Some keys are sent here rather than to commitText.
+ // In particular, del and the digit keys are sent here.
+ // (And I have reports that the HTC Magic also sends Return here.)
+ // As a bit of defensive programming, handle every key.
+ dispatchKeyEvent(event);
+ return true;
+ }
+
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "setComposingText(\"" + text + "\", " + newCursorPosition + ")");
+ }
+ setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) +
+ text + mImeBuffer.substring(mComposingTextEnd));
+ mComposingTextEnd = mComposingTextStart + text.length();
+ mCursor = newCursorPosition > 0 ? mComposingTextEnd + newCursorPosition - 1
+ : mComposingTextStart - newCursorPosition;
+ return true;
+ }
+
+ public boolean setSelection(int start, int end) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "setSelection" + start + "," + end);
+ }
+ int length = mImeBuffer.length();
+ if (start == end && start > 0 && start < length) {
+ mSelectedTextStart = mSelectedTextEnd = 0;
+ mCursor = start;
+ } else if (start < end && start > 0 && end < length) {
+ mSelectedTextStart = start;
+ mSelectedTextEnd = end;
+ mCursor = start;
+ }
+ return true;
+ }
+
+ public boolean setComposingRegion(int start, int end) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "setComposingRegion " + start + "," + end);
+ }
+ if (start < end && start > 0 && end < mImeBuffer.length()) {
+ clearComposingText();
+ mComposingTextStart = start;
+ mComposingTextEnd = end;
+ }
+ return true;
+ }
+
+ public CharSequence getSelectedText(int flags) {
+ if (Term.LOG_IME) {
+ Log.w(TAG, "getSelectedText " + flags);
+ }
+ return mImeBuffer.substring(mSelectedTextStart, mSelectedTextEnd+1);
+ }
+
+ };
+ }
+
+ private void setImeBuffer(String buffer) {
+ if (!buffer.equals(mImeBuffer)) {
+ invalidate();
+ }
+ mImeBuffer = buffer;
+ }
+
+ public boolean getKeypadApplicationMode() {
+ return mEmulator.getKeypadApplicationMode();
+ }
+
+ public EmulatorView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public EmulatorView(Context context, AttributeSet attrs,
+ int defStyle) {
+ super(context, attrs, defStyle);
+ // TypedArray a =
+ // context.obtainStyledAttributes(android.R.styleable.View);
+ // initializeScrollbars(a);
+ // a.recycle();
+ commonConstructor();
+ }
+
+ private void commonConstructor() {
+ mTextRenderer = null;
+ mCursorPaint = new Paint();
+ mCursorPaint.setARGB(255,128,128,128);
+ mBackgroundPaint = new Paint();
+ mTopRow = 0;
+ mLeftColumn = 0;
+ mGestureDetector = new GestureDetector(this);
+ // mGestureDetector.setIsLongpressEnabled(false);
+ setVerticalScrollBarEnabled(true);
+ }
+
+ @Override
+ protected int computeVerticalScrollRange() {
+ return mTranscriptScreen.getActiveRows();
+ }
+
+ @Override
+ protected int computeVerticalScrollExtent() {
+ return mRows;
+ }
+
+ @Override
+ protected int computeVerticalScrollOffset() {
+ return mTranscriptScreen.getActiveRows() + mTopRow - mRows;
+ }
+
+ /**
+ * Call this to initialize the view.
+ *
+ * @param termFd the file descriptor
+ * @param termOut the output stream for the pseudo-teletype
+ */
+ public void initialize(FileDescriptor termFd, FileOutputStream termOut) {
+ mTermOut = termOut;
+ mTermFd = termFd;
+ mTextSize = 10;
+ mForeground = Term.WHITE;
+ mBackground = Term.BLACK;
+ updateText();
+ mTermIn = new FileInputStream(mTermFd);
+ mReceiveBuffer = new byte[4 * 1024];
+ mByteQueue = new ByteQueue(4 * 1024);
+ }
+
+ /**
+ * Accept a sequence of bytes (typically from the pseudo-tty) and process
+ * them.
+ *
+ * @param buffer a byte array containing bytes to be processed
+ * @param base the index of the first byte in the buffer to process
+ * @param length the number of bytes to process
+ */
+ public void append(byte[] buffer, int base, int length) {
+ mEmulator.append(buffer, base, length);
+ if ( mIsSelectingText ) {
+ int rowShift = mEmulator.getScrollCounter();
+ mSelY1 -= rowShift;
+ mSelY2 -= rowShift;
+ mSelYAnchor -= rowShift;
+ }
+ mEmulator.clearScrollCounter();
+ ensureCursorVisible();
+ invalidate();
+ }
+
+ /**
+ * Page the terminal view (scroll it up or down by delta screenfulls.)
+ *
+ * @param delta the number of screens to scroll. Positive means scroll down,
+ * negative means scroll up.
+ */
+ public void page(int delta) {
+ mTopRow =
+ Math.min(0, Math.max(-(mTranscriptScreen
+ .getActiveTranscriptRows()), mTopRow + mRows * delta));
+ invalidate();
+ }
+
+ /**
+ * Page the terminal view horizontally.
+ *
+ * @param deltaColumns the number of columns to scroll. Positive scrolls to
+ * the right.
+ */
+ public void pageHorizontal(int deltaColumns) {
+ mLeftColumn =
+ Math.max(0, Math.min(mLeftColumn + deltaColumns, mColumns
+ - mVisibleColumns));
+ invalidate();
+ }
+
+ /**
+ * Sets the text size, which in turn sets the number of rows and columns
+ *
+ * @param fontSize the new font size, in pixels.
+ */
+ public void setTextSize(int fontSize) {
+ mTextSize = fontSize;
+ updateText();
+ }
+
+ public void setCursorStyle(int style, int blink) {
+ mCursorStyle = style;
+ if (blink != 0 && mCursorBlink == 0) {
+ mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD);
+ } else if (blink == 0 && mCursorBlink != 0) {
+ mHandler.removeCallbacks(mBlinkCursor);
+ }
+ mCursorBlink = blink;
+ }
+
+ public void setUseCookedIME(boolean useCookedIME) {
+ mUseCookedIme = useCookedIME;
+ }
+
+ // Begin GestureDetector.OnGestureListener methods
+
+ public boolean onSingleTapUp(MotionEvent e) {
+ return true;
+ }
+
+ public void onLongPress(MotionEvent e) {
+ showContextMenu();
+ }
+
+ public boolean onScroll(MotionEvent e1, MotionEvent e2,
+ float distanceX, float distanceY) {
+ distanceY += mScrollRemainder;
+ int deltaRows = (int) (distanceY / mCharacterHeight);
+ mScrollRemainder = distanceY - deltaRows * mCharacterHeight;
+ mTopRow =
+ Math.min(0, Math.max(-(mTranscriptScreen
+ .getActiveTranscriptRows()), mTopRow + deltaRows));
+ invalidate();
+
+ return true;
+ }
+
+ public void onSingleTapConfirmed(MotionEvent e) {
+ }
+
+ public boolean onJumpTapDown(MotionEvent e1, MotionEvent e2) {
+ // Scroll to bottom
+ mTopRow = 0;
+ invalidate();
+ return true;
+ }
+
+ public boolean onJumpTapUp(MotionEvent e1, MotionEvent e2) {
+ // Scroll to top
+ mTopRow = -mTranscriptScreen.getActiveTranscriptRows();
+ invalidate();
+ return true;
+ }
+
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ // TODO: add animation man's (non animated) fling
+ mScrollRemainder = 0.0f;
+ onScroll(e1, e2, 2 * velocityX, -2 * velocityY);
+ return true;
+ }
+
+ public void onShowPress(MotionEvent e) {
+ }
+
+ public boolean onDown(MotionEvent e) {
+ mScrollRemainder = 0.0f;
+ return true;
+ }
+
+ // End GestureDetector.OnGestureListener methods
+
+ @Override public boolean onTouchEvent(MotionEvent ev) {
+ if (mIsSelectingText) {
+ return onTouchEventWhileSelectingText(ev);
+ } else {
+ return mGestureDetector.onTouchEvent(ev);
+ }
+ }
+
+ private boolean onTouchEventWhileSelectingText(MotionEvent ev) {
+ int action = ev.getAction();
+ int cx = (int)(ev.getX() / mCharacterWidth);
+ int cy = Math.max(0,
+ (int)((ev.getY() + SELECT_TEXT_OFFSET_Y * mScaledDensity)
+ / mCharacterHeight) + mTopRow);
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mSelXAnchor = cx;
+ mSelYAnchor = cy;
+ mSelX1 = cx;
+ mSelY1 = cy;
+ mSelX2 = mSelX1;
+ mSelY2 = mSelY1;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_UP:
+ int minx = Math.min(mSelXAnchor, cx);
+ int maxx = Math.max(mSelXAnchor, cx);
+ int miny = Math.min(mSelYAnchor, cy);
+ int maxy = Math.max(mSelYAnchor, cy);
+ mSelX1 = minx;
+ mSelY1 = miny;
+ mSelX2 = maxx;
+ mSelY2 = maxy;
+ if (action == MotionEvent.ACTION_UP) {
+ ClipboardManager clip = (ClipboardManager)
+ getContext().getApplicationContext()
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ clip.setText(getSelectedText().trim());
+ toggleSelectingText();
+ }
+ invalidate();
+ break;
+ default:
+ toggleSelectingText();
+ invalidate();
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (LOG_KEY_EVENTS) {
+ Log.w(TAG, "onKeyDown " + keyCode);
+ }
+ if (handleControlKey(keyCode, true)) {
+ return true;
+ } else if (handleFnKey(keyCode, true)) {
+ return true;
+ } else if (isSystemKey(keyCode, event)) {
+ // Don't intercept the system keys
+ return super.onKeyDown(keyCode, event);
+ }
+
+ // Translate the keyCode into an ASCII character.
+
+ try {
+ mKeyListener.keyDown(keyCode, event, mTermOut,
+ getKeypadApplicationMode());
+ } catch (IOException e) {
+ // Ignore I/O exceptions
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (LOG_KEY_EVENTS) {
+ Log.w(TAG, "onKeyUp " + keyCode);
+ }
+ if (handleControlKey(keyCode, false)) {
+ return true;
+ } else if (handleFnKey(keyCode, false)) {
+ return true;
+ } else if (isSystemKey(keyCode, event)) {
+ // Don't intercept the system keys
+ return super.onKeyUp(keyCode, event);
+ }
+
+ mKeyListener.keyUp(keyCode);
+ return true;
+ }
+
+
+ private boolean handleControlKey(int keyCode, boolean down) {
+ if (keyCode == mTerm.getControlKeyCode()) {
+ if (LOG_KEY_EVENTS) {
+ Log.w(TAG, "handleControlKey " + keyCode);
+ }
+ mKeyListener.handleControlKey(down);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean handleFnKey(int keyCode, boolean down) {
+ if (keyCode == mTerm.getFnKeyCode()) {
+ if (LOG_KEY_EVENTS) {
+ Log.w(TAG, "handleFnKey " + keyCode);
+ }
+ mKeyListener.handleFnKey(down);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isSystemKey(int keyCode, KeyEvent event) {
+ return event.isSystem();
+ }
+
+ private void updateText() {
+ if (mTextSize > 0) {
+ mTextRenderer = new PaintRenderer(mTextSize, mForeground,
+ mBackground);
+ }
+ else {
+ mTextRenderer = new Bitmap4x8FontRenderer(getResources(),
+ mForeground, mBackground);
+ }
+ mBackgroundPaint.setColor(mBackground);
+ mCharacterWidth = mTextRenderer.getCharacterWidth();
+ mCharacterHeight = mTextRenderer.getCharacterHeight();
+
+ updateSize(true);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ boolean oldKnownSize = mKnownSize;
+ if (!mKnownSize) {
+ mKnownSize = true;
+ }
+ updateSize(false);
+ if (!oldKnownSize) {
+ // Set up a thread to read input from the
+ // pseudo-teletype:
+
+ mPollingThread = new Thread(new Runnable() {
+
+ public void run() {
+ try {
+ while(true) {
+ int read = mTermIn.read(mBuffer);
+ mByteQueue.write(mBuffer, 0, read);
+ mHandler.sendMessage(
+ mHandler.obtainMessage(UPDATE));
+ }
+ } catch (IOException e) {
+ } catch (InterruptedException e) {
+ }
+ }
+ private byte[] mBuffer = new byte[4096];
+ });
+ mPollingThread.setName("Input reader");
+ mPollingThread.start();
+ }
+ }
+
+ private void updateSize(int w, int h) {
+ mColumns = Math.max(1, w / mCharacterWidth);
+ mRows = Math.max(1, h / mCharacterHeight);
+ mVisibleColumns = mVisibleWidth / mCharacterWidth;
+
+ // Inform the attached pty of our new size:
+ Exec.setPtyWindowSize(mTermFd, mRows, mColumns, w, h);
+
+
+ if (mTranscriptScreen != null) {
+ mEmulator.updateSize(mColumns, mRows);
+ } else {
+ mTranscriptScreen =
+ new TranscriptScreen(mColumns, TRANSCRIPT_ROWS, mRows, 0, 7);
+ mEmulator =
+ new TerminalEmulator(mTranscriptScreen, mColumns, mRows,
+ mTermOut);
+ }
+
+ // Reset our paging:
+ mTopRow = 0;
+ mLeftColumn = 0;
+
+ invalidate();
+ }
+
+ void updateSize(boolean force) {
+ if (mKnownSize) {
+ getWindowVisibleDisplayFrame(mVisibleRect);
+ int w = mVisibleRect.width();
+ int h = mVisibleRect.height();
+ // Log.w("Term", "(" + w + ", " + h + ")");
+ if (force || w != mVisibleWidth || h != mVisibleHeight) {
+ mVisibleWidth = w;
+ mVisibleHeight = h;
+ updateSize(mVisibleWidth, mVisibleHeight);
+ }
+ }
+ }
+
+ /**
+ * Look for new input from the ptty, send it to the terminal emulator.
+ */
+ private void update() {
+ int bytesAvailable = mByteQueue.getBytesAvailable();
+ int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length);
+ try {
+ int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead);
+ append(mReceiveBuffer, 0, bytesRead);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ updateSize(false);
+ int w = getWidth();
+ int h = getHeight();
+ canvas.drawRect(0, 0, w, h, mBackgroundPaint);
+ float x = -mLeftColumn * mCharacterWidth;
+ float y = mCharacterHeight;
+ int endLine = mTopRow + mRows;
+ int cx = mEmulator.getCursorCol();
+ int cy = mEmulator.getCursorRow();
+ for (int i = mTopRow; i < endLine; i++) {
+ int cursorX = -1;
+ if (i == cy && mCursorVisible) {
+ cursorX = cx;
+ }
+ int selx1 = -1;
+ int selx2 = -1;
+ if ( i >= mSelY1 && i <= mSelY2 ) {
+ if ( i == mSelY1 ) {
+ selx1 = mSelX1;
+ }
+ if ( i == mSelY2 ) {
+ selx2 = mSelX2;
+ } else {
+ selx2 = mColumns;
+ }
+ }
+ mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2, mImeBuffer);
+ y += mCharacterHeight;
+ }
+ }
+
+ private void ensureCursorVisible() {
+ mTopRow = 0;
+ if (mVisibleColumns > 0) {
+ int cx = mEmulator.getCursorCol();
+ int visibleCursorX = mEmulator.getCursorCol() - mLeftColumn;
+ if (visibleCursorX < 0) {
+ mLeftColumn = cx;
+ } else if (visibleCursorX >= mVisibleColumns) {
+ mLeftColumn = (cx - mVisibleColumns) + 1;
+ }
+ }
+ }
+
+ public void toggleSelectingText() {
+ mIsSelectingText = ! mIsSelectingText;
+ setVerticalScrollBarEnabled( ! mIsSelectingText );
+ if ( ! mIsSelectingText ) {
+ mSelX1 = -1;
+ mSelY1 = -1;
+ mSelX2 = -1;
+ mSelY2 = -1;
+ }
+ }
+
+ public boolean getSelectingText() {
+ return mIsSelectingText;
+ }
+
+ public String getSelectedText() {
+ return mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2);
+ }
+}
+
+abstract class BaseTextRenderer implements TextRenderer {
+ protected int[] mForePaint = {
+ 0xff000000, // Black
+ 0xffff0000, // Red
+ 0xff00ff00, // green
+ 0xffffff00, // yellow
+ 0xff0000ff, // blue
+ 0xffff00ff, // magenta
+ 0xff00ffff, // cyan
+ 0xffffffff // white -- is overridden by constructor
+ };
+ protected int[] mBackPaint = {
+ 0xff000000, // Black -- is overridden by constructor
+ 0xffcc0000, // Red
+ 0xff00cc00, // green
+ 0xffcccc00, // yellow
+ 0xff0000cc, // blue
+ 0xffff00cc, // magenta
+ 0xff00cccc, // cyan
+ 0xffffffff // white
+ };
+ protected final static int mCursorPaint = 0xff808080;
+
+ public BaseTextRenderer(int forePaintColor, int backPaintColor) {
+ mForePaint[7] = forePaintColor;
+ mBackPaint[0] = backPaintColor;
+
+ }
+}
+
+class Bitmap4x8FontRenderer extends BaseTextRenderer {
+ private final static int kCharacterWidth = 4;
+ private final static int kCharacterHeight = 8;
+ private Bitmap mFont;
+ private int mCurrentForeColor;
+ private int mCurrentBackColor;
+ private float[] mColorMatrix;
+ private Paint mPaint;
+ private static final float BYTE_SCALE = 1.0f / 255.0f;
+
+ public Bitmap4x8FontRenderer(Resources resources,
+ int forePaintColor, int backPaintColor) {
+ super(forePaintColor, backPaintColor);
+ mFont = BitmapFactory.decodeResource(resources,
+ R.drawable.atari_small);
+ mPaint = new Paint();
+ mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
+ }
+
+ public int getCharacterWidth() {
+ return kCharacterWidth;
+ }
+
+ public int getCharacterHeight() {
+ return kCharacterHeight;
+ }
+
+ public void drawTextRun(Canvas canvas, float x, float y,
+ int lineOffset, char[] text, int index, int count,
+ boolean cursor, int foreColor, int backColor) {
+ setColorMatrix(mForePaint[foreColor & 7],
+ cursor ? mCursorPaint : mBackPaint[backColor & 7]);
+ int destX = (int) x + kCharacterWidth * lineOffset;
+ int destY = (int) y;
+ Rect srcRect = new Rect();
+ Rect destRect = new Rect();
+ destRect.top = (destY - kCharacterHeight);
+ destRect.bottom = destY;
+ for(int i = 0; i < count; i++) {
+ char c = text[i + index];
+ if ((cursor || (c != 32)) && (c < 128)) {
+ int cellX = c & 31;
+ int cellY = (c >> 5) & 3;
+ int srcX = cellX * kCharacterWidth;
+ int srcY = cellY * kCharacterHeight;
+ srcRect.set(srcX, srcY,
+ srcX + kCharacterWidth, srcY + kCharacterHeight);
+ destRect.left = destX;
+ destRect.right = destX + kCharacterWidth;
+ canvas.drawBitmap(mFont, srcRect, destRect, mPaint);
+ }
+ destX += kCharacterWidth;
+ }
+ }
+
+ private void setColorMatrix(int foreColor, int backColor) {
+ if ((foreColor != mCurrentForeColor)
+ || (backColor != mCurrentBackColor)
+ || (mColorMatrix == null)) {
+ mCurrentForeColor = foreColor;
+ mCurrentBackColor = backColor;
+ if (mColorMatrix == null) {
+ mColorMatrix = new float[20];
+ mColorMatrix[18] = 1.0f; // Just copy Alpha
+ }
+ for (int component = 0; component < 3; component++) {
+ int rightShift = (2 - component) << 3;
+ int fore = 0xff & (foreColor >> rightShift);
+ int back = 0xff & (backColor >> rightShift);
+ int delta = back - fore;
+ mColorMatrix[component * 6] = delta * BYTE_SCALE;
+ mColorMatrix[component * 5 + 4] = fore;
+ }
+ mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
+ }
+ }
+}
+
+class PaintRenderer extends BaseTextRenderer {
+ public PaintRenderer(int fontSize, int forePaintColor, int backPaintColor) {
+ super(forePaintColor, backPaintColor);
+ mTextPaint = new Paint();
+ mTextPaint.setTypeface(Typeface.MONOSPACE);
+ mTextPaint.setAntiAlias(true);
+ mTextPaint.setTextSize(fontSize);
+
+ mCharHeight = (int) Math.ceil(mTextPaint.getFontSpacing());
+ mCharAscent = (int) Math.ceil(mTextPaint.ascent());
+ mCharDescent = mCharHeight + mCharAscent;
+ mCharWidth = (int) mTextPaint.measureText(EXAMPLE_CHAR, 0, 1);
+ }
+
+ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset,
+ char[] text, int index, int count,
+ boolean cursor, int foreColor, int backColor) {
+ if (cursor) {
+ mTextPaint.setColor(mCursorPaint);
+ } else {
+ mTextPaint.setColor(mBackPaint[backColor & 0x7]);
+ }
+ float left = x + lineOffset * mCharWidth;
+ canvas.drawRect(left, y + mCharAscent,
+ left + count * mCharWidth, y + mCharDescent,
+ mTextPaint);
+ boolean bold = ( foreColor & 0x8 ) != 0;
+ boolean underline = (backColor & 0x8) != 0;
+ if (bold) {
+ mTextPaint.setFakeBoldText(true);
+ }
+ if (underline) {
+ mTextPaint.setUnderlineText(true);
+ }
+ mTextPaint.setColor(mForePaint[foreColor & 0x7]);
+ canvas.drawText(text, index, count, left, y, mTextPaint);
+ if (bold) {
+ mTextPaint.setFakeBoldText(false);
+ }
+ if (underline) {
+ mTextPaint.setUnderlineText(false);
+ }
+ }
+
+ public int getCharacterHeight() {
+ return mCharHeight;
+ }
+
+ public int getCharacterWidth() {
+ return mCharWidth;
+ }
+
+
+ private Paint mTextPaint;
+ private int mCharWidth;
+ private int mCharHeight;
+ private int mCharAscent;
+ private int mCharDescent;
+ private static final char[] EXAMPLE_CHAR = {'X'};
+ }
+
+/**
+ * An ASCII key listener. Supports control characters and escape. Keeps track of
+ * the current state of the alt, shift, and control keys.
+ */
+class TermKeyListener {
+ /**
+ * Android key codes that are defined in the Android 2.3 API.
+ * We want to recognize these codes, because they will be sent to our
+ * app when we run on Android 2.3 systems.
+ * But we don't want to accidentally use 2.3-specific APIs.
+ * So we compile against the Android 1.6 APIs, and have a copy of the codes here.
+ */
+
+ /** Key code constant: Unknown key code. */
+ public static final int KEYCODE_UNKNOWN = 0;
+ /** Key code constant: Soft Left key.
+ * Usually situated below the display on phones and used as a multi-function
+ * feature key for selecting a software defined function shown on the bottom left
+ * of the display. */
+ public static final int KEYCODE_SOFT_LEFT = 1;
+ /** Key code constant: Soft Right key.
+ * Usually situated below the display on phones and used as a multi-function
+ * feature key for selecting a software defined function shown on the bottom right
+ * of the display. */
+ public static final int KEYCODE_SOFT_RIGHT = 2;
+ /** Key code constant: Home key.
+ * This key is handled by the framework and is never delivered to applications. */
+ public static final int KEYCODE_HOME = 3;
+ /** Key code constant: Back key. */
+ public static final int KEYCODE_BACK = 4;
+ /** Key code constant: Call key. */
+ public static final int KEYCODE_CALL = 5;
+ /** Key code constant: End Call key. */
+ public static final int KEYCODE_ENDCALL = 6;
+ /** Key code constant: '0' key. */
+ public static final int KEYCODE_0 = 7;
+ /** Key code constant: '1' key. */
+ public static final int KEYCODE_1 = 8;
+ /** Key code constant: '2' key. */
+ public static final int KEYCODE_2 = 9;
+ /** Key code constant: '3' key. */
+ public static final int KEYCODE_3 = 10;
+ /** Key code constant: '4' key. */
+ public static final int KEYCODE_4 = 11;
+ /** Key code constant: '5' key. */
+ public static final int KEYCODE_5 = 12;
+ /** Key code constant: '6' key. */
+ public static final int KEYCODE_6 = 13;
+ /** Key code constant: '7' key. */
+ public static final int KEYCODE_7 = 14;
+ /** Key code constant: '8' key. */
+ public static final int KEYCODE_8 = 15;
+ /** Key code constant: '9' key. */
+ public static final int KEYCODE_9 = 16;
+ /** Key code constant: '*' key. */
+ public static final int KEYCODE_STAR = 17;
+ /** Key code constant: '#' key. */
+ public static final int KEYCODE_POUND = 18;
+ /** Key code constant: Directional Pad Up key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_UP = 19;
+ /** Key code constant: Directional Pad Down key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_DOWN = 20;
+ /** Key code constant: Directional Pad Left key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_LEFT = 21;
+ /** Key code constant: Directional Pad Right key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_RIGHT = 22;
+ /** Key code constant: Directional Pad Center key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_CENTER = 23;
+ /** Key code constant: Volume Up key.
+ * Adjusts the speaker volume up. */
+ public static final int KEYCODE_VOLUME_UP = 24;
+ /** Key code constant: Volume Down key.
+ * Adjusts the speaker volume down. */
+ public static final int KEYCODE_VOLUME_DOWN = 25;
+ /** Key code constant: Power key. */
+ public static final int KEYCODE_POWER = 26;
+ /** Key code constant: Camera key.
+ * Used to launch a camera application or take pictures. */
+ public static final int KEYCODE_CAMERA = 27;
+ /** Key code constant: Clear key. */
+ public static final int KEYCODE_CLEAR = 28;
+ /** Key code constant: 'A' key. */
+ public static final int KEYCODE_A = 29;
+ /** Key code constant: 'B' key. */
+ public static final int KEYCODE_B = 30;
+ /** Key code constant: 'C' key. */
+ public static final int KEYCODE_C = 31;
+ /** Key code constant: 'D' key. */
+ public static final int KEYCODE_D = 32;
+ /** Key code constant: 'E' key. */
+ public static final int KEYCODE_E = 33;
+ /** Key code constant: 'F' key. */
+ public static final int KEYCODE_F = 34;
+ /** Key code constant: 'G' key. */
+ public static final int KEYCODE_G = 35;
+ /** Key code constant: 'H' key. */
+ public static final int KEYCODE_H = 36;
+ /** Key code constant: 'I' key. */
+ public static final int KEYCODE_I = 37;
+ /** Key code constant: 'J' key. */
+ public static final int KEYCODE_J = 38;
+ /** Key code constant: 'K' key. */
+ public static final int KEYCODE_K = 39;
+ /** Key code constant: 'L' key. */
+ public static final int KEYCODE_L = 40;
+ /** Key code constant: 'M' key. */
+ public static final int KEYCODE_M = 41;
+ /** Key code constant: 'N' key. */
+ public static final int KEYCODE_N = 42;
+ /** Key code constant: 'O' key. */
+ public static final int KEYCODE_O = 43;
+ /** Key code constant: 'P' key. */
+ public static final int KEYCODE_P = 44;
+ /** Key code constant: 'Q' key. */
+ public static final int KEYCODE_Q = 45;
+ /** Key code constant: 'R' key. */
+ public static final int KEYCODE_R = 46;
+ /** Key code constant: 'S' key. */
+ public static final int KEYCODE_S = 47;
+ /** Key code constant: 'T' key. */
+ public static final int KEYCODE_T = 48;
+ /** Key code constant: 'U' key. */
+ public static final int KEYCODE_U = 49;
+ /** Key code constant: 'V' key. */
+ public static final int KEYCODE_V = 50;
+ /** Key code constant: 'W' key. */
+ public static final int KEYCODE_W = 51;
+ /** Key code constant: 'X' key. */
+ public static final int KEYCODE_X = 52;
+ /** Key code constant: 'Y' key. */
+ public static final int KEYCODE_Y = 53;
+ /** Key code constant: 'Z' key. */
+ public static final int KEYCODE_Z = 54;
+ /** Key code constant: ',' key. */
+ public static final int KEYCODE_COMMA = 55;
+ /** Key code constant: '.' key. */
+ public static final int KEYCODE_PERIOD = 56;
+ /** Key code constant: Left Alt modifier key. */
+ public static final int KEYCODE_ALT_LEFT = 57;
+ /** Key code constant: Right Alt modifier key. */
+ public static final int KEYCODE_ALT_RIGHT = 58;
+ /** Key code constant: Left Shift modifier key. */
+ public static final int KEYCODE_SHIFT_LEFT = 59;
+ /** Key code constant: Right Shift modifier key. */
+ public static final int KEYCODE_SHIFT_RIGHT = 60;
+ /** Key code constant: Tab key. */
+ public static final int KEYCODE_TAB = 61;
+ /** Key code constant: Space key. */
+ public static final int KEYCODE_SPACE = 62;
+ /** Key code constant: Symbol modifier key.
+ * Used to enter alternate symbols. */
+ public static final int KEYCODE_SYM = 63;
+ /** Key code constant: Explorer special function key.
+ * Used to launch a browser application. */
+ public static final int KEYCODE_EXPLORER = 64;
+ /** Key code constant: Envelope special function key.
+ * Used to launch a mail application. */
+ public static final int KEYCODE_ENVELOPE = 65;
+ /** Key code constant: Enter key. */
+ public static final int KEYCODE_ENTER = 66;
+ /** Key code constant: Backspace key.
+ * Deletes characters before the insertion point, unlike {@link #KEYCODE_FORWARD_DEL}. */
+ public static final int KEYCODE_DEL = 67;
+ /** Key code constant: '`' (backtick) key. */
+ public static final int KEYCODE_GRAVE = 68;
+ /** Key code constant: '-'. */
+ public static final int KEYCODE_MINUS = 69;
+ /** Key code constant: '=' key. */
+ public static final int KEYCODE_EQUALS = 70;
+ /** Key code constant: '[' key. */
+ public static final int KEYCODE_LEFT_BRACKET = 71;
+ /** Key code constant: ']' key. */
+ public static final int KEYCODE_RIGHT_BRACKET = 72;
+ /** Key code constant: '\' key. */
+ public static final int KEYCODE_BACKSLASH = 73;
+ /** Key code constant: ';' key. */
+ public static final int KEYCODE_SEMICOLON = 74;
+ /** Key code constant: ''' (apostrophe) key. */
+ public static final int KEYCODE_APOSTROPHE = 75;
+ /** Key code constant: '/' key. */
+ public static final int KEYCODE_SLASH = 76;
+ /** Key code constant: '@' key. */
+ public static final int KEYCODE_AT = 77;
+ /** Key code constant: Number modifier key.
+ * Used to enter numeric symbols.
+ * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is
+ * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */
+ public static final int KEYCODE_NUM = 78;
+ /** Key code constant: Headset Hook key.
+ * Used to hang up calls and stop media. */
+ public static final int KEYCODE_HEADSETHOOK = 79;
+ /** Key code constant: Camera Focus key.
+ * Used to focus the camera. */
+ public static final int KEYCODE_FOCUS = 80; // *Camera* focus
+ /** Key code constant: '+' key. */
+ public static final int KEYCODE_PLUS = 81;
+ /** Key code constant: Menu key. */
+ public static final int KEYCODE_MENU = 82;
+ /** Key code constant: Notification key. */
+ public static final int KEYCODE_NOTIFICATION = 83;
+ /** Key code constant: Search key. */
+ public static final int KEYCODE_SEARCH = 84;
+ /** Key code constant: Play/Pause media key. */
+ public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85;
+ /** Key code constant: Stop media key. */
+ public static final int KEYCODE_MEDIA_STOP = 86;
+ /** Key code constant: Play Next media key. */
+ public static final int KEYCODE_MEDIA_NEXT = 87;
+ /** Key code constant: Play Previous media key. */
+ public static final int KEYCODE_MEDIA_PREVIOUS = 88;
+ /** Key code constant: Rewind media key. */
+ public static final int KEYCODE_MEDIA_REWIND = 89;
+ /** Key code constant: Fast Forward media key. */
+ public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
+ /** Key code constant: Mute key.
+ * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */
+ public static final int KEYCODE_MUTE = 91;
+ /** Key code constant: Page Up key. */
+ public static final int KEYCODE_PAGE_UP = 92;
+ /** Key code constant: Page Down key. */
+ public static final int KEYCODE_PAGE_DOWN = 93;
+ /** Key code constant: Picture Symbols modifier key.
+ * Used to switch symbol sets (Emoji, Kao-moji). */
+ public static final int KEYCODE_PICTSYMBOLS = 94; // switch symbol-sets (Emoji,Kao-moji)
+ /** Key code constant: Switch Charset modifier key.
+ * Used to switch character sets (Kanji, Katakana). */
+ public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana)
+ /** Key code constant: A Button key.
+ * On a game controller, the A button should be either the button labeled A
+ * or the first button on the upper row of controller buttons. */
+ public static final int KEYCODE_BUTTON_A = 96;
+ /** Key code constant: B Button key.
+ * On a game controller, the B button should be either the button labeled B
+ * or the second button on the upper row of controller buttons. */
+ public static final int KEYCODE_BUTTON_B = 97;
+ /** Key code constant: C Button key.
+ * On a game controller, the C button should be either the button labeled C
+ * or the third button on the upper row of controller buttons. */
+ public static final int KEYCODE_BUTTON_C = 98;
+ /** Key code constant: X Button key.
+ * On a game controller, the X button should be either the button labeled X
+ * or the first button on the lower row of controller buttons. */
+ public static final int KEYCODE_BUTTON_X = 99;
+ /** Key code constant: Y Button key.
+ * On a game controller, the Y button should be either the button labeled Y
+ * or the second button on the lower row of controller buttons. */
+ public static final int KEYCODE_BUTTON_Y = 100;
+ /** Key code constant: Z Button key.
+ * On a game controller, the Z button should be either the button labeled Z
+ * or the third button on the lower row of controller buttons. */
+ public static final int KEYCODE_BUTTON_Z = 101;
+ /** Key code constant: L1 Button key.
+ * On a game controller, the L1 button should be either the button labeled L1 (or L)
+ * or the top left trigger button. */
+ public static final int KEYCODE_BUTTON_L1 = 102;
+ /** Key code constant: R1 Button key.
+ * On a game controller, the R1 button should be either the button labeled R1 (or R)
+ * or the top right trigger button. */
+ public static final int KEYCODE_BUTTON_R1 = 103;
+ /** Key code constant: L2 Button key.
+ * On a game controller, the L2 button should be either the button labeled L2
+ * or the bottom left trigger button. */
+ public static final int KEYCODE_BUTTON_L2 = 104;
+ /** Key code constant: R2 Button key.
+ * On a game controller, the R2 button should be either the button labeled R2
+ * or the bottom right trigger button. */
+ public static final int KEYCODE_BUTTON_R2 = 105;
+ /** Key code constant: Left Thumb Button key.
+ * On a game controller, the left thumb button indicates that the left (or only)
+ * joystick is pressed. */
+ public static final int KEYCODE_BUTTON_THUMBL = 106;
+ /** Key code constant: Right Thumb Button key.
+ * On a game controller, the right thumb button indicates that the right
+ * joystick is pressed. */
+ public static final int KEYCODE_BUTTON_THUMBR = 107;
+ /** Key code constant: Start Button key.
+ * On a game controller, the button labeled Start. */
+ public static final int KEYCODE_BUTTON_START = 108;
+ /** Key code constant: Select Button key.
+ * On a game controller, the button labeled Select. */
+ public static final int KEYCODE_BUTTON_SELECT = 109;
+ /** Key code constant: Mode Button key.
+ * On a game controller, the button labeled Mode. */
+ public static final int KEYCODE_BUTTON_MODE = 110;
+ /** Key code constant: Escape key. */
+ public static final int KEYCODE_ESCAPE = 111;
+ /** Key code constant: Forward Delete key.
+ * Deletes characters ahead of the insertion point, unlike {@link #KEYCODE_DEL}. */
+ public static final int KEYCODE_FORWARD_DEL = 112;
+ /** Key code constant: Left Control modifier key. */
+ public static final int KEYCODE_CTRL_LEFT = 113;
+ /** Key code constant: Right Control modifier key. */
+ public static final int KEYCODE_CTRL_RIGHT = 114;
+ /** Key code constant: Caps Lock modifier key. */
+ public static final int KEYCODE_CAPS_LOCK = 115;
+ /** Key code constant: Scroll Lock key. */
+ public static final int KEYCODE_SCROLL_LOCK = 116;
+ /** Key code constant: Left Meta modifier key. */
+ public static final int KEYCODE_META_LEFT = 117;
+ /** Key code constant: Right Meta modifier key. */
+ public static final int KEYCODE_META_RIGHT = 118;
+ /** Key code constant: Function modifier key. */
+ public static final int KEYCODE_FUNCTION = 119;
+ /** Key code constant: System Request / Print Screen key. */
+ public static final int KEYCODE_SYSRQ = 120;
+ /** Key code constant: Break / Pause key. */
+ public static final int KEYCODE_BREAK = 121;
+ /** Key code constant: Home Movement key.
+ * Used for scrolling or moving the cursor around to the start of a line
+ * or to the top of a list. */
+ public static final int KEYCODE_MOVE_HOME = 122;
+ /** Key code constant: End Movement key.
+ * Used for scrolling or moving the cursor around to the end of a line
+ * or to the bottom of a list. */
+ public static final int KEYCODE_MOVE_END = 123;
+ /** Key code constant: Insert key.
+ * Toggles insert / overwrite edit mode. */
+ public static final int KEYCODE_INSERT = 124;
+ /** Key code constant: Forward key.
+ * Navigates forward in the history stack. Complement of {@link #KEYCODE_BACK}. */
+ public static final int KEYCODE_FORWARD = 125;
+ /** Key code constant: Play media key. */
+ public static final int KEYCODE_MEDIA_PLAY = 126;
+ /** Key code constant: Pause media key. */
+ public static final int KEYCODE_MEDIA_PAUSE = 127;
+ /** Key code constant: Close media key.
+ * May be used to close a CD tray, for example. */
+ public static final int KEYCODE_MEDIA_CLOSE = 128;
+ /** Key code constant: Eject media key.
+ * May be used to eject a CD tray, for example. */
+ public static final int KEYCODE_MEDIA_EJECT = 129;
+ /** Key code constant: Record media key. */
+ public static final int KEYCODE_MEDIA_RECORD = 130;
+ /** Key code constant: F1 key. */
+ public static final int KEYCODE_F1 = 131;
+ /** Key code constant: F2 key. */
+ public static final int KEYCODE_F2 = 132;
+ /** Key code constant: F3 key. */
+ public static final int KEYCODE_F3 = 133;
+ /** Key code constant: F4 key. */
+ public static final int KEYCODE_F4 = 134;
+ /** Key code constant: F5 key. */
+ public static final int KEYCODE_F5 = 135;
+ /** Key code constant: F6 key. */
+ public static final int KEYCODE_F6 = 136;
+ /** Key code constant: F7 key. */
+ public static final int KEYCODE_F7 = 137;
+ /** Key code constant: F8 key. */
+ public static final int KEYCODE_F8 = 138;
+ /** Key code constant: F9 key. */
+ public static final int KEYCODE_F9 = 139;
+ /** Key code constant: F10 key. */
+ public static final int KEYCODE_F10 = 140;
+ /** Key code constant: F11 key. */
+ public static final int KEYCODE_F11 = 141;
+ /** Key code constant: F12 key. */
+ public static final int KEYCODE_F12 = 142;
+ /** Key code constant: Num Lock modifier key.
+ * This is the Num Lock key; it is different from {@link #KEYCODE_NUM}.
+ * This key generally modifies the behavior of other keys on the numeric keypad. */
+ public static final int KEYCODE_NUM_LOCK = 143;
+ /** Key code constant: Numeric keypad '0' key. */
+ public static final int KEYCODE_NUMPAD_0 = 144;
+ /** Key code constant: Numeric keypad '1' key. */
+ public static final int KEYCODE_NUMPAD_1 = 145;
+ /** Key code constant: Numeric keypad '2' key. */
+ public static final int KEYCODE_NUMPAD_2 = 146;
+ /** Key code constant: Numeric keypad '3' key. */
+ public static final int KEYCODE_NUMPAD_3 = 147;
+ /** Key code constant: Numeric keypad '4' key. */
+ public static final int KEYCODE_NUMPAD_4 = 148;
+ /** Key code constant: Numeric keypad '5' key. */
+ public static final int KEYCODE_NUMPAD_5 = 149;
+ /** Key code constant: Numeric keypad '6' key. */
+ public static final int KEYCODE_NUMPAD_6 = 150;
+ /** Key code constant: Numeric keypad '7' key. */
+ public static final int KEYCODE_NUMPAD_7 = 151;
+ /** Key code constant: Numeric keypad '8' key. */
+ public static final int KEYCODE_NUMPAD_8 = 152;
+ /** Key code constant: Numeric keypad '9' key. */
+ public static final int KEYCODE_NUMPAD_9 = 153;
+ /** Key code constant: Numeric keypad '/' key (for division). */
+ public static final int KEYCODE_NUMPAD_DIVIDE = 154;
+ /** Key code constant: Numeric keypad '*' key (for multiplication). */
+ public static final int KEYCODE_NUMPAD_MULTIPLY = 155;
+ /** Key code constant: Numeric keypad '-' key (for subtraction). */
+ public static final int KEYCODE_NUMPAD_SUBTRACT = 156;
+ /** Key code constant: Numeric keypad '+' key (for addition). */
+ public static final int KEYCODE_NUMPAD_ADD = 157;
+ /** Key code constant: Numeric keypad '.' key (for decimals or digit grouping). */
+ public static final int KEYCODE_NUMPAD_DOT = 158;
+ /** Key code constant: Numeric keypad ',' key (for decimals or digit grouping). */
+ public static final int KEYCODE_NUMPAD_COMMA = 159;
+ /** Key code constant: Numeric keypad Enter key. */
+ public static final int KEYCODE_NUMPAD_ENTER = 160;
+ /** Key code constant: Numeric keypad '=' key. */
+ public static final int KEYCODE_NUMPAD_EQUALS = 161;
+ /** Key code constant: Numeric keypad '(' key. */
+ public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162;
+ /** Key code constant: Numeric keypad ')' key. */
+ public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163;
+ /** Key code constant: Volume Mute key.
+ * Mutes the speaker, unlike {@link #KEYCODE_MUTE}.
+ * This key should normally be implemented as a toggle such that the first press
+ * mutes the speaker and the second press restores the original volume. */
+ public static final int KEYCODE_VOLUME_MUTE = 164;
+ /** Key code constant: Info key.
+ * Common on TV remotes to show additional information related to what is
+ * currently being viewed. */
+ public static final int KEYCODE_INFO = 165;
+ /** Key code constant: Channel up key.
+ * On TV remotes, increments the television channel. */
+ public static final int KEYCODE_CHANNEL_UP = 166;
+ /** Key code constant: Channel down key.
+ * On TV remotes, decrements the television channel. */
+ public static final int KEYCODE_CHANNEL_DOWN = 167;
+ /** Key code constant: Zoom in key. */
+ public static final int KEYCODE_ZOOM_IN = 168;
+ /** Key code constant: Zoom out key. */
+ public static final int KEYCODE_ZOOM_OUT = 169;
+ /** Key code constant: TV key.
+ * On TV remotes, switches to viewing live TV. */
+ public static final int KEYCODE_TV = 170;
+ /** Key code constant: Window key.
+ * On TV remotes, toggles picture-in-picture mode or other windowing functions. */
+ public static final int KEYCODE_WINDOW = 171;
+ /** Key code constant: Guide key.
+ * On TV remotes, shows a programming guide. */
+ public static final int KEYCODE_GUIDE = 172;
+ /** Key code constant: DVR key.
+ * On some TV remotes, switches to a DVR mode for recorded shows. */
+ public static final int KEYCODE_DVR = 173;
+ /** Key code constant: Bookmark key.
+ * On some TV remotes, bookmarks content or web pages. */
+ public static final int KEYCODE_BOOKMARK = 174;
+ /** Key code constant: Toggle captions key.
+ * Switches the mode for closed-captioning text, for example during television shows. */
+ public static final int KEYCODE_CAPTIONS = 175;
+ /** Key code constant: Settings key.
+ * Starts the system settings activity. */
+ public static final int KEYCODE_SETTINGS = 176;
+ /** Key code constant: TV power key.
+ * On TV remotes, toggles the power on a television screen. */
+ public static final int KEYCODE_TV_POWER = 177;
+ /** Key code constant: TV input key.
+ * On TV remotes, switches the input on a television screen. */
+ public static final int KEYCODE_TV_INPUT = 178;
+ /** Key code constant: Set-top-box power key.
+ * On TV remotes, toggles the power on an external Set-top-box. */
+ public static final int KEYCODE_STB_POWER = 179;
+ /** Key code constant: Set-top-box input key.
+ * On TV remotes, switches the input mode on an external Set-top-box. */
+ public static final int KEYCODE_STB_INPUT = 180;
+ /** Key code constant: A/V Receiver power key.
+ * On TV remotes, toggles the power on an external A/V Receiver. */
+ public static final int KEYCODE_AVR_POWER = 181;
+ /** Key code constant: A/V Receiver input key.
+ * On TV remotes, switches the input mode on an external A/V Receiver. */
+ public static final int KEYCODE_AVR_INPUT = 182;
+ /** Key code constant: Red "programmable" key.
+ * On TV remotes, acts as a contextual/programmable key. */
+ public static final int KEYCODE_PROG_RED = 183;
+ /** Key code constant: Green "programmable" key.
+ * On TV remotes, actsas a contextual/programmable key. */
+ public static final int KEYCODE_PROG_GREEN = 184;
+ /** Key code constant: Yellow "programmable" key.
+ * On TV remotes, acts as a contextual/programmable key. */
+ public static final int KEYCODE_PROG_YELLOW = 185;
+ /** Key code constant: Blue "programmable" key.
+ * On TV remotes, acts as a contextual/programmable key. */
+ public static final int KEYCODE_PROG_BLUE = 186;
+
+ private static final int LAST_KEYCODE = KEYCODE_PROG_BLUE;
+
+ private String[] mKeyCodes = new String[256];
+ private String[] mAppKeyCodes = new String[256];
+
+ private void initKeyCodes() {
+ mKeyCodes[KEYCODE_DPAD_CENTER] = "\015";
+ mKeyCodes[KEYCODE_DPAD_UP] = "\033[A";
+ mKeyCodes[KEYCODE_DPAD_DOWN] = "\033[B";
+ mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033[C";
+ mKeyCodes[KEYCODE_DPAD_LEFT] = "\033[D";
+ mKeyCodes[KEYCODE_F1] = "\033[OP";
+ mKeyCodes[KEYCODE_F2] = "\033[OQ";
+ mKeyCodes[KEYCODE_F3] = "\033[OR";
+ mKeyCodes[KEYCODE_F4] = "\033[OS";
+ mKeyCodes[KEYCODE_F5] = "\033[15~";
+ mKeyCodes[KEYCODE_F6] = "\033[17~";
+ mKeyCodes[KEYCODE_F7] = "\033[18~";
+ mKeyCodes[KEYCODE_F8] = "\033[19~";
+ mKeyCodes[KEYCODE_F9] = "\033[20~";
+ mKeyCodes[KEYCODE_F10] = "\033[21~";
+ mKeyCodes[KEYCODE_F11] = "\033[23~";
+ mKeyCodes[KEYCODE_F12] = "\033[24~";
+ mKeyCodes[KEYCODE_SYSRQ] = "\033[32~"; // Sys Request / Print
+ // Is this Scroll lock? mKeyCodes[Cancel] = "\033[33~";
+ mKeyCodes[KEYCODE_BREAK] = "\033[34~"; // Pause/Break
+
+ mKeyCodes[KEYCODE_TAB] = "\011";
+ mKeyCodes[KEYCODE_ENTER] = "\015";
+ mKeyCodes[KEYCODE_ESCAPE] = "\033";
+
+ mKeyCodes[KEYCODE_INSERT] = "\033[2~";
+ mKeyCodes[KEYCODE_FORWARD_DEL] = "\033[3~";
+ mKeyCodes[KEYCODE_MOVE_HOME] = "\033[1~";
+ mKeyCodes[KEYCODE_MOVE_END] = "\033[4~";
+ mKeyCodes[KEYCODE_PAGE_UP] = "\033[5~";
+ mKeyCodes[KEYCODE_PAGE_DOWN] = "\033[6~";
+ mKeyCodes[KEYCODE_DEL]= "\177";
+ mKeyCodes[KEYCODE_NUM_LOCK] = "\033OP";
+ mKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "/";
+ mKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "*";
+ mKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "-";
+ mKeyCodes[KEYCODE_NUMPAD_ADD] = "+";
+ mKeyCodes[KEYCODE_NUMPAD_ENTER] = "\015";
+ mKeyCodes[KEYCODE_NUMPAD_EQUALS] = "=";
+ mKeyCodes[KEYCODE_NUMPAD_DOT] = ".";
+ mKeyCodes[KEYCODE_NUMPAD_COMMA] = ",";
+ mKeyCodes[KEYCODE_NUMPAD_0] = "0";
+ mKeyCodes[KEYCODE_NUMPAD_1] = "1";
+ mKeyCodes[KEYCODE_NUMPAD_2] = "2";
+ mKeyCodes[KEYCODE_NUMPAD_3] = "3";
+ mKeyCodes[KEYCODE_NUMPAD_4] = "4";
+ mKeyCodes[KEYCODE_NUMPAD_5] = "5";
+ mKeyCodes[KEYCODE_NUMPAD_6] = "6";
+ mKeyCodes[KEYCODE_NUMPAD_7] = "7";
+ mKeyCodes[KEYCODE_NUMPAD_8] = "8";
+ mKeyCodes[KEYCODE_NUMPAD_9] = "9";
+
+ mAppKeyCodes[KEYCODE_DPAD_UP] = "\033OA";
+ mAppKeyCodes[KEYCODE_DPAD_DOWN] = "\033OB";
+ mAppKeyCodes[KEYCODE_DPAD_RIGHT] = "\033OC";
+ mAppKeyCodes[KEYCODE_DPAD_LEFT] = "\033OD";
+ mAppKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "\033Oo";
+ mAppKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "\033Oj";
+ mAppKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "\033Om";
+ mAppKeyCodes[KEYCODE_NUMPAD_ADD] = "\033Ok";
+ mAppKeyCodes[KEYCODE_NUMPAD_ENTER] = "\033OM";
+ mAppKeyCodes[KEYCODE_NUMPAD_EQUALS] = "\033OX";
+ mAppKeyCodes[KEYCODE_NUMPAD_DOT] = "\033On";
+ mAppKeyCodes[KEYCODE_NUMPAD_COMMA] = "\033Ol";
+ mAppKeyCodes[KEYCODE_NUMPAD_0] = "\033Op";
+ mAppKeyCodes[KEYCODE_NUMPAD_1] = "\033Oq";
+ mAppKeyCodes[KEYCODE_NUMPAD_2] = "\033Or";
+ mAppKeyCodes[KEYCODE_NUMPAD_3] = "\033Os";
+ mAppKeyCodes[KEYCODE_NUMPAD_4] = "\033Ot";
+ mAppKeyCodes[KEYCODE_NUMPAD_5] = "\033Ou";
+ mAppKeyCodes[KEYCODE_NUMPAD_6] = "\033Ov";
+ mAppKeyCodes[KEYCODE_NUMPAD_7] = "\033Ow";
+ mAppKeyCodes[KEYCODE_NUMPAD_8] = "\033Ox";
+ mAppKeyCodes[KEYCODE_NUMPAD_9] = "\033Oy";
+ }
+
+ /**
+ * The state engine for a modifier key. Can be pressed, released, locked,
+ * and so on.
+ *
+ */
+ private class ModifierKey {
+
+ private int mState;
+
+ private static final int UNPRESSED = 0;
+
+ private static final int PRESSED = 1;
+
+ private static final int RELEASED = 2;
+
+ private static final int USED = 3;
+
+ private static final int LOCKED = 4;
+
+ /**
+ * Construct a modifier key. UNPRESSED by default.
+ *
+ */
+ public ModifierKey() {
+ mState = UNPRESSED;
+ }
+
+ public void onPress() {
+ switch (mState) {
+ case PRESSED:
+ // This is a repeat before use
+ break;
+ case RELEASED:
+ mState = LOCKED;
+ break;
+ case USED:
+ // This is a repeat after use
+ break;
+ case LOCKED:
+ mState = UNPRESSED;
+ break;
+ default:
+ mState = PRESSED;
+ break;
+ }
+ }
+
+ public void onRelease() {
+ switch (mState) {
+ case USED:
+ mState = UNPRESSED;
+ break;
+ case PRESSED:
+ mState = RELEASED;
+ break;
+ default:
+ // Leave state alone
+ break;
+ }
+ }
+
+ public void adjustAfterKeypress() {
+ switch (mState) {
+ case PRESSED:
+ mState = USED;
+ break;
+ case RELEASED:
+ mState = UNPRESSED;
+ break;
+ default:
+ // Leave state alone
+ break;
+ }
+ }
+
+ public boolean isActive() {
+ return mState != UNPRESSED;
+ }
+ }
+
+ private ModifierKey mAltKey = new ModifierKey();
+
+ private ModifierKey mCapKey = new ModifierKey();
+
+ private ModifierKey mControlKey = new ModifierKey();
+
+ private ModifierKey mFnKey = new ModifierKey();
+
+ private boolean mCapsLock;
+
+ static public final int KEYCODE_OFFSET = 1000;
+
+ /**
+ * Construct a term key listener.
+ *
+ */
+ public TermKeyListener() {
+ initKeyCodes();
+ }
+
+ public void handleControlKey(boolean down) {
+ if (down) {
+ mControlKey.onPress();
+ } else {
+ mControlKey.onRelease();
+ }
+ }
+
+ public void handleFnKey(boolean down) {
+ if (down) {
+ mFnKey.onPress();
+ } else {
+ mFnKey.onRelease();
+ }
+ }
+
+ public int mapControlChar(int ch) {
+ int result = ch;
+ if (mControlKey.isActive()) {
+ // Search is the control key.
+ if (result >= 'a' && result <= 'z') {
+ result = (char) (result - 'a' + '\001');
+ } else if (result >= 'A' && result <= 'Z') {
+ result = (char) (result - 'A' + '\001');
+ } else if (result == ' ' || result == '2') {
+ result = 0;
+ } else if (result == '[' || result == '3') {
+ result = 27; // ^[ (Esc)
+ } else if (result == '\\' || result == '4') {
+ result = 28;
+ } else if (result == ']' || result == '5') {
+ result = 29;
+ } else if (result == '^' || result == '6') {
+ result = 30; // control-^
+ } else if (result == '_' || result == '7') {
+ result = 31;
+ } else if (result == '8') {
+ result = 127; // DEL
+ } else if (result == '9') {
+ result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F11;
+ } else if (result == '0') {
+ result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F12;
+ }
+ } else if (mFnKey.isActive()) {
+ if (result == 'w' || result == 'W') {
+ result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP;
+ } else if (result == 'a' || result == 'A') {
+ result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT;
+ } else if (result == 's' || result == 'S') {
+ result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN;
+ } else if (result == 'd' || result == 'D') {
+ result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT;
+ } else if (result == 'p' || result == 'P') {
+ result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_UP;
+ } else if (result == 'n' || result == 'N') {
+ result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_DOWN;
+ } else if (result == 't' || result == 'T') {
+ result = KEYCODE_OFFSET + KeyEvent.KEYCODE_TAB;
+ } else if (result == 'l' || result == 'L') {
+ result = '|';
+ } else if (result == 'u' || result == 'U') {
+ result = '_';
+ } else if (result == 'e' || result == 'E') {
+ result = 27; // ^[ (Esc)
+ } else if (result == '.') {
+ result = 28; // ^\
+ } else if (result > '0' && result <= '9') {
+ // F1-F9
+ result = (char)(result + KEYCODE_OFFSET + TermKeyListener.KEYCODE_F1 - 1);
+ } else if (result == '0') {
+ result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F10;
+ } else if (result == 'i' || result == 'I') {
+ result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_INSERT;
+ } else if (result == 'x' || result == 'X') {
+ result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_FORWARD_DEL;
+ } else if (result == 'h' || result == 'H') {
+ result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_HOME;
+ } else if (result == 'f' || result == 'F') {
+ result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_END;
+ }
+ }
+
+ if (result > -1) {
+ mAltKey.adjustAfterKeypress();
+ mCapKey.adjustAfterKeypress();
+ mControlKey.adjustAfterKeypress();
+ mFnKey.adjustAfterKeypress();
+ }
+
+ return result;
+ }
+
+ /**
+ * Handle a keyDown event.
+ *
+ * @param keyCode the keycode of the keyDown event
+ *
+ */
+ public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMode) throws IOException {
+ if (handleKeyCode(keyCode, out, appMode)) {
+ return;
+ }
+ int result = -1;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_ALT_RIGHT:
+ case KeyEvent.KEYCODE_ALT_LEFT:
+ mAltKey.onPress();
+ break;
+
+ case KeyEvent.KEYCODE_SHIFT_LEFT:
+ case KeyEvent.KEYCODE_SHIFT_RIGHT:
+ mCapKey.onPress();
+ break;
+
+ case KEYCODE_CTRL_LEFT:
+ case KEYCODE_CTRL_RIGHT:
+ mControlKey.onPress();
+ break;
+
+ case KEYCODE_CAPS_LOCK:
+ if (event.getRepeatCount() == 0) {
+ mCapsLock = !mCapsLock;
+ }
+ break;
+
+ default: {
+ result = event.getUnicodeChar(
+ (mCapKey.isActive() || mCapsLock ? KeyEvent.META_SHIFT_ON : 0) |
+ (mAltKey.isActive() ? KeyEvent.META_ALT_ON : 0));
+ break;
+ }
+ }
+
+ result = mapControlChar(result);
+
+ if (result >= KEYCODE_OFFSET) {
+ handleKeyCode(result - KEYCODE_OFFSET, out, appMode);
+ } else if (result >= 0) {
+ out.write(result);
+ }
+ }
+
+ public boolean handleKeyCode(int keyCode, OutputStream out, boolean appMode) throws IOException {
+ if (keyCode >= 0 && keyCode < mKeyCodes.length) {
+ String code = null;
+ if (appMode) {
+ code = mAppKeyCodes[keyCode];
+ }
+ if (code == null) {
+ code = mKeyCodes[keyCode];
+ }
+ if (code != null) {
+ int length = code.length();
+ for (int i = 0; i < length; i++) {
+ out.write(code.charAt(i));
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Handle a keyUp event.
+ *
+ * @param keyCode the keyCode of the keyUp event
+ */
+ public void keyUp(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_ALT_LEFT:
+ case KeyEvent.KEYCODE_ALT_RIGHT:
+ mAltKey.onRelease();
+ break;
+ case KeyEvent.KEYCODE_SHIFT_LEFT:
+ case KeyEvent.KEYCODE_SHIFT_RIGHT:
+ mCapKey.onRelease();
+ break;
+
+ case KEYCODE_CTRL_LEFT:
+ case KEYCODE_CTRL_RIGHT:
+ mControlKey.onRelease();
+ break;
+
+ default:
+ // Ignore other keyUps
+ break;
+ }
+ }
+}
package jackpal.androidterm;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.Typeface;
import android.net.Uri;
+import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.text.ClipboardManager;
-import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
-import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
-import android.view.inputmethod.BaseInputConnection;
-import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.CorrectionInfo;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.ExtractedText;
-import android.view.inputmethod.ExtractedTextRequest;
-import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
-import android.widget.TextView;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Binder;
-import android.os.IBinder;
-
-import android.net.wifi.WifiManager;
-import android.os.PowerManager;
/**
* A terminal emulator activity.
}
}
}
-
-
-/**
- * An abstract screen interface. A terminal screen stores lines of text. (The
- * reason to abstract it is to allow different implementations, and to hide
- * implementation details from clients.)
- */
-interface Screen {
-
- /**
- * Set line wrap flag for a given row. Affects how lines are logically
- * wrapped when changing screen size or converting to a transcript.
- */
- void setLineWrap(int row);
-
- /**
- * Store byte b into the screen at location (x, y)
- *
- * @param x X coordinate (also known as column)
- * @param y Y coordinate (also known as row)
- * @param b ASCII character to store
- * @param foreColor the foreground color
- * @param backColor the background color
- */
- void set(int x, int y, byte b, int foreColor, int backColor);
-
- /**
- * Scroll the screen down one line. To scroll the whole screen of a 24 line
- * screen, the arguments would be (0, 24).
- *
- * @param topMargin First line that is scrolled.
- * @param bottomMargin One line after the last line that is scrolled.
- */
- void scroll(int topMargin, int bottomMargin, int foreColor, int backColor);
-
- /**
- * Block copy characters from one position in the screen to another. The two
- * positions can overlap. All characters of the source and destination must
- * be within the bounds of the screen, or else an InvalidParemeterException
- * will be thrown.
- *
- * @param sx source X coordinate
- * @param sy source Y coordinate
- * @param w width
- * @param h height
- * @param dx destination X coordinate
- * @param dy destination Y coordinate
- */
- void blockCopy(int sx, int sy, int w, int h, int dx, int dy);
-
- /**
- * Block set characters. All characters must be within the bounds of the
- * screen, or else and InvalidParemeterException will be thrown. Typically
- * this is called with a "val" argument of 32 to clear a block of
- * characters.
- *
- * @param sx source X
- * @param sy source Y
- * @param w width
- * @param h height
- * @param val value to set.
- * @param foreColor the foreground color
- * @param backColor the background color
- */
- void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int
- backColor);
-
- /**
- * Get the contents of the transcript buffer as a text string.
- *
- * @return the contents of the transcript buffer.
- */
- String getTranscriptText();
-
- /**
- * Get the selected text inside transcript buffer as a text string.
- * @param x1 Selection start
- * @param y1 Selection start
- * @param x2 Selection end
- * @param y2 Selection end
- * @return the contents of the transcript buffer.
- */
- String getSelectedText(int x1, int y1, int x2, int y2);
-
- /**
- * Resize the screen
- * @param columns
- * @param rows
- */
- void resize(int columns, int rows, int foreColor, int backColor);
-}
-
-
-/**
- * A TranscriptScreen is a screen that remembers data that's been scrolled. The
- * old data is stored in a ring buffer to minimize the amount of copying that
- * needs to be done. The transcript does its own drawing, to avoid having to
- * expose its internal data structures.
- */
-class TranscriptScreen implements Screen {
- private static final String TAG = "TranscriptScreen";
-
- /**
- * The width of the transcript, in characters. Fixed at initialization.
- */
- private int mColumns;
-
- /**
- * The total number of rows in the transcript and the screen. Fixed at
- * initialization.
- */
- private int mTotalRows;
-
- /**
- * The number of rows in the active portion of the transcript. Doesn't
- * include the screen.
- */
- private int mActiveTranscriptRows;
-
- /**
- * Which row is currently the topmost line of the transcript. Used to
- * implement a circular buffer.
- */
- private int mHead;
-
- /**
- * The number of active rows, includes both the transcript and the screen.
- */
- private int mActiveRows;
-
- /**
- * The number of rows in the screen.
- */
- private int mScreenRows;
-
- /**
- * The data for both the screen and the transcript. The first mScreenRows *
- * mLineWidth characters are the screen, the rest are the transcript.
- * The low byte encodes the ASCII character, the high byte encodes the
- * foreground and background colors, plus underline and bold.
- */
- private char[] mData;
-
- /**
- * The data's stored as color-encoded chars, but the drawing routines require chars, so we
- * need a temporary buffer to hold a row's worth of characters.
- */
- private char[] mRowBuffer;
-
- /**
- * Flags that keep track of whether the current line logically wraps to the
- * next line. This is used when resizing the screen and when copying to the
- * clipboard or an email attachment
- */
-
- private boolean[] mLineWrap;
-
- /**
- * Create a transcript screen.
- *
- * @param columns the width of the screen in characters.
- * @param totalRows the height of the entire text area, in rows of text.
- * @param screenRows the height of just the screen, not including the
- * transcript that holds lines that have scrolled off the top of the
- * screen.
- */
- public TranscriptScreen(int columns, int totalRows, int screenRows,
- int foreColor, int backColor) {
- init(columns, totalRows, screenRows, foreColor, backColor);
- }
-
- private void init(int columns, int totalRows, int screenRows, int foreColor, int backColor) {
- mColumns = columns;
- mTotalRows = totalRows;
- mActiveTranscriptRows = 0;
- mHead = 0;
- mActiveRows = screenRows;
- mScreenRows = screenRows;
- int totalSize = columns * totalRows;
- mData = new char[totalSize];
- blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor);
- mRowBuffer = new char[columns];
- mLineWrap = new boolean[totalRows];
- consistencyCheck();
- }
-
- /**
- * Convert a row value from the public external coordinate system to our
- * internal private coordinate system. External coordinate system:
- * -mActiveTranscriptRows to mScreenRows-1, with the screen being
- * 0..mScreenRows-1 Internal coordinate system: 0..mScreenRows-1 rows of
- * mData are the visible rows. mScreenRows..mActiveRows - 1 are the
- * transcript, stored as a circular buffer.
- *
- * @param row a row in the external coordinate system.
- * @return The row corresponding to the input argument in the private
- * coordinate system.
- */
- private int externalToInternalRow(int row) {
- if (row < -mActiveTranscriptRows || row >= mScreenRows) {
- String errorMessage = "externalToInternalRow "+ row +
- " " + mActiveTranscriptRows + " " + mScreenRows;
- Log.e(TAG, errorMessage);
- throw new IllegalArgumentException(errorMessage);
- }
- if (row >= 0) {
- return row; // This is a visible row.
- }
- return mScreenRows
- + ((mHead + mActiveTranscriptRows + row) % mActiveTranscriptRows);
- }
-
- private int getOffset(int externalLine) {
- return externalToInternalRow(externalLine) * mColumns;
- }
-
- private int getOffset(int x, int y) {
- return getOffset(y) + x;
- }
-
- public void setLineWrap(int row) {
- mLineWrap[externalToInternalRow(row)] = true;
- }
-
- /**
- * Store byte b into the screen at location (x, y)
- *
- * @param x X coordinate (also known as column)
- * @param y Y coordinate (also known as row)
- * @param b ASCII character to store
- * @param foreColor the foreground color
- * @param backColor the background color
- */
- public void set(int x, int y, byte b, int foreColor, int backColor) {
- mData[getOffset(x, y)] = encode(b, foreColor, backColor);
- }
-
- private char encode(int b, int foreColor, int backColor) {
- return (char) ((foreColor << 12) | (backColor << 8) | b);
- }
-
- /**
- * Scroll the screen down one line. To scroll the whole screen of a 24 line
- * screen, the arguments would be (0, 24).
- *
- * @param topMargin First line that is scrolled.
- * @param bottomMargin One line after the last line that is scrolled.
- */
- public void scroll(int topMargin, int bottomMargin, int foreColor,
- int backColor) {
- // Separate out reasons so that stack crawls help us
- // figure out which condition was violated.
- if (topMargin > bottomMargin - 1) {
- throw new IllegalArgumentException();
- }
-
- if (topMargin > mScreenRows - 1) {
- throw new IllegalArgumentException();
- }
-
- if (bottomMargin > mScreenRows) {
- throw new IllegalArgumentException();
- }
-
- // Adjust the transcript so that the last line of the transcript
- // is ready to receive the newly scrolled data
- consistencyCheck();
- int expansionRows = Math.min(1, mTotalRows - mActiveRows);
- int rollRows = 1 - expansionRows;
- mActiveRows += expansionRows;
- mActiveTranscriptRows += expansionRows;
- if (mActiveTranscriptRows > 0) {
- mHead = (mHead + rollRows) % mActiveTranscriptRows;
- }
- consistencyCheck();
-
- // Block move the scroll line to the transcript
- int topOffset = getOffset(topMargin);
- int destOffset = getOffset(-1);
- System.arraycopy(mData, topOffset, mData, destOffset, mColumns);
-
- int topLine = externalToInternalRow(topMargin);
- int destLine = externalToInternalRow(-1);
- System.arraycopy(mLineWrap, topLine, mLineWrap, destLine, 1);
-
- // Block move the scrolled data up
- int numScrollChars = (bottomMargin - topMargin - 1) * mColumns;
- System.arraycopy(mData, topOffset + mColumns, mData, topOffset,
- numScrollChars);
- int numScrollLines = (bottomMargin - topMargin - 1);
- System.arraycopy(mLineWrap, topLine + 1, mLineWrap, topLine,
- numScrollLines);
-
- // Erase the bottom line of the scroll region
- blockSet(0, bottomMargin - 1, mColumns, 1, ' ', foreColor, backColor);
- mLineWrap[externalToInternalRow(bottomMargin-1)] = false;
- }
-
- private void consistencyCheck() {
- checkPositive(mColumns);
- checkPositive(mTotalRows);
- checkRange(0, mActiveTranscriptRows, mTotalRows);
- if (mActiveTranscriptRows == 0) {
- checkEqual(mHead, 0);
- } else {
- checkRange(0, mHead, mActiveTranscriptRows-1);
- }
- checkEqual(mScreenRows + mActiveTranscriptRows, mActiveRows);
- checkRange(0, mScreenRows, mTotalRows);
-
- checkEqual(mTotalRows, mLineWrap.length);
- checkEqual(mTotalRows*mColumns, mData.length);
- checkEqual(mColumns, mRowBuffer.length);
- }
-
- private void checkPositive(int n) {
- if (n < 0) {
- throw new IllegalArgumentException("checkPositive " + n);
- }
- }
-
- private void checkRange(int a, int b, int c) {
- if (a > b || b > c) {
- throw new IllegalArgumentException("checkRange " + a + " <= " + b + " <= " + c);
- }
- }
-
- private void checkEqual(int a, int b) {
- if (a != b) {
- throw new IllegalArgumentException("checkEqual " + a + " == " + b);
- }
- }
-
- /**
- * Block copy characters from one position in the screen to another. The two
- * positions can overlap. All characters of the source and destination must
- * be within the bounds of the screen, or else an InvalidParemeterException
- * will be thrown.
- *
- * @param sx source X coordinate
- * @param sy source Y coordinate
- * @param w width
- * @param h height
- * @param dx destination X coordinate
- * @param dy destination Y coordinate
- */
- public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
- if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows
- || dx < 0 || dx + w > mColumns || dy < 0
- || dy + h > mScreenRows) {
- throw new IllegalArgumentException();
- }
- if (sy > dy) {
- // Move in increasing order
- for (int y = 0; y < h; y++) {
- int srcOffset = getOffset(sx, sy + y);
- int dstOffset = getOffset(dx, dy + y);
- System.arraycopy(mData, srcOffset, mData, dstOffset, w);
- }
- } else {
- // Move in decreasing order
- for (int y = 0; y < h; y++) {
- int y2 = h - (y + 1);
- int srcOffset = getOffset(sx, sy + y2);
- int dstOffset = getOffset(dx, dy + y2);
- System.arraycopy(mData, srcOffset, mData, dstOffset, w);
- }
- }
- }
-
- /**
- * Block set characters. All characters must be within the bounds of the
- * screen, or else and InvalidParemeterException will be thrown. Typically
- * this is called with a "val" argument of 32 to clear a block of
- * characters.
- *
- * @param sx source X
- * @param sy source Y
- * @param w width
- * @param h height
- * @param val value to set.
- */
- public void blockSet(int sx, int sy, int w, int h, int val,
- int foreColor, int backColor) {
- if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
- throw new IllegalArgumentException();
- }
- char[] data = mData;
- char encodedVal = encode(val, foreColor, backColor);
- for (int y = 0; y < h; y++) {
- int offset = getOffset(sx, sy + y);
- for (int x = 0; x < w; x++) {
- data[offset + x] = encodedVal;
- }
- }
- }
-
- /**
- * Draw a row of text. Out-of-bounds rows are blank, not errors.
- *
- * @param row The row of text to draw.
- * @param canvas The canvas to draw to.
- * @param x The x coordinate origin of the drawing
- * @param y The y coordinate origin of the drawing
- * @param renderer The renderer to use to draw the text
- * @param cx the cursor X coordinate, -1 means don't draw it
- * @param selx1 the text selection start X coordinate
- * @param selx2 the text selection end X coordinate, if equals to selx1 don't draw selection
- * @param imeText current IME text, to be rendered at cursor
- */
- public final void drawText(int row, Canvas canvas, float x, float y,
- TextRenderer renderer, int cx, int selx1, int selx2, String imeText) {
-
- // Out-of-bounds rows are blank.
- if (row < -mActiveTranscriptRows || row >= mScreenRows) {
- return;
- }
-
- // Copy the data from the byte array to a char array so they can
- // be drawn.
-
- int offset = getOffset(row);
- char[] rowBuffer = mRowBuffer;
- char[] data = mData;
- int columns = mColumns;
- int lastColors = 0;
- int lastRunStart = -1;
- final int CURSOR_MASK = 0x10000;
- for (int i = 0; i < columns; i++) {
- char c = data[offset + i];
- int colors = (char) (c & 0xff00);
- if (cx == i || (i >= selx1 && i <= selx2)) {
- // Set cursor background color:
- colors |= CURSOR_MASK;
- }
- rowBuffer[i] = (char) (c & 0x00ff);
- if (colors != lastColors) {
- if (lastRunStart >= 0) {
- renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
- lastRunStart, i - lastRunStart,
- (lastColors & CURSOR_MASK) != 0,
- 0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
- }
- lastColors = colors;
- lastRunStart = i;
- }
- }
- if (lastRunStart >= 0) {
- renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
- lastRunStart, columns - lastRunStart,
- (lastColors & CURSOR_MASK) != 0,
- 0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
- }
-
- if (cx >= 0 && imeText.length() > 0) {
- int imeLength = Math.min(columns, imeText.length());
- int imeOffset = imeText.length() - imeLength;
- int imePosition = Math.min(cx, columns - imeLength);
- renderer.drawTextRun(canvas, x, y, imePosition, imeText.toCharArray(),
- imeOffset, imeLength, true, 0x0f, 0x00);
- }
- }
-
- /**
- * Get the count of active rows.
- *
- * @return the count of active rows.
- */
- public int getActiveRows() {
- return mActiveRows;
- }
-
- /**
- * Get the count of active transcript rows.
- *
- * @return the count of active transcript rows.
- */
- public int getActiveTranscriptRows() {
- return mActiveTranscriptRows;
- }
-
- public String getTranscriptText() {
- return internalGetTranscriptText(true, 0, -mActiveTranscriptRows, mColumns, mScreenRows);
- }
-
- public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
- return internalGetTranscriptText(true, selX1, selY1, selX2, selY2);
- }
-
- private String internalGetTranscriptText(boolean stripColors, int selX1, int selY1, int selX2, int selY2) {
- StringBuilder builder = new StringBuilder();
- char[] rowBuffer = mRowBuffer;
- char[] data = mData;
- int columns = mColumns;
- for (int row = -mActiveTranscriptRows; row < mScreenRows; row++) {
- int offset = getOffset(row);
- int lastPrintingChar = -1;
- for (int column = 0; column < columns; column++) {
- char c = data[offset + column];
- if (stripColors) {
- c = (char) (c & 0xff);
- }
- if ((c & 0xff) != ' ') {
- lastPrintingChar = column;
- }
- rowBuffer[column] = c;
- }
- if ( row >= selY1 && row <= selY2 ) {
- int x1 = 0;
- int x2 = 0;
- if ( row == selY1 ) {
- x1 = selX1;
- }
- if ( row == selY2 ) {
- x2 = selX2;
- } else {
- x2 = columns;
- }
- if (mLineWrap[externalToInternalRow(row)]) {
- builder.append(rowBuffer, x1, x2 - x1);
- } else {
- builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1)));
- builder.append('\n');
- }
- }
- }
- return builder.toString();
- }
-
- public void resize(int columns, int rows, int foreColor, int backColor) {
- init(columns, mTotalRows, rows, foreColor, backColor);
- }
-}
-
-/**
- * Renders text into a screen. Contains all the terminal-specific knowlege and
- * state. Emulates a subset of the X Window System xterm terminal, which in turn
- * is an emulator for a subset of the Digital Equipment Corporation vt100
- * terminal. Missing functionality: text attributes (bold, underline, reverse
- * video, color) alternate screen cursor key and keypad escape sequences.
- */
-class TerminalEmulator {
-
- /**
- * The cursor row. Numbered 0..mRows-1.
- */
- private int mCursorRow;
-
- /**
- * The cursor column. Numbered 0..mColumns-1.
- */
- private int mCursorCol;
-
- /**
- * The number of character rows in the terminal screen.
- */
- private int mRows;
-
- /**
- * The number of character columns in the terminal screen.
- */
- private int mColumns;
-
- /**
- * Used to send data to the remote process. Needed to implement the various
- * "report" escape sequences.
- */
- private FileOutputStream mTermOut;
-
- /**
- * Stores the characters that appear on the screen of the emulated terminal.
- */
- private Screen mScreen;
-
- /**
- * Keeps track of the current argument of the current escape sequence.
- * Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. (Typically just 0 or 1.)
- */
- private int mArgIndex;
-
- /**
- * The number of parameter arguments. This name comes from the ANSI standard
- * for terminal escape codes.
- */
- private static final int MAX_ESCAPE_PARAMETERS = 16;
-
- /**
- * Holds the arguments of the current escape sequence.
- */
- private int[] mArgs = new int[MAX_ESCAPE_PARAMETERS];
-
- // Escape processing states:
-
- /**
- * Escape processing state: Not currently in an escape sequence.
- */
- private static final int ESC_NONE = 0;
-
- /**
- * Escape processing state: Have seen an ESC character
- */
- private static final int ESC = 1;
-
- /**
- * Escape processing state: Have seen ESC POUND
- */
- private static final int ESC_POUND = 2;
-
- /**
- * Escape processing state: Have seen ESC and a character-set-select char
- */
- private static final int ESC_SELECT_LEFT_PAREN = 3;
-
- /**
- * Escape processing state: Have seen ESC and a character-set-select char
- */
- private static final int ESC_SELECT_RIGHT_PAREN = 4;
-
- /**
- * Escape processing state: ESC [
- */
- private static final int ESC_LEFT_SQUARE_BRACKET = 5;
-
- /**
- * Escape processing state: ESC [ ?
- */
- private static final int ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK = 6;
-
- /**
- * True if the current escape sequence should continue, false if the current
- * escape sequence should be terminated. Used when parsing a single
- * character.
- */
- private boolean mContinueSequence;
-
- /**
- * The current state of the escape sequence state machine.
- */
- private int mEscapeState;
-
- /**
- * Saved state of the cursor row, Used to implement the save/restore cursor
- * position escape sequences.
- */
- private int mSavedCursorRow;
-
- /**
- * Saved state of the cursor column, Used to implement the save/restore
- * cursor position escape sequences.
- */
- private int mSavedCursorCol;
-
- // DecSet booleans
-
- /**
- * This mask indicates 132-column mode is set. (As opposed to 80-column
- * mode.)
- */
- private static final int K_132_COLUMN_MODE_MASK = 1 << 3;
-
- /**
- * This mask indicates that origin mode is set. (Cursor addressing is
- * relative to the absolute screen size, rather than the currently set top
- * and bottom margins.)
- */
- private static final int K_ORIGIN_MODE_MASK = 1 << 6;
-
- /**
- * This mask indicates that wraparound mode is set. (As opposed to
- * stop-at-right-column mode.)
- */
- private static final int K_WRAPAROUND_MODE_MASK = 1 << 7;
-
- /**
- * Holds multiple DECSET flags. The data is stored this way, rather than in
- * separate booleans, to make it easier to implement the save-and-restore
- * semantics. The various k*ModeMask masks can be used to extract and modify
- * the individual flags current states.
- */
- private int mDecFlags;
-
- /**
- * Saves away a snapshot of the DECSET flags. Used to implement save and
- * restore escape sequences.
- */
- private int mSavedDecFlags;
-
- // Modes set with Set Mode / Reset Mode
-
- /**
- * True if insert mode (as opposed to replace mode) is active. In insert
- * mode new characters are inserted, pushing existing text to the right.
- */
- private boolean mInsertMode;
-
- /**
- * Automatic newline mode. Configures whether pressing return on the
- * keyboard automatically generates a return as well. Not currently
- * implemented.
- */
- private boolean mAutomaticNewlineMode;
-
- /**
- * An array of tab stops. mTabStop[i] is true if there is a tab stop set for
- * column i.
- */
- private boolean[] mTabStop;
-
- // The margins allow portions of the screen to be locked.
-
- /**
- * The top margin of the screen, for scrolling purposes. Ranges from 0 to
- * mRows-2.
- */
- private int mTopMargin;
-
- /**
- * The bottom margin of the screen, for scrolling purposes. Ranges from
- * mTopMargin + 2 to mRows. (Defines the first row after the scrolling
- * region.
- */
- private int mBottomMargin;
-
- /**
- * True if the next character to be emitted will be automatically wrapped to
- * the next line. Used to disambiguate the case where the cursor is
- * positioned on column mColumns-1.
- */
- private boolean mAboutToAutoWrap;
-
- /**
- * Used for debugging, counts how many chars have been processed.
- */
- private int mProcessedCharCount;
-
- /**
- * Foreground color, 0..7, mask with 8 for bold
- */
- private int mForeColor;
-
- /**
- * Background color, 0..7, mask with 8 for underline
- */
- private int mBackColor;
-
- private boolean mInverseColors;
-
- private boolean mbKeypadApplicationMode;
-
- private boolean mAlternateCharSet;
-
- /**
- * Used for moving selection up along with the scrolling text
- */
- private int mScrollCounter = 0;
-
- /**
- * Construct a terminal emulator that uses the supplied screen
- *
- * @param screen the screen to render characters into.
- * @param columns the number of columns to emulate
- * @param rows the number of rows to emulate
- * @param termOut the output file descriptor that talks to the pseudo-tty.
- */
- public TerminalEmulator(Screen screen, int columns, int rows,
- FileOutputStream termOut) {
- mScreen = screen;
- mRows = rows;
- mColumns = columns;
- mTabStop = new boolean[mColumns];
- mTermOut = termOut;
- reset();
- }
-
- public void updateSize(int columns, int rows) {
- if (mRows == rows && mColumns == columns) {
- return;
- }
- if (columns <= 0) {
- throw new IllegalArgumentException("rows:" + columns);
- }
-
- if (rows <= 0) {
- throw new IllegalArgumentException("rows:" + rows);
- }
-
- String transcriptText = mScreen.getTranscriptText();
-
- mScreen.resize(columns, rows, mForeColor, mBackColor);
-
- if (mRows != rows) {
- mRows = rows;
- mTopMargin = 0;
- mBottomMargin = mRows;
- }
- if (mColumns != columns) {
- int oldColumns = mColumns;
- mColumns = columns;
- boolean[] oldTabStop = mTabStop;
- mTabStop = new boolean[mColumns];
- int toTransfer = Math.min(oldColumns, columns);
- System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer);
- while (mCursorCol >= columns) {
- mCursorCol -= columns;
- mCursorRow = Math.min(mBottomMargin-1, mCursorRow + 1);
- }
- }
- mCursorRow = 0;
- mCursorCol = 0;
- mAboutToAutoWrap = false;
-
- int end = transcriptText.length()-1;
- while ((end >= 0) && transcriptText.charAt(end) == '\n') {
- end--;
- }
- for(int i = 0; i <= end; i++) {
- byte c = (byte) transcriptText.charAt(i);
- if (c == '\n') {
- setCursorCol(0);
- doLinefeed();
- } else {
- emit(c);
- }
- }
- }
-
- /**
- * Get the cursor's current row.
- *
- * @return the cursor's current row.
- */
- public final int getCursorRow() {
- return mCursorRow;
- }
-
- /**
- * Get the cursor's current column.
- *
- * @return the cursor's current column.
- */
- public final int getCursorCol() {
- return mCursorCol;
- }
-
- public final boolean getKeypadApplicationMode() {
- return mbKeypadApplicationMode;
- }
-
- private void setDefaultTabStops() {
- for (int i = 0; i < mColumns; i++) {
- mTabStop[i] = (i & 7) == 0 && i != 0;
- }
- }
-
- /**
- * Accept bytes (typically from the pseudo-teletype) and process them.
- *
- * @param buffer a byte array containing the bytes to be processed
- * @param base the first index of the array to process
- * @param length the number of bytes in the array to process
- */
- public void append(byte[] buffer, int base, int length) {
- for (int i = 0; i < length; i++) {
- byte b = buffer[base + i];
- try {
- if (Term.LOG_CHARACTERS_FLAG) {
- char printableB = (char) b;
- if (b < 32 || b > 126) {
- printableB = ' ';
- }
- Log.w(Term.LOG_TAG, "'" + Character.toString(printableB)
- + "' (" + Integer.toString(b) + ")");
- }
- process(b);
- mProcessedCharCount++;
- } catch (Exception e) {
- Log.e(Term.LOG_TAG, "Exception while processing character "
- + Integer.toString(mProcessedCharCount) + " code "
- + Integer.toString(b), e);
- }
- }
- }
-
- private void process(byte b) {
- switch (b) {
- case 0: // NUL
- // Do nothing
- break;
-
- case 7: // BEL
- // Do nothing
- break;
-
- case 8: // BS
- setCursorCol(Math.max(0, mCursorCol - 1));
- break;
-
- case 9: // HT
- // Move to next tab stop, but not past edge of screen
- setCursorCol(nextTabStop(mCursorCol));
- break;
-
- case 13:
- setCursorCol(0);
- break;
-
- case 10: // CR
- case 11: // VT
- case 12: // LF
- doLinefeed();
- break;
-
- case 14: // SO:
- setAltCharSet(true);
- break;
-
- case 15: // SI:
- setAltCharSet(false);
- break;
-
-
- case 24: // CAN
- case 26: // SUB
- if (mEscapeState != ESC_NONE) {
- mEscapeState = ESC_NONE;
- emit((byte) 127);
- }
- break;
-
- case 27: // ESC
- // Always starts an escape sequence
- startEscapeSequence(ESC);
- break;
-
- case (byte) 0x9b: // CSI
- startEscapeSequence(ESC_LEFT_SQUARE_BRACKET);
- break;
-
- default:
- mContinueSequence = false;
- switch (mEscapeState) {
- case ESC_NONE:
- if (b >= 32) {
- emit(b);
- }
- break;
-
- case ESC:
- doEsc(b);
- break;
-
- case ESC_POUND:
- doEscPound(b);
- break;
-
- case ESC_SELECT_LEFT_PAREN:
- doEscSelectLeftParen(b);
- break;
-
- case ESC_SELECT_RIGHT_PAREN:
- doEscSelectRightParen(b);
- break;
-
- case ESC_LEFT_SQUARE_BRACKET:
- doEscLeftSquareBracket(b);
- break;
-
- case ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK:
- doEscLSBQuest(b);
- break;
-
- default:
- unknownSequence(b);
- break;
- }
- if (!mContinueSequence) {
- mEscapeState = ESC_NONE;
- }
- break;
- }
- }
-
- private void setAltCharSet(boolean alternateCharSet) {
- mAlternateCharSet = alternateCharSet;
- }
-
- private int nextTabStop(int cursorCol) {
- for (int i = cursorCol; i < mColumns; i++) {
- if (mTabStop[i]) {
- return i;
- }
- }
- return mColumns - 1;
- }
-
- private void doEscLSBQuest(byte b) {
- int mask = getDecFlagsMask(getArg0(0));
- switch (b) {
- case 'h': // Esc [ ? Pn h - DECSET
- mDecFlags |= mask;
- break;
-
- case 'l': // Esc [ ? Pn l - DECRST
- mDecFlags &= ~mask;
- break;
-
- case 'r': // Esc [ ? Pn r - restore
- mDecFlags = (mDecFlags & ~mask) | (mSavedDecFlags & mask);
- break;
-
- case 's': // Esc [ ? Pn s - save
- mSavedDecFlags = (mSavedDecFlags & ~mask) | (mDecFlags & mask);
- break;
-
- default:
- parseArg(b);
- break;
- }
-
- // 132 column mode
- if ((mask & K_132_COLUMN_MODE_MASK) != 0) {
- // We don't actually set 132 cols, but we do want the
- // side effect of clearing the screen and homing the cursor.
- blockClear(0, 0, mColumns, mRows);
- setCursorRowCol(0, 0);
- }
-
- // origin mode
- if ((mask & K_ORIGIN_MODE_MASK) != 0) {
- // Home the cursor.
- setCursorPosition(0, 0);
- }
- }
-
- private int getDecFlagsMask(int argument) {
- if (argument >= 1 && argument <= 9) {
- return (1 << argument);
- }
-
- return 0;
- }
-
- private void startEscapeSequence(int escapeState) {
- mEscapeState = escapeState;
- mArgIndex = 0;
- for (int j = 0; j < MAX_ESCAPE_PARAMETERS; j++) {
- mArgs[j] = -1;
- }
- }
-
- private void doLinefeed() {
- int newCursorRow = mCursorRow + 1;
- if (newCursorRow >= mBottomMargin) {
- scroll();
- newCursorRow = mBottomMargin - 1;
- }
- setCursorRow(newCursorRow);
- }
-
- private void continueSequence() {
- mContinueSequence = true;
- }
-
- private void continueSequence(int state) {
- mEscapeState = state;
- mContinueSequence = true;
- }
-
- private void doEscSelectLeftParen(byte b) {
- doSelectCharSet(true, b);
- }
-
- private void doEscSelectRightParen(byte b) {
- doSelectCharSet(false, b);
- }
-
- private void doSelectCharSet(boolean isG0CharSet, byte b) {
- switch (b) {
- case 'A': // United Kingdom character set
- break;
- case 'B': // ASCII set
- break;
- case '0': // Special Graphics
- break;
- case '1': // Alternate character set
- break;
- case '2':
- break;
- default:
- unknownSequence(b);
- }
- }
-
- private void doEscPound(byte b) {
- switch (b) {
- case '8': // Esc # 8 - DECALN alignment test
- mScreen.blockSet(0, 0, mColumns, mRows, 'E',
- getForeColor(), getBackColor());
- break;
-
- default:
- unknownSequence(b);
- break;
- }
- }
-
- private void doEsc(byte b) {
- switch (b) {
- case '#':
- continueSequence(ESC_POUND);
- break;
-
- case '(':
- continueSequence(ESC_SELECT_LEFT_PAREN);
- break;
-
- case ')':
- continueSequence(ESC_SELECT_RIGHT_PAREN);
- break;
-
- case '7': // DECSC save cursor
- mSavedCursorRow = mCursorRow;
- mSavedCursorCol = mCursorCol;
- break;
-
- case '8': // DECRC restore cursor
- setCursorRowCol(mSavedCursorRow, mSavedCursorCol);
- break;
-
- case 'D': // INDEX
- doLinefeed();
- break;
-
- case 'E': // NEL
- setCursorCol(0);
- doLinefeed();
- break;
-
- case 'F': // Cursor to lower-left corner of screen
- setCursorRowCol(0, mBottomMargin - 1);
- break;
-
- case 'H': // Tab set
- mTabStop[mCursorCol] = true;
- break;
-
- case 'M': // Reverse index
- if (mCursorRow <= mTopMargin) {
- mScreen.blockCopy(0, mTopMargin, mColumns, mBottomMargin
- - (mTopMargin + 1), 0, mTopMargin + 1);
- blockClear(0, mTopMargin, mColumns);
- } else {
- mCursorRow--;
- }
-
- break;
-
- case 'N': // SS2
- unimplementedSequence(b);
- break;
-
- case '0': // SS3
- unimplementedSequence(b);
- break;
-
- case 'P': // Device control string
- unimplementedSequence(b);
- break;
-
- case 'Z': // return terminal ID
- sendDeviceAttributes();
- break;
-
- case '[':
- continueSequence(ESC_LEFT_SQUARE_BRACKET);
- break;
-
- case '=': // DECKPAM
- mbKeypadApplicationMode = true;
- break;
-
- case '>' : // DECKPNM
- mbKeypadApplicationMode = false;
- break;
-
- default:
- unknownSequence(b);
- break;
- }
- }
-
- private void doEscLeftSquareBracket(byte b) {
- switch (b) {
- case '@': // ESC [ Pn @ - ICH Insert Characters
- {
- int charsAfterCursor = mColumns - mCursorCol;
- int charsToInsert = Math.min(getArg0(1), charsAfterCursor);
- int charsToMove = charsAfterCursor - charsToInsert;
- mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1,
- mCursorCol + charsToInsert, mCursorRow);
- blockClear(mCursorCol, mCursorRow, charsToInsert);
- }
- break;
-
- case 'A': // ESC [ Pn A - Cursor Up
- setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1)));
- break;
-
- case 'B': // ESC [ Pn B - Cursor Down
- setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1)));
- break;
-
- case 'C': // ESC [ Pn C - Cursor Right
- setCursorCol(Math.min(mColumns - 1, mCursorCol + getArg0(1)));
- break;
-
- case 'D': // ESC [ Pn D - Cursor Left
- setCursorCol(Math.max(0, mCursorCol - getArg0(1)));
- break;
-
- case 'G': // ESC [ Pn G - Cursor Horizontal Absolute
- setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1);
- break;
-
- case 'H': // ESC [ Pn ; H - Cursor Position
- setHorizontalVerticalPosition();
- break;
-
- case 'J': // ESC [ Pn J - Erase in Display
- switch (getArg0(0)) {
- case 0: // Clear below
- blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
- blockClear(0, mCursorRow + 1, mColumns,
- mBottomMargin - (mCursorRow + 1));
- break;
-
- case 1: // Erase from the start of the screen to the cursor.
- blockClear(0, mTopMargin, mColumns, mCursorRow - mTopMargin);
- blockClear(0, mCursorRow, mCursorCol + 1);
- break;
-
- case 2: // Clear all
- blockClear(0, mTopMargin, mColumns, mBottomMargin - mTopMargin);
- break;
-
- default:
- unknownSequence(b);
- break;
- }
- break;
-
- case 'K': // ESC [ Pn K - Erase in Line
- switch (getArg0(0)) {
- case 0: // Clear to right
- blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
- break;
-
- case 1: // Erase start of line to cursor (including cursor)
- blockClear(0, mCursorRow, mCursorCol + 1);
- break;
-
- case 2: // Clear whole line
- blockClear(0, mCursorRow, mColumns);
- break;
-
- default:
- unknownSequence(b);
- break;
- }
- break;
-
- case 'L': // Insert Lines
- {
- int linesAfterCursor = mBottomMargin - mCursorRow;
- int linesToInsert = Math.min(getArg0(1), linesAfterCursor);
- int linesToMove = linesAfterCursor - linesToInsert;
- mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0,
- mCursorRow + linesToInsert);
- blockClear(0, mCursorRow, mColumns, linesToInsert);
- }
- break;
-
- case 'M': // Delete Lines
- {
- int linesAfterCursor = mBottomMargin - mCursorRow;
- int linesToDelete = Math.min(getArg0(1), linesAfterCursor);
- int linesToMove = linesAfterCursor - linesToDelete;
- mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns,
- linesToMove, 0, mCursorRow);
- blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete);
- }
- break;
-
- case 'P': // Delete Characters
- {
- int charsAfterCursor = mColumns - mCursorCol;
- int charsToDelete = Math.min(getArg0(1), charsAfterCursor);
- int charsToMove = charsAfterCursor - charsToDelete;
- mScreen.blockCopy(mCursorCol + charsToDelete, mCursorRow,
- charsToMove, 1, mCursorCol, mCursorRow);
- blockClear(mCursorCol + charsToMove, mCursorRow, charsToDelete);
- }
- break;
-
- case 'T': // Mouse tracking
- unimplementedSequence(b);
- break;
-
- case '?': // Esc [ ? -- start of a private mode set
- continueSequence(ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK);
- break;
-
- case 'c': // Send device attributes
- sendDeviceAttributes();
- break;
-
- case 'd': // ESC [ Pn d - Vert Position Absolute
- setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1);
- break;
-
- case 'f': // Horizontal and Vertical Position
- setHorizontalVerticalPosition();
- break;
-
- case 'g': // Clear tab stop
- switch (getArg0(0)) {
- case 0:
- mTabStop[mCursorCol] = false;
- break;
-
- case 3:
- for (int i = 0; i < mColumns; i++) {
- mTabStop[i] = false;
- }
- break;
-
- default:
- // Specified to have no effect.
- break;
- }
- break;
-
- case 'h': // Set Mode
- doSetMode(true);
- break;
-
- case 'l': // Reset Mode
- doSetMode(false);
- break;
-
- case 'm': // Esc [ Pn m - character attributes.
- selectGraphicRendition();
- break;
-
- case 'r': // Esc [ Pn ; Pn r - set top and bottom margins
- {
- // The top margin defaults to 1, the bottom margin
- // (unusually for arguments) defaults to mRows.
- //
- // The escape sequence numbers top 1..23, but we
- // number top 0..22.
- // The escape sequence numbers bottom 2..24, and
- // so do we (because we use a zero based numbering
- // scheme, but we store the first line below the
- // bottom-most scrolling line.
- // As a result, we adjust the top line by -1, but
- // we leave the bottom line alone.
- //
- // Also require that top + 2 <= bottom
-
- int top = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
- int bottom = Math.max(top + 2, Math.min(getArg1(mRows), mRows));
- mTopMargin = top;
- mBottomMargin = bottom;
-
- // The cursor is placed in the home position
- setCursorRowCol(mTopMargin, 0);
- }
- break;
-
- default:
- parseArg(b);
- break;
- }
- }
-
- private void selectGraphicRendition() {
- for (int i = 0; i <= mArgIndex; i++) {
- int code = mArgs[i];
- if ( code < 0) {
- if (mArgIndex > 0) {
- continue;
- } else {
- code = 0;
- }
- }
-
- // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
-
- if (code == 0) { // reset
- mInverseColors = false;
- mForeColor = 7;
- mBackColor = 0;
- } else if (code == 1) { // bold
- mForeColor |= 0x8;
- } else if (code == 4) { // underscore
- mBackColor |= 0x8;
- } else if (code == 7) { // inverse
- mInverseColors = true;
- } else if (code == 22) { // Normal color or intensity, neither bright, bold nor faint
- mForeColor &= 0x7;
- } else if (code == 24) { // underline: none
- mBackColor &= 0x7;
- } else if (code == 27) { // image: positive
- mInverseColors = false;
- } else if (code >= 30 && code <= 37) { // foreground color
- mForeColor = (mForeColor & 0x8) | (code - 30);
- } else if (code == 39) { // set default text color
- mForeColor = 7;
- mBackColor = mBackColor & 0x7;
- } else if (code >= 40 && code <= 47) { // background color
- mBackColor = (mBackColor & 0x8) | (code - 40);
- } else if (code == 49) { // set default background color
- mBackColor = mBackColor & 0x8; // color 0, but preserve underscore.
- } else {
- if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
- Log.w(Term.LOG_TAG, String.format("SGR unknown code %d", code));
- }
- }
- }
- }
-
- private void blockClear(int sx, int sy, int w) {
- blockClear(sx, sy, w, 1);
- }
-
- private void blockClear(int sx, int sy, int w, int h) {
- mScreen.blockSet(sx, sy, w, h, ' ', getForeColor(), getBackColor());
- }
-
- private int getForeColor() {
- return mInverseColors ?
- ((mBackColor & 0x7) | (mForeColor & 0x8)) : mForeColor;
- }
-
- private int getBackColor() {
- return mInverseColors ?
- ((mForeColor & 0x7) | (mBackColor & 0x8)) : mBackColor;
- }
-
- private void doSetMode(boolean newValue) {
- int modeBit = getArg0(0);
- switch (modeBit) {
- case 4:
- mInsertMode = newValue;
- break;
-
- case 20:
- mAutomaticNewlineMode = newValue;
- break;
-
- default:
- unknownParameter(modeBit);
- break;
- }
- }
-
- private void setHorizontalVerticalPosition() {
-
- // Parameters are Row ; Column
-
- setCursorPosition(getArg1(1) - 1, getArg0(1) - 1);
- }
-
- private void setCursorPosition(int x, int y) {
- int effectiveTopMargin = 0;
- int effectiveBottomMargin = mRows;
- if ((mDecFlags & K_ORIGIN_MODE_MASK) != 0) {
- effectiveTopMargin = mTopMargin;
- effectiveBottomMargin = mBottomMargin;
- }
- int newRow =
- Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y,
- effectiveBottomMargin - 1));
- int newCol = Math.max(0, Math.min(x, mColumns - 1));
- setCursorRowCol(newRow, newCol);
- }
-
- private void sendDeviceAttributes() {
- // This identifies us as a DEC vt100 with advanced
- // video options. This is what the xterm terminal
- // emulator sends.
- byte[] attributes =
- {
- /* VT100 */
- (byte) 27, (byte) '[', (byte) '?', (byte) '1',
- (byte) ';', (byte) '2', (byte) 'c'
-
- /* VT220
- (byte) 27, (byte) '[', (byte) '?', (byte) '6',
- (byte) '0', (byte) ';',
- (byte) '1', (byte) ';',
- (byte) '2', (byte) ';',
- (byte) '6', (byte) ';',
- (byte) '8', (byte) ';',
- (byte) '9', (byte) ';',
- (byte) '1', (byte) '5', (byte) ';',
- (byte) 'c'
- */
- };
-
- write(attributes);
- }
-
- /**
- * Send data to the shell process
- * @param data
- */
- private void write(byte[] data) {
- try {
- mTermOut.write(data);
- mTermOut.flush();
- } catch (IOException e) {
- // Ignore exception
- // We don't really care if the receiver isn't listening.
- // We just make a best effort to answer the query.
- }
- }
-
- private void scroll() {
- //System.out.println("Scroll(): mTopMargin " + mTopMargin + " mBottomMargin " + mBottomMargin);
- mScrollCounter ++;
- mScreen.scroll(mTopMargin, mBottomMargin,
- getForeColor(), getBackColor());
- }
-
- /**
- * Process the next ASCII character of a parameter.
- *
- * @param b The next ASCII character of the paramater sequence.
- */
- private void parseArg(byte b) {
- if (b >= '0' && b <= '9') {
- if (mArgIndex < mArgs.length) {
- int oldValue = mArgs[mArgIndex];
- int thisDigit = b - '0';
- int value;
- if (oldValue >= 0) {
- value = oldValue * 10 + thisDigit;
- } else {
- value = thisDigit;
- }
- mArgs[mArgIndex] = value;
- }
- continueSequence();
- } else if (b == ';') {
- if (mArgIndex < mArgs.length) {
- mArgIndex++;
- }
- continueSequence();
- } else {
- unknownSequence(b);
- }
- }
-
- private int getArg0(int defaultValue) {
- return getArg(0, defaultValue);
- }
-
- private int getArg1(int defaultValue) {
- return getArg(1, defaultValue);
- }
-
- private int getArg(int index, int defaultValue) {
- int result = mArgs[index];
- if (result < 0) {
- result = defaultValue;
- }
- return result;
- }
-
- private void unimplementedSequence(byte b) {
- if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
- logError("unimplemented", b);
- }
- finishSequence();
- }
-
- private void unknownSequence(byte b) {
- if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
- logError("unknown", b);
- }
- finishSequence();
- }
-
- private void unknownParameter(int parameter) {
- if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
- StringBuilder buf = new StringBuilder();
- buf.append("Unknown parameter");
- buf.append(parameter);
- logError(buf.toString());
- }
- }
-
- private void logError(String errorType, byte b) {
- if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
- StringBuilder buf = new StringBuilder();
- buf.append(errorType);
- buf.append(" sequence ");
- buf.append(" EscapeState: ");
- buf.append(mEscapeState);
- buf.append(" char: '");
- buf.append((char) b);
- buf.append("' (");
- buf.append(b);
- buf.append(")");
- boolean firstArg = true;
- for (int i = 0; i <= mArgIndex; i++) {
- int value = mArgs[i];
- if (value >= 0) {
- if (firstArg) {
- firstArg = false;
- buf.append("args = ");
- }
- buf.append(String.format("%d; ", value));
- }
- }
- logError(buf.toString());
- }
- }
-
- private void logError(String error) {
- if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
- Log.e(Term.LOG_TAG, error);
- }
- finishSequence();
- }
-
- private void finishSequence() {
- mEscapeState = ESC_NONE;
- }
-
- private boolean autoWrapEnabled() {
- // Always enable auto wrap, because it's useful on a small screen
- return true;
- // return (mDecFlags & K_WRAPAROUND_MODE_MASK) != 0;
- }
-
- /**
- * Send an ASCII character to the screen.
- *
- * @param b the ASCII character to display.
- */
- private void emit(byte b) {
- boolean autoWrap = autoWrapEnabled();
-
- if (autoWrap) {
- if (mCursorCol == mColumns - 1 && mAboutToAutoWrap) {
- mScreen.setLineWrap(mCursorRow);
- mCursorCol = 0;
- if (mCursorRow + 1 < mBottomMargin) {
- mCursorRow++;
- } else {
- scroll();
- }
- }
- }
-
- if (mInsertMode) { // Move character to right one space
- int destCol = mCursorCol + 1;
- if (destCol < mColumns) {
- mScreen.blockCopy(mCursorCol, mCursorRow, mColumns - destCol,
- 1, destCol, mCursorRow);
- }
- }
-
- mScreen.set(mCursorCol, mCursorRow, b, getForeColor(), getBackColor());
-
- if (autoWrap) {
- mAboutToAutoWrap = (mCursorCol == mColumns - 1);
- }
-
- mCursorCol = Math.min(mCursorCol + 1, mColumns - 1);
- }
-
- private void setCursorRow(int row) {
- mCursorRow = row;
- mAboutToAutoWrap = false;
- }
-
- private void setCursorCol(int col) {
- mCursorCol = col;
- mAboutToAutoWrap = false;
- }
-
- private void setCursorRowCol(int row, int col) {
- mCursorRow = Math.min(row, mRows-1);
- mCursorCol = Math.min(col, mColumns-1);
- mAboutToAutoWrap = false;
- }
-
- public int getScrollCounter() {
- return mScrollCounter;
- }
-
- public void clearScrollCounter() {
- mScrollCounter = 0;
- }
-
- /**
- * Reset the terminal emulator to its initial state.
- */
- public void reset() {
- mCursorRow = 0;
- mCursorCol = 0;
- mArgIndex = 0;
- mContinueSequence = false;
- mEscapeState = ESC_NONE;
- mSavedCursorRow = 0;
- mSavedCursorCol = 0;
- mDecFlags = 0;
- mSavedDecFlags = 0;
- mInsertMode = false;
- mAutomaticNewlineMode = false;
- mTopMargin = 0;
- mBottomMargin = mRows;
- mAboutToAutoWrap = false;
- mForeColor = 7;
- mBackColor = 0;
- mInverseColors = false;
- mbKeypadApplicationMode = false;
- mAlternateCharSet = false;
- // mProcessedCharCount is preserved unchanged.
- setDefaultTabStops();
- blockClear(0, 0, mColumns, mRows);
- }
-
- public String getTranscriptText() {
- return mScreen.getTranscriptText();
- }
- public String getSelectedText(int x1, int y1, int x2, int y2) {
- return mScreen.getSelectedText(x1, y1, x2, y2);
- }
-}
-
-/**
- * Text renderer interface
- */
-
-interface TextRenderer {
- int getCharacterWidth();
- int getCharacterHeight();
- void drawTextRun(Canvas canvas, float x, float y,
- int lineOffset, char[] text,
- int index, int count, boolean cursor, int foreColor, int backColor);
-}
-
-abstract class BaseTextRenderer implements TextRenderer {
- protected int[] mForePaint = {
- 0xff000000, // Black
- 0xffff0000, // Red
- 0xff00ff00, // green
- 0xffffff00, // yellow
- 0xff0000ff, // blue
- 0xffff00ff, // magenta
- 0xff00ffff, // cyan
- 0xffffffff // white -- is overridden by constructor
- };
- protected int[] mBackPaint = {
- 0xff000000, // Black -- is overridden by constructor
- 0xffcc0000, // Red
- 0xff00cc00, // green
- 0xffcccc00, // yellow
- 0xff0000cc, // blue
- 0xffff00cc, // magenta
- 0xff00cccc, // cyan
- 0xffffffff // white
- };
- protected final static int mCursorPaint = 0xff808080;
-
- public BaseTextRenderer(int forePaintColor, int backPaintColor) {
- mForePaint[7] = forePaintColor;
- mBackPaint[0] = backPaintColor;
-
- }
-}
-
-class Bitmap4x8FontRenderer extends BaseTextRenderer {
- private final static int kCharacterWidth = 4;
- private final static int kCharacterHeight = 8;
- private Bitmap mFont;
- private int mCurrentForeColor;
- private int mCurrentBackColor;
- private float[] mColorMatrix;
- private Paint mPaint;
- private static final float BYTE_SCALE = 1.0f / 255.0f;
-
- public Bitmap4x8FontRenderer(Resources resources,
- int forePaintColor, int backPaintColor) {
- super(forePaintColor, backPaintColor);
- mFont = BitmapFactory.decodeResource(resources,
- R.drawable.atari_small);
- mPaint = new Paint();
- mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
- }
-
- public int getCharacterWidth() {
- return kCharacterWidth;
- }
-
- public int getCharacterHeight() {
- return kCharacterHeight;
- }
-
- public void drawTextRun(Canvas canvas, float x, float y,
- int lineOffset, char[] text, int index, int count,
- boolean cursor, int foreColor, int backColor) {
- setColorMatrix(mForePaint[foreColor & 7],
- cursor ? mCursorPaint : mBackPaint[backColor & 7]);
- int destX = (int) x + kCharacterWidth * lineOffset;
- int destY = (int) y;
- Rect srcRect = new Rect();
- Rect destRect = new Rect();
- destRect.top = (destY - kCharacterHeight);
- destRect.bottom = destY;
- for(int i = 0; i < count; i++) {
- char c = text[i + index];
- if ((cursor || (c != 32)) && (c < 128)) {
- int cellX = c & 31;
- int cellY = (c >> 5) & 3;
- int srcX = cellX * kCharacterWidth;
- int srcY = cellY * kCharacterHeight;
- srcRect.set(srcX, srcY,
- srcX + kCharacterWidth, srcY + kCharacterHeight);
- destRect.left = destX;
- destRect.right = destX + kCharacterWidth;
- canvas.drawBitmap(mFont, srcRect, destRect, mPaint);
- }
- destX += kCharacterWidth;
- }
- }
-
- private void setColorMatrix(int foreColor, int backColor) {
- if ((foreColor != mCurrentForeColor)
- || (backColor != mCurrentBackColor)
- || (mColorMatrix == null)) {
- mCurrentForeColor = foreColor;
- mCurrentBackColor = backColor;
- if (mColorMatrix == null) {
- mColorMatrix = new float[20];
- mColorMatrix[18] = 1.0f; // Just copy Alpha
- }
- for (int component = 0; component < 3; component++) {
- int rightShift = (2 - component) << 3;
- int fore = 0xff & (foreColor >> rightShift);
- int back = 0xff & (backColor >> rightShift);
- int delta = back - fore;
- mColorMatrix[component * 6] = delta * BYTE_SCALE;
- mColorMatrix[component * 5 + 4] = fore;
- }
- mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
- }
- }
-}
-
-class PaintRenderer extends BaseTextRenderer {
- public PaintRenderer(int fontSize, int forePaintColor, int backPaintColor) {
- super(forePaintColor, backPaintColor);
- mTextPaint = new Paint();
- mTextPaint.setTypeface(Typeface.MONOSPACE);
- mTextPaint.setAntiAlias(true);
- mTextPaint.setTextSize(fontSize);
-
- mCharHeight = (int) Math.ceil(mTextPaint.getFontSpacing());
- mCharAscent = (int) Math.ceil(mTextPaint.ascent());
- mCharDescent = mCharHeight + mCharAscent;
- mCharWidth = (int) mTextPaint.measureText(EXAMPLE_CHAR, 0, 1);
- }
-
- public void drawTextRun(Canvas canvas, float x, float y, int lineOffset,
- char[] text, int index, int count,
- boolean cursor, int foreColor, int backColor) {
- if (cursor) {
- mTextPaint.setColor(mCursorPaint);
- } else {
- mTextPaint.setColor(mBackPaint[backColor & 0x7]);
- }
- float left = x + lineOffset * mCharWidth;
- canvas.drawRect(left, y + mCharAscent,
- left + count * mCharWidth, y + mCharDescent,
- mTextPaint);
- boolean bold = ( foreColor & 0x8 ) != 0;
- boolean underline = (backColor & 0x8) != 0;
- if (bold) {
- mTextPaint.setFakeBoldText(true);
- }
- if (underline) {
- mTextPaint.setUnderlineText(true);
- }
- mTextPaint.setColor(mForePaint[foreColor & 0x7]);
- canvas.drawText(text, index, count, left, y, mTextPaint);
- if (bold) {
- mTextPaint.setFakeBoldText(false);
- }
- if (underline) {
- mTextPaint.setUnderlineText(false);
- }
- }
-
- public int getCharacterHeight() {
- return mCharHeight;
- }
-
- public int getCharacterWidth() {
- return mCharWidth;
- }
-
-
- private Paint mTextPaint;
- private int mCharWidth;
- private int mCharHeight;
- private int mCharAscent;
- private int mCharDescent;
- private static final char[] EXAMPLE_CHAR = {'X'};
- }
-
-/**
- * A multi-thread-safe produce-consumer byte array.
- * Only allows one producer and one consumer.
- */
-
-class ByteQueue {
- public ByteQueue(int size) {
- mBuffer = new byte[size];
- }
-
- public int getBytesAvailable() {
- synchronized(this) {
- return mStoredBytes;
- }
- }
-
- public int read(byte[] buffer, int offset, int length)
- throws InterruptedException {
- if (length + offset > buffer.length) {
- throw
- new IllegalArgumentException("length + offset > buffer.length");
- }
- if (length < 0) {
- throw
- new IllegalArgumentException("length < 0");
-
- }
- if (length == 0) {
- return 0;
- }
- synchronized(this) {
- while (mStoredBytes == 0) {
- wait();
- }
- int totalRead = 0;
- int bufferLength = mBuffer.length;
- boolean wasFull = bufferLength == mStoredBytes;
- while (length > 0 && mStoredBytes > 0) {
- int oneRun = Math.min(bufferLength - mHead, mStoredBytes);
- int bytesToCopy = Math.min(length, oneRun);
- System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy);
- mHead += bytesToCopy;
- if (mHead >= bufferLength) {
- mHead = 0;
- }
- mStoredBytes -= bytesToCopy;
- length -= bytesToCopy;
- offset += bytesToCopy;
- totalRead += bytesToCopy;
- }
- if (wasFull) {
- notify();
- }
- return totalRead;
- }
- }
-
- public void write(byte[] buffer, int offset, int length)
- throws InterruptedException {
- if (length + offset > buffer.length) {
- throw
- new IllegalArgumentException("length + offset > buffer.length");
- }
- if (length < 0) {
- throw
- new IllegalArgumentException("length < 0");
-
- }
- if (length == 0) {
- return;
- }
- synchronized(this) {
- int bufferLength = mBuffer.length;
- boolean wasEmpty = mStoredBytes == 0;
- while (length > 0) {
- while(bufferLength == mStoredBytes) {
- wait();
- }
- int tail = mHead + mStoredBytes;
- int oneRun;
- if (tail >= bufferLength) {
- tail = tail - bufferLength;
- oneRun = mHead - tail;
- } else {
- oneRun = bufferLength - tail;
- }
- int bytesToCopy = Math.min(oneRun, length);
- System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy);
- offset += bytesToCopy;
- mStoredBytes += bytesToCopy;
- length -= bytesToCopy;
- }
- if (wasEmpty) {
- notify();
- }
- }
- }
-
- private byte[] mBuffer;
- private int mHead;
- private int mStoredBytes;
-}
-/**
- * A view on a transcript and a terminal emulator. Displays the text of the
- * transcript and the current cursor position of the terminal emulator.
- */
-class EmulatorView extends View implements GestureDetector.OnGestureListener {
-
- private final String TAG = "EmulatorView";
- private final boolean LOG_KEY_EVENTS = Term.DEBUG && false;
-
- private Term mTerm;
-
- /**
- * We defer some initialization until we have been layed out in the view
- * hierarchy. The boolean tracks when we know what our size is.
- */
- private boolean mKnownSize;
-
- private int mVisibleWidth;
- private int mVisibleHeight;
- private Rect mVisibleRect = new Rect();
-
- /**
- * Our transcript. Contains the screen and the transcript.
- */
- private TranscriptScreen mTranscriptScreen;
-
- /**
- * Number of rows in the transcript.
- */
- private static final int TRANSCRIPT_ROWS = 10000;
-
- /**
- * Total width of each character, in pixels
- */
- private int mCharacterWidth;
-
- /**
- * Total height of each character, in pixels
- */
- private int mCharacterHeight;
-
- /**
- * Used to render text
- */
- private TextRenderer mTextRenderer;
-
- /**
- * Text size. Zero means 4 x 8 font.
- */
- private int mTextSize;
-
- private int mCursorStyle;
- private int mCursorBlink;
-
- /**
- * Foreground color.
- */
- private int mForeground;
-
- /**
- * Background color.
- */
- private int mBackground;
-
- /**
- * Used to paint the cursor
- */
- private Paint mCursorPaint;
-
- private Paint mBackgroundPaint;
-
- private boolean mUseCookedIme;
-
- /**
- * Our terminal emulator. We use this to get the current cursor position.
- */
- private TerminalEmulator mEmulator;
-
- /**
- * The number of rows of text to display.
- */
- private int mRows;
-
- /**
- * The number of columns of text to display.
- */
- private int mColumns;
-
- /**
- * The number of columns that are visible on the display.
- */
-
- private int mVisibleColumns;
-
- /**
- * The top row of text to display. Ranges from -activeTranscriptRows to 0
- */
- private int mTopRow;
-
- private int mLeftColumn;
-
- private FileDescriptor mTermFd;
- /**
- * Used to receive data from the remote process.
- */
- private FileInputStream mTermIn;
-
- private FileOutputStream mTermOut;
-
- private ByteQueue mByteQueue;
-
- /**
- * Used to temporarily hold data received from the remote process. Allocated
- * once and used permanently to minimize heap thrashing.
- */
- private byte[] mReceiveBuffer;
-
- /**
- * Our private message id, which we use to receive new input from the
- * remote process.
- */
- private static final int UPDATE = 1;
-
- private static final int SCREEN_CHECK_PERIOD = 1000;
- private static final int CURSOR_BLINK_PERIOD = 1000;
-
- private boolean mCursorVisible = true;
-
- private boolean mIsSelectingText = false;
-
-
- private float mScaledDensity;
- private static final int SELECT_TEXT_OFFSET_Y = -40;
- private int mSelXAnchor = -1;
- private int mSelYAnchor = -1;
- private int mSelX1 = -1;
- private int mSelY1 = -1;
- private int mSelX2 = -1;
- private int mSelY2 = -1;
-
- /**
- * Used to poll if the view has changed size. Wish there was a better way to do this.
- */
- private Runnable mCheckSize = new Runnable() {
-
- public void run() {
- updateSize(false);
- mHandler.postDelayed(this, SCREEN_CHECK_PERIOD);
- }
- };
-
- private Runnable mBlinkCursor = new Runnable() {
- public void run() {
- if (mCursorBlink != 0) {
- mCursorVisible = ! mCursorVisible;
- mHandler.postDelayed(this, CURSOR_BLINK_PERIOD);
- } else {
- mCursorVisible = true;
- }
- // Perhaps just invalidate the character with the cursor.
- invalidate();
- }
- };
-
- /**
- * Thread that polls for input from the remote process
- */
-
- private Thread mPollingThread;
-
- private GestureDetector mGestureDetector;
- private float mScrollRemainder;
- private TermKeyListener mKeyListener;
-
- private String mImeBuffer = "";
-
- /**
- * Our message handler class. Implements a periodic callback.
- */
- private final Handler mHandler = new Handler() {
- /**
- * Handle the callback message. Call our enclosing class's update
- * method.
- *
- * @param msg The callback message.
- */
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == UPDATE) {
- update();
- }
- }
- };
-
- public EmulatorView(Context context) {
- super(context);
- commonConstructor();
- }
-
- public void setScaledDensity(float scaledDensity) {
- mScaledDensity = scaledDensity;
- }
-
- public void onResume() {
- updateSize(false);
- mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD);
- if (mCursorBlink != 0) {
- mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD);
- }
- }
-
- public void onPause() {
- mHandler.removeCallbacks(mCheckSize);
- if (mCursorBlink != 0) {
- mHandler.removeCallbacks(mBlinkCursor);
- }
- }
-
- public void register(Term term, TermKeyListener listener) {
- mTerm = term;
- mKeyListener = listener;
- }
-
- public void setColors(int foreground, int background) {
- mForeground = foreground;
- mBackground = background;
- updateText();
- }
-
- public String getTranscriptText() {
- return mEmulator.getTranscriptText();
- }
-
- public void resetTerminal() {
- mEmulator.reset();
- invalidate();
- }
-
- @Override
- public boolean onCheckIsTextEditor() {
- return true;
- }
-
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- outAttrs.inputType = mUseCookedIme ?
- EditorInfo.TYPE_CLASS_TEXT :
- EditorInfo.TYPE_NULL;
- return new InputConnection() {
- private boolean mInBatchEdit;
- /**
- * Used to handle composing text requests
- */
- private int mCursor;
- private int mComposingTextStart;
- private int mComposingTextEnd;
- private int mSelectedTextStart;
- private int mSelectedTextEnd;
-
- private void sendChar(int c) {
- try {
- mapAndSend(c);
- } catch (IOException ex) {
-
- }
- }
-
- private void sendText(CharSequence text) {
- int n = text.length();
- try {
- for(int i = 0; i < n; i++) {
- char c = text.charAt(i);
- mapAndSend(c);
- }
- mTermOut.flush();
- } catch (IOException e) {
- Log.e(TAG, "error writing ", e);
- }
- }
-
- private void mapAndSend(int c) throws IOException {
- int result = mKeyListener.mapControlChar(c);
- if (result < TermKeyListener.KEYCODE_OFFSET) {
- mTermOut.write(result);
- } else {
- mKeyListener.handleKeyCode(result - TermKeyListener.KEYCODE_OFFSET, mTermOut, getKeypadApplicationMode());
- }
- }
-
- public boolean beginBatchEdit() {
- if (Term.LOG_IME) {
- Log.w(TAG, "beginBatchEdit");
- }
- setImeBuffer("");
- mCursor = 0;
- mComposingTextStart = 0;
- mComposingTextEnd = 0;
- mInBatchEdit = true;
- return true;
- }
-
- public boolean clearMetaKeyStates(int arg0) {
- if (Term.LOG_IME) {
- Log.w(TAG, "clearMetaKeyStates " + arg0);
- }
- return false;
- }
-
- public boolean commitCompletion(CompletionInfo arg0) {
- if (Term.LOG_IME) {
- Log.w(TAG, "commitCompletion " + arg0);
- }
- return false;
- }
-
- public boolean endBatchEdit() {
- if (Term.LOG_IME) {
- Log.w(TAG, "endBatchEdit");
- }
- mInBatchEdit = false;
- return true;
- }
-
- public boolean finishComposingText() {
- if (Term.LOG_IME) {
- Log.w(TAG, "finishComposingText");
- }
- sendText(mImeBuffer);
- setImeBuffer("");
- mComposingTextStart = 0;
- mComposingTextEnd = 0;
- mCursor = 0;
- return true;
- }
-
- public int getCursorCapsMode(int arg0) {
- if (Term.LOG_IME) {
- Log.w(TAG, "getCursorCapsMode(" + arg0 + ")");
- }
- return 0;
- }
-
- public ExtractedText getExtractedText(ExtractedTextRequest arg0,
- int arg1) {
- if (Term.LOG_IME) {
- Log.w(TAG, "getExtractedText" + arg0 + "," + arg1);
- }
- return null;
- }
-
- public CharSequence getTextAfterCursor(int n, int flags) {
- if (Term.LOG_IME) {
- Log.w(TAG, "getTextAfterCursor(" + n + "," + flags + ")");
- }
- int len = Math.min(n, mImeBuffer.length() - mCursor);
- if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) {
- return "";
- }
- return mImeBuffer.substring(mCursor, mCursor + len);
- }
-
- public CharSequence getTextBeforeCursor(int n, int flags) {
- if (Term.LOG_IME) {
- Log.w(TAG, "getTextBeforeCursor(" + n + "," + flags + ")");
- }
- int len = Math.min(n, mCursor);
- if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) {
- return "";
- }
- return mImeBuffer.substring(mCursor-len, mCursor);
- }
-
- public boolean performContextMenuAction(int arg0) {
- if (Term.LOG_IME) {
- Log.w(TAG, "performContextMenuAction" + arg0);
- }
- return true;
- }
-
- public boolean performPrivateCommand(String arg0, Bundle arg1) {
- if (Term.LOG_IME) {
- Log.w(TAG, "performPrivateCommand" + arg0 + "," + arg1);
- }
- return true;
- }
-
- public boolean reportFullscreenMode(boolean arg0) {
- if (Term.LOG_IME) {
- Log.w(TAG, "reportFullscreenMode" + arg0);
- }
- return true;
- }
-
- public boolean commitCorrection (CorrectionInfo correctionInfo) {
- if (Term.LOG_IME) {
- Log.w(TAG, "commitCorrection");
- }
- return true;
- }
-
- public boolean commitText(CharSequence text, int newCursorPosition) {
- if (Term.LOG_IME) {
- Log.w(TAG, "commitText(\"" + text + "\", " + newCursorPosition + ")");
- }
- clearComposingText();
- sendText(text);
- setImeBuffer("");
- mCursor = 0;
- return true;
- }
-
- private void clearComposingText() {
- setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) +
- mImeBuffer.substring(mComposingTextEnd));
- if (mCursor < mComposingTextStart) {
- // do nothing
- } else if (mCursor < mComposingTextEnd) {
- mCursor = mComposingTextStart;
- } else {
- mCursor -= mComposingTextEnd - mComposingTextStart;
- }
- mComposingTextEnd = mComposingTextStart = 0;
- }
-
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
- if (Term.LOG_IME) {
- Log.w(TAG, "deleteSurroundingText(" + leftLength +
- "," + rightLength + ")");
- }
- if (leftLength > 0) {
- for (int i = 0; i < leftLength; i++) {
- sendKeyEvent(
- new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
- }
- } else if ((leftLength == 0) && (rightLength == 0)) {
- // Delete key held down / repeating
- sendKeyEvent(
- new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
- }
- // TODO: handle forward deletes.
- return true;
- }
-
- public boolean performEditorAction(int actionCode) {
- if (Term.LOG_IME) {
- Log.w(TAG, "performEditorAction(" + actionCode + ")");
- }
- if (actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) {
- // The "return" key has been pressed on the IME.
- sendText("\n");
- }
- return true;
- }
-
- public boolean sendKeyEvent(KeyEvent event) {
- if (Term.LOG_IME) {
- Log.w(TAG, "sendKeyEvent(" + event + ")");
- }
- // Some keys are sent here rather than to commitText.
- // In particular, del and the digit keys are sent here.
- // (And I have reports that the HTC Magic also sends Return here.)
- // As a bit of defensive programming, handle every key.
- dispatchKeyEvent(event);
- return true;
- }
-
- public boolean setComposingText(CharSequence text, int newCursorPosition) {
- if (Term.LOG_IME) {
- Log.w(TAG, "setComposingText(\"" + text + "\", " + newCursorPosition + ")");
- }
- setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) +
- text + mImeBuffer.substring(mComposingTextEnd));
- mComposingTextEnd = mComposingTextStart + text.length();
- mCursor = newCursorPosition > 0 ? mComposingTextEnd + newCursorPosition - 1
- : mComposingTextStart - newCursorPosition;
- return true;
- }
-
- public boolean setSelection(int start, int end) {
- if (Term.LOG_IME) {
- Log.w(TAG, "setSelection" + start + "," + end);
- }
- int length = mImeBuffer.length();
- if (start == end && start > 0 && start < length) {
- mSelectedTextStart = mSelectedTextEnd = 0;
- mCursor = start;
- } else if (start < end && start > 0 && end < length) {
- mSelectedTextStart = start;
- mSelectedTextEnd = end;
- mCursor = start;
- }
- return true;
- }
-
- public boolean setComposingRegion(int start, int end) {
- if (Term.LOG_IME) {
- Log.w(TAG, "setComposingRegion " + start + "," + end);
- }
- if (start < end && start > 0 && end < mImeBuffer.length()) {
- clearComposingText();
- mComposingTextStart = start;
- mComposingTextEnd = end;
- }
- return true;
- }
-
- public CharSequence getSelectedText(int flags) {
- if (Term.LOG_IME) {
- Log.w(TAG, "getSelectedText " + flags);
- }
- return mImeBuffer.substring(mSelectedTextStart, mSelectedTextEnd+1);
- }
-
- };
- }
-
- private void setImeBuffer(String buffer) {
- if (!buffer.equals(mImeBuffer)) {
- invalidate();
- }
- mImeBuffer = buffer;
- }
-
- public boolean getKeypadApplicationMode() {
- return mEmulator.getKeypadApplicationMode();
- }
-
- public EmulatorView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public EmulatorView(Context context, AttributeSet attrs,
- int defStyle) {
- super(context, attrs, defStyle);
- // TypedArray a =
- // context.obtainStyledAttributes(android.R.styleable.View);
- // initializeScrollbars(a);
- // a.recycle();
- commonConstructor();
- }
-
- private void commonConstructor() {
- mTextRenderer = null;
- mCursorPaint = new Paint();
- mCursorPaint.setARGB(255,128,128,128);
- mBackgroundPaint = new Paint();
- mTopRow = 0;
- mLeftColumn = 0;
- mGestureDetector = new GestureDetector(this);
- // mGestureDetector.setIsLongpressEnabled(false);
- setVerticalScrollBarEnabled(true);
- }
-
- @Override
- protected int computeVerticalScrollRange() {
- return mTranscriptScreen.getActiveRows();
- }
-
- @Override
- protected int computeVerticalScrollExtent() {
- return mRows;
- }
-
- @Override
- protected int computeVerticalScrollOffset() {
- return mTranscriptScreen.getActiveRows() + mTopRow - mRows;
- }
-
- /**
- * Call this to initialize the view.
- *
- * @param termFd the file descriptor
- * @param termOut the output stream for the pseudo-teletype
- */
- public void initialize(FileDescriptor termFd, FileOutputStream termOut) {
- mTermOut = termOut;
- mTermFd = termFd;
- mTextSize = 10;
- mForeground = Term.WHITE;
- mBackground = Term.BLACK;
- updateText();
- mTermIn = new FileInputStream(mTermFd);
- mReceiveBuffer = new byte[4 * 1024];
- mByteQueue = new ByteQueue(4 * 1024);
- }
-
- /**
- * Accept a sequence of bytes (typically from the pseudo-tty) and process
- * them.
- *
- * @param buffer a byte array containing bytes to be processed
- * @param base the index of the first byte in the buffer to process
- * @param length the number of bytes to process
- */
- public void append(byte[] buffer, int base, int length) {
- mEmulator.append(buffer, base, length);
- if ( mIsSelectingText ) {
- int rowShift = mEmulator.getScrollCounter();
- mSelY1 -= rowShift;
- mSelY2 -= rowShift;
- mSelYAnchor -= rowShift;
- }
- mEmulator.clearScrollCounter();
- ensureCursorVisible();
- invalidate();
- }
-
- /**
- * Page the terminal view (scroll it up or down by delta screenfulls.)
- *
- * @param delta the number of screens to scroll. Positive means scroll down,
- * negative means scroll up.
- */
- public void page(int delta) {
- mTopRow =
- Math.min(0, Math.max(-(mTranscriptScreen
- .getActiveTranscriptRows()), mTopRow + mRows * delta));
- invalidate();
- }
-
- /**
- * Page the terminal view horizontally.
- *
- * @param deltaColumns the number of columns to scroll. Positive scrolls to
- * the right.
- */
- public void pageHorizontal(int deltaColumns) {
- mLeftColumn =
- Math.max(0, Math.min(mLeftColumn + deltaColumns, mColumns
- - mVisibleColumns));
- invalidate();
- }
-
- /**
- * Sets the text size, which in turn sets the number of rows and columns
- *
- * @param fontSize the new font size, in pixels.
- */
- public void setTextSize(int fontSize) {
- mTextSize = fontSize;
- updateText();
- }
-
- public void setCursorStyle(int style, int blink) {
- mCursorStyle = style;
- if (blink != 0 && mCursorBlink == 0) {
- mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD);
- } else if (blink == 0 && mCursorBlink != 0) {
- mHandler.removeCallbacks(mBlinkCursor);
- }
- mCursorBlink = blink;
- }
-
- public void setUseCookedIME(boolean useCookedIME) {
- mUseCookedIme = useCookedIME;
- }
-
- // Begin GestureDetector.OnGestureListener methods
-
- public boolean onSingleTapUp(MotionEvent e) {
- return true;
- }
-
- public void onLongPress(MotionEvent e) {
- showContextMenu();
- }
-
- public boolean onScroll(MotionEvent e1, MotionEvent e2,
- float distanceX, float distanceY) {
- distanceY += mScrollRemainder;
- int deltaRows = (int) (distanceY / mCharacterHeight);
- mScrollRemainder = distanceY - deltaRows * mCharacterHeight;
- mTopRow =
- Math.min(0, Math.max(-(mTranscriptScreen
- .getActiveTranscriptRows()), mTopRow + deltaRows));
- invalidate();
-
- return true;
- }
-
- public void onSingleTapConfirmed(MotionEvent e) {
- }
-
- public boolean onJumpTapDown(MotionEvent e1, MotionEvent e2) {
- // Scroll to bottom
- mTopRow = 0;
- invalidate();
- return true;
- }
-
- public boolean onJumpTapUp(MotionEvent e1, MotionEvent e2) {
- // Scroll to top
- mTopRow = -mTranscriptScreen.getActiveTranscriptRows();
- invalidate();
- return true;
- }
-
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
- float velocityY) {
- // TODO: add animation man's (non animated) fling
- mScrollRemainder = 0.0f;
- onScroll(e1, e2, 2 * velocityX, -2 * velocityY);
- return true;
- }
-
- public void onShowPress(MotionEvent e) {
- }
-
- public boolean onDown(MotionEvent e) {
- mScrollRemainder = 0.0f;
- return true;
- }
-
- // End GestureDetector.OnGestureListener methods
-
- @Override public boolean onTouchEvent(MotionEvent ev) {
- if (mIsSelectingText) {
- return onTouchEventWhileSelectingText(ev);
- } else {
- return mGestureDetector.onTouchEvent(ev);
- }
- }
-
- private boolean onTouchEventWhileSelectingText(MotionEvent ev) {
- int action = ev.getAction();
- int cx = (int)(ev.getX() / mCharacterWidth);
- int cy = Math.max(0,
- (int)((ev.getY() + SELECT_TEXT_OFFSET_Y * mScaledDensity)
- / mCharacterHeight) + mTopRow);
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mSelXAnchor = cx;
- mSelYAnchor = cy;
- mSelX1 = cx;
- mSelY1 = cy;
- mSelX2 = mSelX1;
- mSelY2 = mSelY1;
- break;
- case MotionEvent.ACTION_MOVE:
- case MotionEvent.ACTION_UP:
- int minx = Math.min(mSelXAnchor, cx);
- int maxx = Math.max(mSelXAnchor, cx);
- int miny = Math.min(mSelYAnchor, cy);
- int maxy = Math.max(mSelYAnchor, cy);
- mSelX1 = minx;
- mSelY1 = miny;
- mSelX2 = maxx;
- mSelY2 = maxy;
- if (action == MotionEvent.ACTION_UP) {
- ClipboardManager clip = (ClipboardManager)
- getContext().getApplicationContext()
- .getSystemService(Context.CLIPBOARD_SERVICE);
- clip.setText(getSelectedText().trim());
- toggleSelectingText();
- }
- invalidate();
- break;
- default:
- toggleSelectingText();
- invalidate();
- break;
- }
- return true;
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (LOG_KEY_EVENTS) {
- Log.w(TAG, "onKeyDown " + keyCode);
- }
- if (handleControlKey(keyCode, true)) {
- return true;
- } else if (handleFnKey(keyCode, true)) {
- return true;
- } else if (isSystemKey(keyCode, event)) {
- // Don't intercept the system keys
- return super.onKeyDown(keyCode, event);
- }
-
- // Translate the keyCode into an ASCII character.
-
- try {
- mKeyListener.keyDown(keyCode, event, mTermOut,
- getKeypadApplicationMode());
- } catch (IOException e) {
- // Ignore I/O exceptions
- }
- return true;
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (LOG_KEY_EVENTS) {
- Log.w(TAG, "onKeyUp " + keyCode);
- }
- if (handleControlKey(keyCode, false)) {
- return true;
- } else if (handleFnKey(keyCode, false)) {
- return true;
- } else if (isSystemKey(keyCode, event)) {
- // Don't intercept the system keys
- return super.onKeyUp(keyCode, event);
- }
-
- mKeyListener.keyUp(keyCode);
- return true;
- }
-
-
- private boolean handleControlKey(int keyCode, boolean down) {
- if (keyCode == mTerm.getControlKeyCode()) {
- if (LOG_KEY_EVENTS) {
- Log.w(TAG, "handleControlKey " + keyCode);
- }
- mKeyListener.handleControlKey(down);
- return true;
- }
- return false;
- }
-
- private boolean handleFnKey(int keyCode, boolean down) {
- if (keyCode == mTerm.getFnKeyCode()) {
- if (LOG_KEY_EVENTS) {
- Log.w(TAG, "handleFnKey " + keyCode);
- }
- mKeyListener.handleFnKey(down);
- return true;
- }
- return false;
- }
-
- private boolean isSystemKey(int keyCode, KeyEvent event) {
- return event.isSystem();
- }
-
- private void updateText() {
- if (mTextSize > 0) {
- mTextRenderer = new PaintRenderer(mTextSize, mForeground,
- mBackground);
- }
- else {
- mTextRenderer = new Bitmap4x8FontRenderer(getResources(),
- mForeground, mBackground);
- }
- mBackgroundPaint.setColor(mBackground);
- mCharacterWidth = mTextRenderer.getCharacterWidth();
- mCharacterHeight = mTextRenderer.getCharacterHeight();
-
- updateSize(true);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- boolean oldKnownSize = mKnownSize;
- if (!mKnownSize) {
- mKnownSize = true;
- }
- updateSize(false);
- if (!oldKnownSize) {
- // Set up a thread to read input from the
- // pseudo-teletype:
-
- mPollingThread = new Thread(new Runnable() {
-
- public void run() {
- try {
- while(true) {
- int read = mTermIn.read(mBuffer);
- mByteQueue.write(mBuffer, 0, read);
- mHandler.sendMessage(
- mHandler.obtainMessage(UPDATE));
- }
- } catch (IOException e) {
- } catch (InterruptedException e) {
- }
- }
- private byte[] mBuffer = new byte[4096];
- });
- mPollingThread.setName("Input reader");
- mPollingThread.start();
- }
- }
-
- private void updateSize(int w, int h) {
- mColumns = Math.max(1, w / mCharacterWidth);
- mRows = Math.max(1, h / mCharacterHeight);
- mVisibleColumns = mVisibleWidth / mCharacterWidth;
-
- // Inform the attached pty of our new size:
- Exec.setPtyWindowSize(mTermFd, mRows, mColumns, w, h);
-
-
- if (mTranscriptScreen != null) {
- mEmulator.updateSize(mColumns, mRows);
- } else {
- mTranscriptScreen =
- new TranscriptScreen(mColumns, TRANSCRIPT_ROWS, mRows, 0, 7);
- mEmulator =
- new TerminalEmulator(mTranscriptScreen, mColumns, mRows,
- mTermOut);
- }
-
- // Reset our paging:
- mTopRow = 0;
- mLeftColumn = 0;
-
- invalidate();
- }
-
- void updateSize(boolean force) {
- if (mKnownSize) {
- getWindowVisibleDisplayFrame(mVisibleRect);
- int w = mVisibleRect.width();
- int h = mVisibleRect.height();
- // Log.w("Term", "(" + w + ", " + h + ")");
- if (force || w != mVisibleWidth || h != mVisibleHeight) {
- mVisibleWidth = w;
- mVisibleHeight = h;
- updateSize(mVisibleWidth, mVisibleHeight);
- }
- }
- }
-
- /**
- * Look for new input from the ptty, send it to the terminal emulator.
- */
- private void update() {
- int bytesAvailable = mByteQueue.getBytesAvailable();
- int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length);
- try {
- int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead);
- append(mReceiveBuffer, 0, bytesRead);
- } catch (InterruptedException e) {
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- updateSize(false);
- int w = getWidth();
- int h = getHeight();
- canvas.drawRect(0, 0, w, h, mBackgroundPaint);
- float x = -mLeftColumn * mCharacterWidth;
- float y = mCharacterHeight;
- int endLine = mTopRow + mRows;
- int cx = mEmulator.getCursorCol();
- int cy = mEmulator.getCursorRow();
- for (int i = mTopRow; i < endLine; i++) {
- int cursorX = -1;
- if (i == cy && mCursorVisible) {
- cursorX = cx;
- }
- int selx1 = -1;
- int selx2 = -1;
- if ( i >= mSelY1 && i <= mSelY2 ) {
- if ( i == mSelY1 ) {
- selx1 = mSelX1;
- }
- if ( i == mSelY2 ) {
- selx2 = mSelX2;
- } else {
- selx2 = mColumns;
- }
- }
- mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2, mImeBuffer);
- y += mCharacterHeight;
- }
- }
-
- private void ensureCursorVisible() {
- mTopRow = 0;
- if (mVisibleColumns > 0) {
- int cx = mEmulator.getCursorCol();
- int visibleCursorX = mEmulator.getCursorCol() - mLeftColumn;
- if (visibleCursorX < 0) {
- mLeftColumn = cx;
- } else if (visibleCursorX >= mVisibleColumns) {
- mLeftColumn = (cx - mVisibleColumns) + 1;
- }
- }
- }
-
- public void toggleSelectingText() {
- mIsSelectingText = ! mIsSelectingText;
- setVerticalScrollBarEnabled( ! mIsSelectingText );
- if ( ! mIsSelectingText ) {
- mSelX1 = -1;
- mSelY1 = -1;
- mSelX2 = -1;
- mSelY2 = -1;
- }
- }
-
- public boolean getSelectingText() {
- return mIsSelectingText;
- }
-
- public String getSelectedText() {
- return mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2);
- }
-}
-
-
-/**
- * An ASCII key listener. Supports control characters and escape. Keeps track of
- * the current state of the alt, shift, and control keys.
- */
-class TermKeyListener {
- /**
- * Android key codes that are defined in the Android 2.3 API.
- * We want to recognize these codes, because they will be sent to our
- * app when we run on Android 2.3 systems.
- * But we don't want to accidentally use 2.3-specific APIs.
- * So we compile against the Android 1.6 APIs, and have a copy of the codes here.
- */
-
- /** Key code constant: Unknown key code. */
- public static final int KEYCODE_UNKNOWN = 0;
- /** Key code constant: Soft Left key.
- * Usually situated below the display on phones and used as a multi-function
- * feature key for selecting a software defined function shown on the bottom left
- * of the display. */
- public static final int KEYCODE_SOFT_LEFT = 1;
- /** Key code constant: Soft Right key.
- * Usually situated below the display on phones and used as a multi-function
- * feature key for selecting a software defined function shown on the bottom right
- * of the display. */
- public static final int KEYCODE_SOFT_RIGHT = 2;
- /** Key code constant: Home key.
- * This key is handled by the framework and is never delivered to applications. */
- public static final int KEYCODE_HOME = 3;
- /** Key code constant: Back key. */
- public static final int KEYCODE_BACK = 4;
- /** Key code constant: Call key. */
- public static final int KEYCODE_CALL = 5;
- /** Key code constant: End Call key. */
- public static final int KEYCODE_ENDCALL = 6;
- /** Key code constant: '0' key. */
- public static final int KEYCODE_0 = 7;
- /** Key code constant: '1' key. */
- public static final int KEYCODE_1 = 8;
- /** Key code constant: '2' key. */
- public static final int KEYCODE_2 = 9;
- /** Key code constant: '3' key. */
- public static final int KEYCODE_3 = 10;
- /** Key code constant: '4' key. */
- public static final int KEYCODE_4 = 11;
- /** Key code constant: '5' key. */
- public static final int KEYCODE_5 = 12;
- /** Key code constant: '6' key. */
- public static final int KEYCODE_6 = 13;
- /** Key code constant: '7' key. */
- public static final int KEYCODE_7 = 14;
- /** Key code constant: '8' key. */
- public static final int KEYCODE_8 = 15;
- /** Key code constant: '9' key. */
- public static final int KEYCODE_9 = 16;
- /** Key code constant: '*' key. */
- public static final int KEYCODE_STAR = 17;
- /** Key code constant: '#' key. */
- public static final int KEYCODE_POUND = 18;
- /** Key code constant: Directional Pad Up key.
- * May also be synthesized from trackball motions. */
- public static final int KEYCODE_DPAD_UP = 19;
- /** Key code constant: Directional Pad Down key.
- * May also be synthesized from trackball motions. */
- public static final int KEYCODE_DPAD_DOWN = 20;
- /** Key code constant: Directional Pad Left key.
- * May also be synthesized from trackball motions. */
- public static final int KEYCODE_DPAD_LEFT = 21;
- /** Key code constant: Directional Pad Right key.
- * May also be synthesized from trackball motions. */
- public static final int KEYCODE_DPAD_RIGHT = 22;
- /** Key code constant: Directional Pad Center key.
- * May also be synthesized from trackball motions. */
- public static final int KEYCODE_DPAD_CENTER = 23;
- /** Key code constant: Volume Up key.
- * Adjusts the speaker volume up. */
- public static final int KEYCODE_VOLUME_UP = 24;
- /** Key code constant: Volume Down key.
- * Adjusts the speaker volume down. */
- public static final int KEYCODE_VOLUME_DOWN = 25;
- /** Key code constant: Power key. */
- public static final int KEYCODE_POWER = 26;
- /** Key code constant: Camera key.
- * Used to launch a camera application or take pictures. */
- public static final int KEYCODE_CAMERA = 27;
- /** Key code constant: Clear key. */
- public static final int KEYCODE_CLEAR = 28;
- /** Key code constant: 'A' key. */
- public static final int KEYCODE_A = 29;
- /** Key code constant: 'B' key. */
- public static final int KEYCODE_B = 30;
- /** Key code constant: 'C' key. */
- public static final int KEYCODE_C = 31;
- /** Key code constant: 'D' key. */
- public static final int KEYCODE_D = 32;
- /** Key code constant: 'E' key. */
- public static final int KEYCODE_E = 33;
- /** Key code constant: 'F' key. */
- public static final int KEYCODE_F = 34;
- /** Key code constant: 'G' key. */
- public static final int KEYCODE_G = 35;
- /** Key code constant: 'H' key. */
- public static final int KEYCODE_H = 36;
- /** Key code constant: 'I' key. */
- public static final int KEYCODE_I = 37;
- /** Key code constant: 'J' key. */
- public static final int KEYCODE_J = 38;
- /** Key code constant: 'K' key. */
- public static final int KEYCODE_K = 39;
- /** Key code constant: 'L' key. */
- public static final int KEYCODE_L = 40;
- /** Key code constant: 'M' key. */
- public static final int KEYCODE_M = 41;
- /** Key code constant: 'N' key. */
- public static final int KEYCODE_N = 42;
- /** Key code constant: 'O' key. */
- public static final int KEYCODE_O = 43;
- /** Key code constant: 'P' key. */
- public static final int KEYCODE_P = 44;
- /** Key code constant: 'Q' key. */
- public static final int KEYCODE_Q = 45;
- /** Key code constant: 'R' key. */
- public static final int KEYCODE_R = 46;
- /** Key code constant: 'S' key. */
- public static final int KEYCODE_S = 47;
- /** Key code constant: 'T' key. */
- public static final int KEYCODE_T = 48;
- /** Key code constant: 'U' key. */
- public static final int KEYCODE_U = 49;
- /** Key code constant: 'V' key. */
- public static final int KEYCODE_V = 50;
- /** Key code constant: 'W' key. */
- public static final int KEYCODE_W = 51;
- /** Key code constant: 'X' key. */
- public static final int KEYCODE_X = 52;
- /** Key code constant: 'Y' key. */
- public static final int KEYCODE_Y = 53;
- /** Key code constant: 'Z' key. */
- public static final int KEYCODE_Z = 54;
- /** Key code constant: ',' key. */
- public static final int KEYCODE_COMMA = 55;
- /** Key code constant: '.' key. */
- public static final int KEYCODE_PERIOD = 56;
- /** Key code constant: Left Alt modifier key. */
- public static final int KEYCODE_ALT_LEFT = 57;
- /** Key code constant: Right Alt modifier key. */
- public static final int KEYCODE_ALT_RIGHT = 58;
- /** Key code constant: Left Shift modifier key. */
- public static final int KEYCODE_SHIFT_LEFT = 59;
- /** Key code constant: Right Shift modifier key. */
- public static final int KEYCODE_SHIFT_RIGHT = 60;
- /** Key code constant: Tab key. */
- public static final int KEYCODE_TAB = 61;
- /** Key code constant: Space key. */
- public static final int KEYCODE_SPACE = 62;
- /** Key code constant: Symbol modifier key.
- * Used to enter alternate symbols. */
- public static final int KEYCODE_SYM = 63;
- /** Key code constant: Explorer special function key.
- * Used to launch a browser application. */
- public static final int KEYCODE_EXPLORER = 64;
- /** Key code constant: Envelope special function key.
- * Used to launch a mail application. */
- public static final int KEYCODE_ENVELOPE = 65;
- /** Key code constant: Enter key. */
- public static final int KEYCODE_ENTER = 66;
- /** Key code constant: Backspace key.
- * Deletes characters before the insertion point, unlike {@link #KEYCODE_FORWARD_DEL}. */
- public static final int KEYCODE_DEL = 67;
- /** Key code constant: '`' (backtick) key. */
- public static final int KEYCODE_GRAVE = 68;
- /** Key code constant: '-'. */
- public static final int KEYCODE_MINUS = 69;
- /** Key code constant: '=' key. */
- public static final int KEYCODE_EQUALS = 70;
- /** Key code constant: '[' key. */
- public static final int KEYCODE_LEFT_BRACKET = 71;
- /** Key code constant: ']' key. */
- public static final int KEYCODE_RIGHT_BRACKET = 72;
- /** Key code constant: '\' key. */
- public static final int KEYCODE_BACKSLASH = 73;
- /** Key code constant: ';' key. */
- public static final int KEYCODE_SEMICOLON = 74;
- /** Key code constant: ''' (apostrophe) key. */
- public static final int KEYCODE_APOSTROPHE = 75;
- /** Key code constant: '/' key. */
- public static final int KEYCODE_SLASH = 76;
- /** Key code constant: '@' key. */
- public static final int KEYCODE_AT = 77;
- /** Key code constant: Number modifier key.
- * Used to enter numeric symbols.
- * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is
- * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */
- public static final int KEYCODE_NUM = 78;
- /** Key code constant: Headset Hook key.
- * Used to hang up calls and stop media. */
- public static final int KEYCODE_HEADSETHOOK = 79;
- /** Key code constant: Camera Focus key.
- * Used to focus the camera. */
- public static final int KEYCODE_FOCUS = 80; // *Camera* focus
- /** Key code constant: '+' key. */
- public static final int KEYCODE_PLUS = 81;
- /** Key code constant: Menu key. */
- public static final int KEYCODE_MENU = 82;
- /** Key code constant: Notification key. */
- public static final int KEYCODE_NOTIFICATION = 83;
- /** Key code constant: Search key. */
- public static final int KEYCODE_SEARCH = 84;
- /** Key code constant: Play/Pause media key. */
- public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85;
- /** Key code constant: Stop media key. */
- public static final int KEYCODE_MEDIA_STOP = 86;
- /** Key code constant: Play Next media key. */
- public static final int KEYCODE_MEDIA_NEXT = 87;
- /** Key code constant: Play Previous media key. */
- public static final int KEYCODE_MEDIA_PREVIOUS = 88;
- /** Key code constant: Rewind media key. */
- public static final int KEYCODE_MEDIA_REWIND = 89;
- /** Key code constant: Fast Forward media key. */
- public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
- /** Key code constant: Mute key.
- * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */
- public static final int KEYCODE_MUTE = 91;
- /** Key code constant: Page Up key. */
- public static final int KEYCODE_PAGE_UP = 92;
- /** Key code constant: Page Down key. */
- public static final int KEYCODE_PAGE_DOWN = 93;
- /** Key code constant: Picture Symbols modifier key.
- * Used to switch symbol sets (Emoji, Kao-moji). */
- public static final int KEYCODE_PICTSYMBOLS = 94; // switch symbol-sets (Emoji,Kao-moji)
- /** Key code constant: Switch Charset modifier key.
- * Used to switch character sets (Kanji, Katakana). */
- public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana)
- /** Key code constant: A Button key.
- * On a game controller, the A button should be either the button labeled A
- * or the first button on the upper row of controller buttons. */
- public static final int KEYCODE_BUTTON_A = 96;
- /** Key code constant: B Button key.
- * On a game controller, the B button should be either the button labeled B
- * or the second button on the upper row of controller buttons. */
- public static final int KEYCODE_BUTTON_B = 97;
- /** Key code constant: C Button key.
- * On a game controller, the C button should be either the button labeled C
- * or the third button on the upper row of controller buttons. */
- public static final int KEYCODE_BUTTON_C = 98;
- /** Key code constant: X Button key.
- * On a game controller, the X button should be either the button labeled X
- * or the first button on the lower row of controller buttons. */
- public static final int KEYCODE_BUTTON_X = 99;
- /** Key code constant: Y Button key.
- * On a game controller, the Y button should be either the button labeled Y
- * or the second button on the lower row of controller buttons. */
- public static final int KEYCODE_BUTTON_Y = 100;
- /** Key code constant: Z Button key.
- * On a game controller, the Z button should be either the button labeled Z
- * or the third button on the lower row of controller buttons. */
- public static final int KEYCODE_BUTTON_Z = 101;
- /** Key code constant: L1 Button key.
- * On a game controller, the L1 button should be either the button labeled L1 (or L)
- * or the top left trigger button. */
- public static final int KEYCODE_BUTTON_L1 = 102;
- /** Key code constant: R1 Button key.
- * On a game controller, the R1 button should be either the button labeled R1 (or R)
- * or the top right trigger button. */
- public static final int KEYCODE_BUTTON_R1 = 103;
- /** Key code constant: L2 Button key.
- * On a game controller, the L2 button should be either the button labeled L2
- * or the bottom left trigger button. */
- public static final int KEYCODE_BUTTON_L2 = 104;
- /** Key code constant: R2 Button key.
- * On a game controller, the R2 button should be either the button labeled R2
- * or the bottom right trigger button. */
- public static final int KEYCODE_BUTTON_R2 = 105;
- /** Key code constant: Left Thumb Button key.
- * On a game controller, the left thumb button indicates that the left (or only)
- * joystick is pressed. */
- public static final int KEYCODE_BUTTON_THUMBL = 106;
- /** Key code constant: Right Thumb Button key.
- * On a game controller, the right thumb button indicates that the right
- * joystick is pressed. */
- public static final int KEYCODE_BUTTON_THUMBR = 107;
- /** Key code constant: Start Button key.
- * On a game controller, the button labeled Start. */
- public static final int KEYCODE_BUTTON_START = 108;
- /** Key code constant: Select Button key.
- * On a game controller, the button labeled Select. */
- public static final int KEYCODE_BUTTON_SELECT = 109;
- /** Key code constant: Mode Button key.
- * On a game controller, the button labeled Mode. */
- public static final int KEYCODE_BUTTON_MODE = 110;
- /** Key code constant: Escape key. */
- public static final int KEYCODE_ESCAPE = 111;
- /** Key code constant: Forward Delete key.
- * Deletes characters ahead of the insertion point, unlike {@link #KEYCODE_DEL}. */
- public static final int KEYCODE_FORWARD_DEL = 112;
- /** Key code constant: Left Control modifier key. */
- public static final int KEYCODE_CTRL_LEFT = 113;
- /** Key code constant: Right Control modifier key. */
- public static final int KEYCODE_CTRL_RIGHT = 114;
- /** Key code constant: Caps Lock modifier key. */
- public static final int KEYCODE_CAPS_LOCK = 115;
- /** Key code constant: Scroll Lock key. */
- public static final int KEYCODE_SCROLL_LOCK = 116;
- /** Key code constant: Left Meta modifier key. */
- public static final int KEYCODE_META_LEFT = 117;
- /** Key code constant: Right Meta modifier key. */
- public static final int KEYCODE_META_RIGHT = 118;
- /** Key code constant: Function modifier key. */
- public static final int KEYCODE_FUNCTION = 119;
- /** Key code constant: System Request / Print Screen key. */
- public static final int KEYCODE_SYSRQ = 120;
- /** Key code constant: Break / Pause key. */
- public static final int KEYCODE_BREAK = 121;
- /** Key code constant: Home Movement key.
- * Used for scrolling or moving the cursor around to the start of a line
- * or to the top of a list. */
- public static final int KEYCODE_MOVE_HOME = 122;
- /** Key code constant: End Movement key.
- * Used for scrolling or moving the cursor around to the end of a line
- * or to the bottom of a list. */
- public static final int KEYCODE_MOVE_END = 123;
- /** Key code constant: Insert key.
- * Toggles insert / overwrite edit mode. */
- public static final int KEYCODE_INSERT = 124;
- /** Key code constant: Forward key.
- * Navigates forward in the history stack. Complement of {@link #KEYCODE_BACK}. */
- public static final int KEYCODE_FORWARD = 125;
- /** Key code constant: Play media key. */
- public static final int KEYCODE_MEDIA_PLAY = 126;
- /** Key code constant: Pause media key. */
- public static final int KEYCODE_MEDIA_PAUSE = 127;
- /** Key code constant: Close media key.
- * May be used to close a CD tray, for example. */
- public static final int KEYCODE_MEDIA_CLOSE = 128;
- /** Key code constant: Eject media key.
- * May be used to eject a CD tray, for example. */
- public static final int KEYCODE_MEDIA_EJECT = 129;
- /** Key code constant: Record media key. */
- public static final int KEYCODE_MEDIA_RECORD = 130;
- /** Key code constant: F1 key. */
- public static final int KEYCODE_F1 = 131;
- /** Key code constant: F2 key. */
- public static final int KEYCODE_F2 = 132;
- /** Key code constant: F3 key. */
- public static final int KEYCODE_F3 = 133;
- /** Key code constant: F4 key. */
- public static final int KEYCODE_F4 = 134;
- /** Key code constant: F5 key. */
- public static final int KEYCODE_F5 = 135;
- /** Key code constant: F6 key. */
- public static final int KEYCODE_F6 = 136;
- /** Key code constant: F7 key. */
- public static final int KEYCODE_F7 = 137;
- /** Key code constant: F8 key. */
- public static final int KEYCODE_F8 = 138;
- /** Key code constant: F9 key. */
- public static final int KEYCODE_F9 = 139;
- /** Key code constant: F10 key. */
- public static final int KEYCODE_F10 = 140;
- /** Key code constant: F11 key. */
- public static final int KEYCODE_F11 = 141;
- /** Key code constant: F12 key. */
- public static final int KEYCODE_F12 = 142;
- /** Key code constant: Num Lock modifier key.
- * This is the Num Lock key; it is different from {@link #KEYCODE_NUM}.
- * This key generally modifies the behavior of other keys on the numeric keypad. */
- public static final int KEYCODE_NUM_LOCK = 143;
- /** Key code constant: Numeric keypad '0' key. */
- public static final int KEYCODE_NUMPAD_0 = 144;
- /** Key code constant: Numeric keypad '1' key. */
- public static final int KEYCODE_NUMPAD_1 = 145;
- /** Key code constant: Numeric keypad '2' key. */
- public static final int KEYCODE_NUMPAD_2 = 146;
- /** Key code constant: Numeric keypad '3' key. */
- public static final int KEYCODE_NUMPAD_3 = 147;
- /** Key code constant: Numeric keypad '4' key. */
- public static final int KEYCODE_NUMPAD_4 = 148;
- /** Key code constant: Numeric keypad '5' key. */
- public static final int KEYCODE_NUMPAD_5 = 149;
- /** Key code constant: Numeric keypad '6' key. */
- public static final int KEYCODE_NUMPAD_6 = 150;
- /** Key code constant: Numeric keypad '7' key. */
- public static final int KEYCODE_NUMPAD_7 = 151;
- /** Key code constant: Numeric keypad '8' key. */
- public static final int KEYCODE_NUMPAD_8 = 152;
- /** Key code constant: Numeric keypad '9' key. */
- public static final int KEYCODE_NUMPAD_9 = 153;
- /** Key code constant: Numeric keypad '/' key (for division). */
- public static final int KEYCODE_NUMPAD_DIVIDE = 154;
- /** Key code constant: Numeric keypad '*' key (for multiplication). */
- public static final int KEYCODE_NUMPAD_MULTIPLY = 155;
- /** Key code constant: Numeric keypad '-' key (for subtraction). */
- public static final int KEYCODE_NUMPAD_SUBTRACT = 156;
- /** Key code constant: Numeric keypad '+' key (for addition). */
- public static final int KEYCODE_NUMPAD_ADD = 157;
- /** Key code constant: Numeric keypad '.' key (for decimals or digit grouping). */
- public static final int KEYCODE_NUMPAD_DOT = 158;
- /** Key code constant: Numeric keypad ',' key (for decimals or digit grouping). */
- public static final int KEYCODE_NUMPAD_COMMA = 159;
- /** Key code constant: Numeric keypad Enter key. */
- public static final int KEYCODE_NUMPAD_ENTER = 160;
- /** Key code constant: Numeric keypad '=' key. */
- public static final int KEYCODE_NUMPAD_EQUALS = 161;
- /** Key code constant: Numeric keypad '(' key. */
- public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162;
- /** Key code constant: Numeric keypad ')' key. */
- public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163;
- /** Key code constant: Volume Mute key.
- * Mutes the speaker, unlike {@link #KEYCODE_MUTE}.
- * This key should normally be implemented as a toggle such that the first press
- * mutes the speaker and the second press restores the original volume. */
- public static final int KEYCODE_VOLUME_MUTE = 164;
- /** Key code constant: Info key.
- * Common on TV remotes to show additional information related to what is
- * currently being viewed. */
- public static final int KEYCODE_INFO = 165;
- /** Key code constant: Channel up key.
- * On TV remotes, increments the television channel. */
- public static final int KEYCODE_CHANNEL_UP = 166;
- /** Key code constant: Channel down key.
- * On TV remotes, decrements the television channel. */
- public static final int KEYCODE_CHANNEL_DOWN = 167;
- /** Key code constant: Zoom in key. */
- public static final int KEYCODE_ZOOM_IN = 168;
- /** Key code constant: Zoom out key. */
- public static final int KEYCODE_ZOOM_OUT = 169;
- /** Key code constant: TV key.
- * On TV remotes, switches to viewing live TV. */
- public static final int KEYCODE_TV = 170;
- /** Key code constant: Window key.
- * On TV remotes, toggles picture-in-picture mode or other windowing functions. */
- public static final int KEYCODE_WINDOW = 171;
- /** Key code constant: Guide key.
- * On TV remotes, shows a programming guide. */
- public static final int KEYCODE_GUIDE = 172;
- /** Key code constant: DVR key.
- * On some TV remotes, switches to a DVR mode for recorded shows. */
- public static final int KEYCODE_DVR = 173;
- /** Key code constant: Bookmark key.
- * On some TV remotes, bookmarks content or web pages. */
- public static final int KEYCODE_BOOKMARK = 174;
- /** Key code constant: Toggle captions key.
- * Switches the mode for closed-captioning text, for example during television shows. */
- public static final int KEYCODE_CAPTIONS = 175;
- /** Key code constant: Settings key.
- * Starts the system settings activity. */
- public static final int KEYCODE_SETTINGS = 176;
- /** Key code constant: TV power key.
- * On TV remotes, toggles the power on a television screen. */
- public static final int KEYCODE_TV_POWER = 177;
- /** Key code constant: TV input key.
- * On TV remotes, switches the input on a television screen. */
- public static final int KEYCODE_TV_INPUT = 178;
- /** Key code constant: Set-top-box power key.
- * On TV remotes, toggles the power on an external Set-top-box. */
- public static final int KEYCODE_STB_POWER = 179;
- /** Key code constant: Set-top-box input key.
- * On TV remotes, switches the input mode on an external Set-top-box. */
- public static final int KEYCODE_STB_INPUT = 180;
- /** Key code constant: A/V Receiver power key.
- * On TV remotes, toggles the power on an external A/V Receiver. */
- public static final int KEYCODE_AVR_POWER = 181;
- /** Key code constant: A/V Receiver input key.
- * On TV remotes, switches the input mode on an external A/V Receiver. */
- public static final int KEYCODE_AVR_INPUT = 182;
- /** Key code constant: Red "programmable" key.
- * On TV remotes, acts as a contextual/programmable key. */
- public static final int KEYCODE_PROG_RED = 183;
- /** Key code constant: Green "programmable" key.
- * On TV remotes, actsas a contextual/programmable key. */
- public static final int KEYCODE_PROG_GREEN = 184;
- /** Key code constant: Yellow "programmable" key.
- * On TV remotes, acts as a contextual/programmable key. */
- public static final int KEYCODE_PROG_YELLOW = 185;
- /** Key code constant: Blue "programmable" key.
- * On TV remotes, acts as a contextual/programmable key. */
- public static final int KEYCODE_PROG_BLUE = 186;
-
- private static final int LAST_KEYCODE = KEYCODE_PROG_BLUE;
-
- private String[] mKeyCodes = new String[256];
- private String[] mAppKeyCodes = new String[256];
-
- private void initKeyCodes() {
- mKeyCodes[KEYCODE_DPAD_CENTER] = "\015";
- mKeyCodes[KEYCODE_DPAD_UP] = "\033[A";
- mKeyCodes[KEYCODE_DPAD_DOWN] = "\033[B";
- mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033[C";
- mKeyCodes[KEYCODE_DPAD_LEFT] = "\033[D";
- mKeyCodes[KEYCODE_F1] = "\033[OP";
- mKeyCodes[KEYCODE_F2] = "\033[OQ";
- mKeyCodes[KEYCODE_F3] = "\033[OR";
- mKeyCodes[KEYCODE_F4] = "\033[OS";
- mKeyCodes[KEYCODE_F5] = "\033[15~";
- mKeyCodes[KEYCODE_F6] = "\033[17~";
- mKeyCodes[KEYCODE_F7] = "\033[18~";
- mKeyCodes[KEYCODE_F8] = "\033[19~";
- mKeyCodes[KEYCODE_F9] = "\033[20~";
- mKeyCodes[KEYCODE_F10] = "\033[21~";
- mKeyCodes[KEYCODE_F11] = "\033[23~";
- mKeyCodes[KEYCODE_F12] = "\033[24~";
- mKeyCodes[KEYCODE_SYSRQ] = "\033[32~"; // Sys Request / Print
- // Is this Scroll lock? mKeyCodes[Cancel] = "\033[33~";
- mKeyCodes[KEYCODE_BREAK] = "\033[34~"; // Pause/Break
-
- mKeyCodes[KEYCODE_TAB] = "\011";
- mKeyCodes[KEYCODE_ENTER] = "\015";
- mKeyCodes[KEYCODE_ESCAPE] = "\033";
-
- mKeyCodes[KEYCODE_INSERT] = "\033[2~";
- mKeyCodes[KEYCODE_FORWARD_DEL] = "\033[3~";
- mKeyCodes[KEYCODE_MOVE_HOME] = "\033[1~";
- mKeyCodes[KEYCODE_MOVE_END] = "\033[4~";
- mKeyCodes[KEYCODE_PAGE_UP] = "\033[5~";
- mKeyCodes[KEYCODE_PAGE_DOWN] = "\033[6~";
- mKeyCodes[KEYCODE_DEL]= "\177";
- mKeyCodes[KEYCODE_NUM_LOCK] = "\033OP";
- mKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "/";
- mKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "*";
- mKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "-";
- mKeyCodes[KEYCODE_NUMPAD_ADD] = "+";
- mKeyCodes[KEYCODE_NUMPAD_ENTER] = "\015";
- mKeyCodes[KEYCODE_NUMPAD_EQUALS] = "=";
- mKeyCodes[KEYCODE_NUMPAD_DOT] = ".";
- mKeyCodes[KEYCODE_NUMPAD_COMMA] = ",";
- mKeyCodes[KEYCODE_NUMPAD_0] = "0";
- mKeyCodes[KEYCODE_NUMPAD_1] = "1";
- mKeyCodes[KEYCODE_NUMPAD_2] = "2";
- mKeyCodes[KEYCODE_NUMPAD_3] = "3";
- mKeyCodes[KEYCODE_NUMPAD_4] = "4";
- mKeyCodes[KEYCODE_NUMPAD_5] = "5";
- mKeyCodes[KEYCODE_NUMPAD_6] = "6";
- mKeyCodes[KEYCODE_NUMPAD_7] = "7";
- mKeyCodes[KEYCODE_NUMPAD_8] = "8";
- mKeyCodes[KEYCODE_NUMPAD_9] = "9";
-
- mAppKeyCodes[KEYCODE_DPAD_UP] = "\033OA";
- mAppKeyCodes[KEYCODE_DPAD_DOWN] = "\033OB";
- mAppKeyCodes[KEYCODE_DPAD_RIGHT] = "\033OC";
- mAppKeyCodes[KEYCODE_DPAD_LEFT] = "\033OD";
- mAppKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "\033Oo";
- mAppKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "\033Oj";
- mAppKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "\033Om";
- mAppKeyCodes[KEYCODE_NUMPAD_ADD] = "\033Ok";
- mAppKeyCodes[KEYCODE_NUMPAD_ENTER] = "\033OM";
- mAppKeyCodes[KEYCODE_NUMPAD_EQUALS] = "\033OX";
- mAppKeyCodes[KEYCODE_NUMPAD_DOT] = "\033On";
- mAppKeyCodes[KEYCODE_NUMPAD_COMMA] = "\033Ol";
- mAppKeyCodes[KEYCODE_NUMPAD_0] = "\033Op";
- mAppKeyCodes[KEYCODE_NUMPAD_1] = "\033Oq";
- mAppKeyCodes[KEYCODE_NUMPAD_2] = "\033Or";
- mAppKeyCodes[KEYCODE_NUMPAD_3] = "\033Os";
- mAppKeyCodes[KEYCODE_NUMPAD_4] = "\033Ot";
- mAppKeyCodes[KEYCODE_NUMPAD_5] = "\033Ou";
- mAppKeyCodes[KEYCODE_NUMPAD_6] = "\033Ov";
- mAppKeyCodes[KEYCODE_NUMPAD_7] = "\033Ow";
- mAppKeyCodes[KEYCODE_NUMPAD_8] = "\033Ox";
- mAppKeyCodes[KEYCODE_NUMPAD_9] = "\033Oy";
- }
-
- /**
- * The state engine for a modifier key. Can be pressed, released, locked,
- * and so on.
- *
- */
- private class ModifierKey {
-
- private int mState;
-
- private static final int UNPRESSED = 0;
-
- private static final int PRESSED = 1;
-
- private static final int RELEASED = 2;
-
- private static final int USED = 3;
-
- private static final int LOCKED = 4;
-
- /**
- * Construct a modifier key. UNPRESSED by default.
- *
- */
- public ModifierKey() {
- mState = UNPRESSED;
- }
-
- public void onPress() {
- switch (mState) {
- case PRESSED:
- // This is a repeat before use
- break;
- case RELEASED:
- mState = LOCKED;
- break;
- case USED:
- // This is a repeat after use
- break;
- case LOCKED:
- mState = UNPRESSED;
- break;
- default:
- mState = PRESSED;
- break;
- }
- }
-
- public void onRelease() {
- switch (mState) {
- case USED:
- mState = UNPRESSED;
- break;
- case PRESSED:
- mState = RELEASED;
- break;
- default:
- // Leave state alone
- break;
- }
- }
-
- public void adjustAfterKeypress() {
- switch (mState) {
- case PRESSED:
- mState = USED;
- break;
- case RELEASED:
- mState = UNPRESSED;
- break;
- default:
- // Leave state alone
- break;
- }
- }
-
- public boolean isActive() {
- return mState != UNPRESSED;
- }
- }
-
- private ModifierKey mAltKey = new ModifierKey();
-
- private ModifierKey mCapKey = new ModifierKey();
-
- private ModifierKey mControlKey = new ModifierKey();
-
- private ModifierKey mFnKey = new ModifierKey();
-
- private boolean mCapsLock;
-
- static public final int KEYCODE_OFFSET = 1000;
-
- /**
- * Construct a term key listener.
- *
- */
- public TermKeyListener() {
- initKeyCodes();
- }
-
- public void handleControlKey(boolean down) {
- if (down) {
- mControlKey.onPress();
- } else {
- mControlKey.onRelease();
- }
- }
-
- public void handleFnKey(boolean down) {
- if (down) {
- mFnKey.onPress();
- } else {
- mFnKey.onRelease();
- }
- }
-
- public int mapControlChar(int ch) {
- int result = ch;
- if (mControlKey.isActive()) {
- // Search is the control key.
- if (result >= 'a' && result <= 'z') {
- result = (char) (result - 'a' + '\001');
- } else if (result >= 'A' && result <= 'Z') {
- result = (char) (result - 'A' + '\001');
- } else if (result == ' ' || result == '2') {
- result = 0;
- } else if (result == '[' || result == '3') {
- result = 27; // ^[ (Esc)
- } else if (result == '\\' || result == '4') {
- result = 28;
- } else if (result == ']' || result == '5') {
- result = 29;
- } else if (result == '^' || result == '6') {
- result = 30; // control-^
- } else if (result == '_' || result == '7') {
- result = 31;
- } else if (result == '8') {
- result = 127; // DEL
- } else if (result == '9') {
- result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F11;
- } else if (result == '0') {
- result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F12;
- }
- } else if (mFnKey.isActive()) {
- if (result == 'w' || result == 'W') {
- result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP;
- } else if (result == 'a' || result == 'A') {
- result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT;
- } else if (result == 's' || result == 'S') {
- result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN;
- } else if (result == 'd' || result == 'D') {
- result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT;
- } else if (result == 'p' || result == 'P') {
- result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_UP;
- } else if (result == 'n' || result == 'N') {
- result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_DOWN;
- } else if (result == 't' || result == 'T') {
- result = KEYCODE_OFFSET + KeyEvent.KEYCODE_TAB;
- } else if (result == 'l' || result == 'L') {
- result = '|';
- } else if (result == 'u' || result == 'U') {
- result = '_';
- } else if (result == 'e' || result == 'E') {
- result = 27; // ^[ (Esc)
- } else if (result == '.') {
- result = 28; // ^\
- } else if (result > '0' && result <= '9') {
- // F1-F9
- result = (char)(result + KEYCODE_OFFSET + TermKeyListener.KEYCODE_F1 - 1);
- } else if (result == '0') {
- result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F10;
- } else if (result == 'i' || result == 'I') {
- result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_INSERT;
- } else if (result == 'x' || result == 'X') {
- result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_FORWARD_DEL;
- } else if (result == 'h' || result == 'H') {
- result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_HOME;
- } else if (result == 'f' || result == 'F') {
- result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_END;
- }
- }
-
- if (result > -1) {
- mAltKey.adjustAfterKeypress();
- mCapKey.adjustAfterKeypress();
- mControlKey.adjustAfterKeypress();
- mFnKey.adjustAfterKeypress();
- }
-
- return result;
- }
-
- /**
- * Handle a keyDown event.
- *
- * @param keyCode the keycode of the keyDown event
- *
- */
- public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMode) throws IOException {
- if (handleKeyCode(keyCode, out, appMode)) {
- return;
- }
- int result = -1;
- switch (keyCode) {
- case KeyEvent.KEYCODE_ALT_RIGHT:
- case KeyEvent.KEYCODE_ALT_LEFT:
- mAltKey.onPress();
- break;
-
- case KeyEvent.KEYCODE_SHIFT_LEFT:
- case KeyEvent.KEYCODE_SHIFT_RIGHT:
- mCapKey.onPress();
- break;
-
- case KEYCODE_CTRL_LEFT:
- case KEYCODE_CTRL_RIGHT:
- mControlKey.onPress();
- break;
-
- case KEYCODE_CAPS_LOCK:
- if (event.getRepeatCount() == 0) {
- mCapsLock = !mCapsLock;
- }
- break;
-
- default: {
- result = event.getUnicodeChar(
- (mCapKey.isActive() || mCapsLock ? KeyEvent.META_SHIFT_ON : 0) |
- (mAltKey.isActive() ? KeyEvent.META_ALT_ON : 0));
- break;
- }
- }
-
- result = mapControlChar(result);
-
- if (result >= KEYCODE_OFFSET) {
- handleKeyCode(result - KEYCODE_OFFSET, out, appMode);
- } else if (result >= 0) {
- out.write(result);
- }
- }
-
- public boolean handleKeyCode(int keyCode, OutputStream out, boolean appMode) throws IOException {
- if (keyCode >= 0 && keyCode < mKeyCodes.length) {
- String code = null;
- if (appMode) {
- code = mAppKeyCodes[keyCode];
- }
- if (code == null) {
- code = mKeyCodes[keyCode];
- }
- if (code != null) {
- int length = code.length();
- for (int i = 0; i < length; i++) {
- out.write(code.charAt(i));
- }
- return true;
- }
- }
- return false;
- }
-
- /**
- * Handle a keyUp event.
- *
- * @param keyCode the keyCode of the keyUp event
- */
- public void keyUp(int keyCode) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_ALT_LEFT:
- case KeyEvent.KEYCODE_ALT_RIGHT:
- mAltKey.onRelease();
- break;
- case KeyEvent.KEYCODE_SHIFT_LEFT:
- case KeyEvent.KEYCODE_SHIFT_RIGHT:
- mCapKey.onRelease();
- break;
-
- case KEYCODE_CTRL_LEFT:
- case KEYCODE_CTRL_RIGHT:
- mControlKey.onRelease();
- break;
-
- default:
- // Ignore other keyUps
- break;
- }
- }
-}
import android.app.NotificationManager;
import android.app.PendingIntent;
+import jackpal.androidterm.util.ServiceForegroundCompat;
+
public class TermService extends Service
{
/* Parallels the value of START_STICKY on API Level >= 5 */
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jackpal.androidterm.model;
+
+/**
+ * An abstract screen interface. A terminal screen stores lines of text. (The
+ * reason to abstract it is to allow different implementations, and to hide
+ * implementation details from clients.)
+ */
+public interface Screen {
+
+ /**
+ * Set line wrap flag for a given row. Affects how lines are logically
+ * wrapped when changing screen size or converting to a transcript.
+ */
+ void setLineWrap(int row);
+
+ /**
+ * Store byte b into the screen at location (x, y)
+ *
+ * @param x X coordinate (also known as column)
+ * @param y Y coordinate (also known as row)
+ * @param b ASCII character to store
+ * @param foreColor the foreground color
+ * @param backColor the background color
+ */
+ void set(int x, int y, byte b, int foreColor, int backColor);
+
+ /**
+ * Scroll the screen down one line. To scroll the whole screen of a 24 line
+ * screen, the arguments would be (0, 24).
+ *
+ * @param topMargin First line that is scrolled.
+ * @param bottomMargin One line after the last line that is scrolled.
+ */
+ void scroll(int topMargin, int bottomMargin, int foreColor, int backColor);
+
+ /**
+ * Block copy characters from one position in the screen to another. The two
+ * positions can overlap. All characters of the source and destination must
+ * be within the bounds of the screen, or else an InvalidParemeterException
+ * will be thrown.
+ *
+ * @param sx source X coordinate
+ * @param sy source Y coordinate
+ * @param w width
+ * @param h height
+ * @param dx destination X coordinate
+ * @param dy destination Y coordinate
+ */
+ void blockCopy(int sx, int sy, int w, int h, int dx, int dy);
+
+ /**
+ * Block set characters. All characters must be within the bounds of the
+ * screen, or else and InvalidParemeterException will be thrown. Typically
+ * this is called with a "val" argument of 32 to clear a block of
+ * characters.
+ *
+ * @param sx source X
+ * @param sy source Y
+ * @param w width
+ * @param h height
+ * @param val value to set.
+ * @param foreColor the foreground color
+ * @param backColor the background color
+ */
+ void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int
+ backColor);
+
+ /**
+ * Get the contents of the transcript buffer as a text string.
+ *
+ * @return the contents of the transcript buffer.
+ */
+ String getTranscriptText();
+
+ /**
+ * Get the selected text inside transcript buffer as a text string.
+ * @param x1 Selection start
+ * @param y1 Selection start
+ * @param x2 Selection end
+ * @param y2 Selection end
+ * @return the contents of the transcript buffer.
+ */
+ String getSelectedText(int x1, int y1, int x2, int y2);
+
+ /**
+ * Resize the screen
+ * @param columns
+ * @param rows
+ */
+ void resize(int columns, int rows, int foreColor, int backColor);
+}
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jackpal.androidterm.model;
+
+import android.graphics.Canvas;
+
+/**
+ * Text renderer interface
+ */
+
+public interface TextRenderer {
+ int getCharacterWidth();
+ int getCharacterHeight();
+ void drawTextRun(Canvas canvas, float x, float y,
+ int lineOffset, char[] text,
+ int index, int count, boolean cursor, int foreColor, int backColor);
+}
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jackpal.androidterm.session;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import android.util.Log;
+
+import jackpal.androidterm.Term;
+import jackpal.androidterm.model.Screen;
+
+/**
+ * Renders text into a screen. Contains all the terminal-specific knowlege and
+ * state. Emulates a subset of the X Window System xterm terminal, which in turn
+ * is an emulator for a subset of the Digital Equipment Corporation vt100
+ * terminal. Missing functionality: text attributes (bold, underline, reverse
+ * video, color) alternate screen cursor key and keypad escape sequences.
+ */
+public class TerminalEmulator {
+
+ /**
+ * The cursor row. Numbered 0..mRows-1.
+ */
+ private int mCursorRow;
+
+ /**
+ * The cursor column. Numbered 0..mColumns-1.
+ */
+ private int mCursorCol;
+
+ /**
+ * The number of character rows in the terminal screen.
+ */
+ private int mRows;
+
+ /**
+ * The number of character columns in the terminal screen.
+ */
+ private int mColumns;
+
+ /**
+ * Used to send data to the remote process. Needed to implement the various
+ * "report" escape sequences.
+ */
+ private FileOutputStream mTermOut;
+
+ /**
+ * Stores the characters that appear on the screen of the emulated terminal.
+ */
+ private Screen mScreen;
+
+ /**
+ * Keeps track of the current argument of the current escape sequence.
+ * Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. (Typically just 0 or 1.)
+ */
+ private int mArgIndex;
+
+ /**
+ * The number of parameter arguments. This name comes from the ANSI standard
+ * for terminal escape codes.
+ */
+ private static final int MAX_ESCAPE_PARAMETERS = 16;
+
+ /**
+ * Holds the arguments of the current escape sequence.
+ */
+ private int[] mArgs = new int[MAX_ESCAPE_PARAMETERS];
+
+ // Escape processing states:
+
+ /**
+ * Escape processing state: Not currently in an escape sequence.
+ */
+ private static final int ESC_NONE = 0;
+
+ /**
+ * Escape processing state: Have seen an ESC character
+ */
+ private static final int ESC = 1;
+
+ /**
+ * Escape processing state: Have seen ESC POUND
+ */
+ private static final int ESC_POUND = 2;
+
+ /**
+ * Escape processing state: Have seen ESC and a character-set-select char
+ */
+ private static final int ESC_SELECT_LEFT_PAREN = 3;
+
+ /**
+ * Escape processing state: Have seen ESC and a character-set-select char
+ */
+ private static final int ESC_SELECT_RIGHT_PAREN = 4;
+
+ /**
+ * Escape processing state: ESC [
+ */
+ private static final int ESC_LEFT_SQUARE_BRACKET = 5;
+
+ /**
+ * Escape processing state: ESC [ ?
+ */
+ private static final int ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK = 6;
+
+ /**
+ * True if the current escape sequence should continue, false if the current
+ * escape sequence should be terminated. Used when parsing a single
+ * character.
+ */
+ private boolean mContinueSequence;
+
+ /**
+ * The current state of the escape sequence state machine.
+ */
+ private int mEscapeState;
+
+ /**
+ * Saved state of the cursor row, Used to implement the save/restore cursor
+ * position escape sequences.
+ */
+ private int mSavedCursorRow;
+
+ /**
+ * Saved state of the cursor column, Used to implement the save/restore
+ * cursor position escape sequences.
+ */
+ private int mSavedCursorCol;
+
+ // DecSet booleans
+
+ /**
+ * This mask indicates 132-column mode is set. (As opposed to 80-column
+ * mode.)
+ */
+ private static final int K_132_COLUMN_MODE_MASK = 1 << 3;
+
+ /**
+ * This mask indicates that origin mode is set. (Cursor addressing is
+ * relative to the absolute screen size, rather than the currently set top
+ * and bottom margins.)
+ */
+ private static final int K_ORIGIN_MODE_MASK = 1 << 6;
+
+ /**
+ * This mask indicates that wraparound mode is set. (As opposed to
+ * stop-at-right-column mode.)
+ */
+ private static final int K_WRAPAROUND_MODE_MASK = 1 << 7;
+
+ /**
+ * Holds multiple DECSET flags. The data is stored this way, rather than in
+ * separate booleans, to make it easier to implement the save-and-restore
+ * semantics. The various k*ModeMask masks can be used to extract and modify
+ * the individual flags current states.
+ */
+ private int mDecFlags;
+
+ /**
+ * Saves away a snapshot of the DECSET flags. Used to implement save and
+ * restore escape sequences.
+ */
+ private int mSavedDecFlags;
+
+ // Modes set with Set Mode / Reset Mode
+
+ /**
+ * True if insert mode (as opposed to replace mode) is active. In insert
+ * mode new characters are inserted, pushing existing text to the right.
+ */
+ private boolean mInsertMode;
+
+ /**
+ * Automatic newline mode. Configures whether pressing return on the
+ * keyboard automatically generates a return as well. Not currently
+ * implemented.
+ */
+ private boolean mAutomaticNewlineMode;
+
+ /**
+ * An array of tab stops. mTabStop[i] is true if there is a tab stop set for
+ * column i.
+ */
+ private boolean[] mTabStop;
+
+ // The margins allow portions of the screen to be locked.
+
+ /**
+ * The top margin of the screen, for scrolling purposes. Ranges from 0 to
+ * mRows-2.
+ */
+ private int mTopMargin;
+
+ /**
+ * The bottom margin of the screen, for scrolling purposes. Ranges from
+ * mTopMargin + 2 to mRows. (Defines the first row after the scrolling
+ * region.
+ */
+ private int mBottomMargin;
+
+ /**
+ * True if the next character to be emitted will be automatically wrapped to
+ * the next line. Used to disambiguate the case where the cursor is
+ * positioned on column mColumns-1.
+ */
+ private boolean mAboutToAutoWrap;
+
+ /**
+ * Used for debugging, counts how many chars have been processed.
+ */
+ private int mProcessedCharCount;
+
+ /**
+ * Foreground color, 0..7, mask with 8 for bold
+ */
+ private int mForeColor;
+
+ /**
+ * Background color, 0..7, mask with 8 for underline
+ */
+ private int mBackColor;
+
+ private boolean mInverseColors;
+
+ private boolean mbKeypadApplicationMode;
+
+ private boolean mAlternateCharSet;
+
+ /**
+ * Used for moving selection up along with the scrolling text
+ */
+ private int mScrollCounter = 0;
+
+ /**
+ * Construct a terminal emulator that uses the supplied screen
+ *
+ * @param screen the screen to render characters into.
+ * @param columns the number of columns to emulate
+ * @param rows the number of rows to emulate
+ * @param termOut the output file descriptor that talks to the pseudo-tty.
+ */
+ public TerminalEmulator(Screen screen, int columns, int rows,
+ FileOutputStream termOut) {
+ mScreen = screen;
+ mRows = rows;
+ mColumns = columns;
+ mTabStop = new boolean[mColumns];
+ mTermOut = termOut;
+ reset();
+ }
+
+ public void updateSize(int columns, int rows) {
+ if (mRows == rows && mColumns == columns) {
+ return;
+ }
+ if (columns <= 0) {
+ throw new IllegalArgumentException("rows:" + columns);
+ }
+
+ if (rows <= 0) {
+ throw new IllegalArgumentException("rows:" + rows);
+ }
+
+ String transcriptText = mScreen.getTranscriptText();
+
+ mScreen.resize(columns, rows, mForeColor, mBackColor);
+
+ if (mRows != rows) {
+ mRows = rows;
+ mTopMargin = 0;
+ mBottomMargin = mRows;
+ }
+ if (mColumns != columns) {
+ int oldColumns = mColumns;
+ mColumns = columns;
+ boolean[] oldTabStop = mTabStop;
+ mTabStop = new boolean[mColumns];
+ int toTransfer = Math.min(oldColumns, columns);
+ System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer);
+ while (mCursorCol >= columns) {
+ mCursorCol -= columns;
+ mCursorRow = Math.min(mBottomMargin-1, mCursorRow + 1);
+ }
+ }
+ mCursorRow = 0;
+ mCursorCol = 0;
+ mAboutToAutoWrap = false;
+
+ int end = transcriptText.length()-1;
+ while ((end >= 0) && transcriptText.charAt(end) == '\n') {
+ end--;
+ }
+ for(int i = 0; i <= end; i++) {
+ byte c = (byte) transcriptText.charAt(i);
+ if (c == '\n') {
+ setCursorCol(0);
+ doLinefeed();
+ } else {
+ emit(c);
+ }
+ }
+ }
+
+ /**
+ * Get the cursor's current row.
+ *
+ * @return the cursor's current row.
+ */
+ public final int getCursorRow() {
+ return mCursorRow;
+ }
+
+ /**
+ * Get the cursor's current column.
+ *
+ * @return the cursor's current column.
+ */
+ public final int getCursorCol() {
+ return mCursorCol;
+ }
+
+ public final boolean getKeypadApplicationMode() {
+ return mbKeypadApplicationMode;
+ }
+
+ private void setDefaultTabStops() {
+ for (int i = 0; i < mColumns; i++) {
+ mTabStop[i] = (i & 7) == 0 && i != 0;
+ }
+ }
+
+ /**
+ * Accept bytes (typically from the pseudo-teletype) and process them.
+ *
+ * @param buffer a byte array containing the bytes to be processed
+ * @param base the first index of the array to process
+ * @param length the number of bytes in the array to process
+ */
+ public void append(byte[] buffer, int base, int length) {
+ for (int i = 0; i < length; i++) {
+ byte b = buffer[base + i];
+ try {
+ if (Term.LOG_CHARACTERS_FLAG) {
+ char printableB = (char) b;
+ if (b < 32 || b > 126) {
+ printableB = ' ';
+ }
+ Log.w(Term.LOG_TAG, "'" + Character.toString(printableB)
+ + "' (" + Integer.toString(b) + ")");
+ }
+ process(b);
+ mProcessedCharCount++;
+ } catch (Exception e) {
+ Log.e(Term.LOG_TAG, "Exception while processing character "
+ + Integer.toString(mProcessedCharCount) + " code "
+ + Integer.toString(b), e);
+ }
+ }
+ }
+
+ private void process(byte b) {
+ switch (b) {
+ case 0: // NUL
+ // Do nothing
+ break;
+
+ case 7: // BEL
+ // Do nothing
+ break;
+
+ case 8: // BS
+ setCursorCol(Math.max(0, mCursorCol - 1));
+ break;
+
+ case 9: // HT
+ // Move to next tab stop, but not past edge of screen
+ setCursorCol(nextTabStop(mCursorCol));
+ break;
+
+ case 13:
+ setCursorCol(0);
+ break;
+
+ case 10: // CR
+ case 11: // VT
+ case 12: // LF
+ doLinefeed();
+ break;
+
+ case 14: // SO:
+ setAltCharSet(true);
+ break;
+
+ case 15: // SI:
+ setAltCharSet(false);
+ break;
+
+
+ case 24: // CAN
+ case 26: // SUB
+ if (mEscapeState != ESC_NONE) {
+ mEscapeState = ESC_NONE;
+ emit((byte) 127);
+ }
+ break;
+
+ case 27: // ESC
+ // Always starts an escape sequence
+ startEscapeSequence(ESC);
+ break;
+
+ case (byte) 0x9b: // CSI
+ startEscapeSequence(ESC_LEFT_SQUARE_BRACKET);
+ break;
+
+ default:
+ mContinueSequence = false;
+ switch (mEscapeState) {
+ case ESC_NONE:
+ if (b >= 32) {
+ emit(b);
+ }
+ break;
+
+ case ESC:
+ doEsc(b);
+ break;
+
+ case ESC_POUND:
+ doEscPound(b);
+ break;
+
+ case ESC_SELECT_LEFT_PAREN:
+ doEscSelectLeftParen(b);
+ break;
+
+ case ESC_SELECT_RIGHT_PAREN:
+ doEscSelectRightParen(b);
+ break;
+
+ case ESC_LEFT_SQUARE_BRACKET:
+ doEscLeftSquareBracket(b);
+ break;
+
+ case ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK:
+ doEscLSBQuest(b);
+ break;
+
+ default:
+ unknownSequence(b);
+ break;
+ }
+ if (!mContinueSequence) {
+ mEscapeState = ESC_NONE;
+ }
+ break;
+ }
+ }
+
+ private void setAltCharSet(boolean alternateCharSet) {
+ mAlternateCharSet = alternateCharSet;
+ }
+
+ private int nextTabStop(int cursorCol) {
+ for (int i = cursorCol; i < mColumns; i++) {
+ if (mTabStop[i]) {
+ return i;
+ }
+ }
+ return mColumns - 1;
+ }
+
+ private void doEscLSBQuest(byte b) {
+ int mask = getDecFlagsMask(getArg0(0));
+ switch (b) {
+ case 'h': // Esc [ ? Pn h - DECSET
+ mDecFlags |= mask;
+ break;
+
+ case 'l': // Esc [ ? Pn l - DECRST
+ mDecFlags &= ~mask;
+ break;
+
+ case 'r': // Esc [ ? Pn r - restore
+ mDecFlags = (mDecFlags & ~mask) | (mSavedDecFlags & mask);
+ break;
+
+ case 's': // Esc [ ? Pn s - save
+ mSavedDecFlags = (mSavedDecFlags & ~mask) | (mDecFlags & mask);
+ break;
+
+ default:
+ parseArg(b);
+ break;
+ }
+
+ // 132 column mode
+ if ((mask & K_132_COLUMN_MODE_MASK) != 0) {
+ // We don't actually set 132 cols, but we do want the
+ // side effect of clearing the screen and homing the cursor.
+ blockClear(0, 0, mColumns, mRows);
+ setCursorRowCol(0, 0);
+ }
+
+ // origin mode
+ if ((mask & K_ORIGIN_MODE_MASK) != 0) {
+ // Home the cursor.
+ setCursorPosition(0, 0);
+ }
+ }
+
+ private int getDecFlagsMask(int argument) {
+ if (argument >= 1 && argument <= 9) {
+ return (1 << argument);
+ }
+
+ return 0;
+ }
+
+ private void startEscapeSequence(int escapeState) {
+ mEscapeState = escapeState;
+ mArgIndex = 0;
+ for (int j = 0; j < MAX_ESCAPE_PARAMETERS; j++) {
+ mArgs[j] = -1;
+ }
+ }
+
+ private void doLinefeed() {
+ int newCursorRow = mCursorRow + 1;
+ if (newCursorRow >= mBottomMargin) {
+ scroll();
+ newCursorRow = mBottomMargin - 1;
+ }
+ setCursorRow(newCursorRow);
+ }
+
+ private void continueSequence() {
+ mContinueSequence = true;
+ }
+
+ private void continueSequence(int state) {
+ mEscapeState = state;
+ mContinueSequence = true;
+ }
+
+ private void doEscSelectLeftParen(byte b) {
+ doSelectCharSet(true, b);
+ }
+
+ private void doEscSelectRightParen(byte b) {
+ doSelectCharSet(false, b);
+ }
+
+ private void doSelectCharSet(boolean isG0CharSet, byte b) {
+ switch (b) {
+ case 'A': // United Kingdom character set
+ break;
+ case 'B': // ASCII set
+ break;
+ case '0': // Special Graphics
+ break;
+ case '1': // Alternate character set
+ break;
+ case '2':
+ break;
+ default:
+ unknownSequence(b);
+ }
+ }
+
+ private void doEscPound(byte b) {
+ switch (b) {
+ case '8': // Esc # 8 - DECALN alignment test
+ mScreen.blockSet(0, 0, mColumns, mRows, 'E',
+ getForeColor(), getBackColor());
+ break;
+
+ default:
+ unknownSequence(b);
+ break;
+ }
+ }
+
+ private void doEsc(byte b) {
+ switch (b) {
+ case '#':
+ continueSequence(ESC_POUND);
+ break;
+
+ case '(':
+ continueSequence(ESC_SELECT_LEFT_PAREN);
+ break;
+
+ case ')':
+ continueSequence(ESC_SELECT_RIGHT_PAREN);
+ break;
+
+ case '7': // DECSC save cursor
+ mSavedCursorRow = mCursorRow;
+ mSavedCursorCol = mCursorCol;
+ break;
+
+ case '8': // DECRC restore cursor
+ setCursorRowCol(mSavedCursorRow, mSavedCursorCol);
+ break;
+
+ case 'D': // INDEX
+ doLinefeed();
+ break;
+
+ case 'E': // NEL
+ setCursorCol(0);
+ doLinefeed();
+ break;
+
+ case 'F': // Cursor to lower-left corner of screen
+ setCursorRowCol(0, mBottomMargin - 1);
+ break;
+
+ case 'H': // Tab set
+ mTabStop[mCursorCol] = true;
+ break;
+
+ case 'M': // Reverse index
+ if (mCursorRow <= mTopMargin) {
+ mScreen.blockCopy(0, mTopMargin, mColumns, mBottomMargin
+ - (mTopMargin + 1), 0, mTopMargin + 1);
+ blockClear(0, mTopMargin, mColumns);
+ } else {
+ mCursorRow--;
+ }
+
+ break;
+
+ case 'N': // SS2
+ unimplementedSequence(b);
+ break;
+
+ case '0': // SS3
+ unimplementedSequence(b);
+ break;
+
+ case 'P': // Device control string
+ unimplementedSequence(b);
+ break;
+
+ case 'Z': // return terminal ID
+ sendDeviceAttributes();
+ break;
+
+ case '[':
+ continueSequence(ESC_LEFT_SQUARE_BRACKET);
+ break;
+
+ case '=': // DECKPAM
+ mbKeypadApplicationMode = true;
+ break;
+
+ case '>' : // DECKPNM
+ mbKeypadApplicationMode = false;
+ break;
+
+ default:
+ unknownSequence(b);
+ break;
+ }
+ }
+
+ private void doEscLeftSquareBracket(byte b) {
+ switch (b) {
+ case '@': // ESC [ Pn @ - ICH Insert Characters
+ {
+ int charsAfterCursor = mColumns - mCursorCol;
+ int charsToInsert = Math.min(getArg0(1), charsAfterCursor);
+ int charsToMove = charsAfterCursor - charsToInsert;
+ mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1,
+ mCursorCol + charsToInsert, mCursorRow);
+ blockClear(mCursorCol, mCursorRow, charsToInsert);
+ }
+ break;
+
+ case 'A': // ESC [ Pn A - Cursor Up
+ setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1)));
+ break;
+
+ case 'B': // ESC [ Pn B - Cursor Down
+ setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1)));
+ break;
+
+ case 'C': // ESC [ Pn C - Cursor Right
+ setCursorCol(Math.min(mColumns - 1, mCursorCol + getArg0(1)));
+ break;
+
+ case 'D': // ESC [ Pn D - Cursor Left
+ setCursorCol(Math.max(0, mCursorCol - getArg0(1)));
+ break;
+
+ case 'G': // ESC [ Pn G - Cursor Horizontal Absolute
+ setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1);
+ break;
+
+ case 'H': // ESC [ Pn ; H - Cursor Position
+ setHorizontalVerticalPosition();
+ break;
+
+ case 'J': // ESC [ Pn J - Erase in Display
+ switch (getArg0(0)) {
+ case 0: // Clear below
+ blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
+ blockClear(0, mCursorRow + 1, mColumns,
+ mBottomMargin - (mCursorRow + 1));
+ break;
+
+ case 1: // Erase from the start of the screen to the cursor.
+ blockClear(0, mTopMargin, mColumns, mCursorRow - mTopMargin);
+ blockClear(0, mCursorRow, mCursorCol + 1);
+ break;
+
+ case 2: // Clear all
+ blockClear(0, mTopMargin, mColumns, mBottomMargin - mTopMargin);
+ break;
+
+ default:
+ unknownSequence(b);
+ break;
+ }
+ break;
+
+ case 'K': // ESC [ Pn K - Erase in Line
+ switch (getArg0(0)) {
+ case 0: // Clear to right
+ blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
+ break;
+
+ case 1: // Erase start of line to cursor (including cursor)
+ blockClear(0, mCursorRow, mCursorCol + 1);
+ break;
+
+ case 2: // Clear whole line
+ blockClear(0, mCursorRow, mColumns);
+ break;
+
+ default:
+ unknownSequence(b);
+ break;
+ }
+ break;
+
+ case 'L': // Insert Lines
+ {
+ int linesAfterCursor = mBottomMargin - mCursorRow;
+ int linesToInsert = Math.min(getArg0(1), linesAfterCursor);
+ int linesToMove = linesAfterCursor - linesToInsert;
+ mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0,
+ mCursorRow + linesToInsert);
+ blockClear(0, mCursorRow, mColumns, linesToInsert);
+ }
+ break;
+
+ case 'M': // Delete Lines
+ {
+ int linesAfterCursor = mBottomMargin - mCursorRow;
+ int linesToDelete = Math.min(getArg0(1), linesAfterCursor);
+ int linesToMove = linesAfterCursor - linesToDelete;
+ mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns,
+ linesToMove, 0, mCursorRow);
+ blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete);
+ }
+ break;
+
+ case 'P': // Delete Characters
+ {
+ int charsAfterCursor = mColumns - mCursorCol;
+ int charsToDelete = Math.min(getArg0(1), charsAfterCursor);
+ int charsToMove = charsAfterCursor - charsToDelete;
+ mScreen.blockCopy(mCursorCol + charsToDelete, mCursorRow,
+ charsToMove, 1, mCursorCol, mCursorRow);
+ blockClear(mCursorCol + charsToMove, mCursorRow, charsToDelete);
+ }
+ break;
+
+ case 'T': // Mouse tracking
+ unimplementedSequence(b);
+ break;
+
+ case '?': // Esc [ ? -- start of a private mode set
+ continueSequence(ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK);
+ break;
+
+ case 'c': // Send device attributes
+ sendDeviceAttributes();
+ break;
+
+ case 'd': // ESC [ Pn d - Vert Position Absolute
+ setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1);
+ break;
+
+ case 'f': // Horizontal and Vertical Position
+ setHorizontalVerticalPosition();
+ break;
+
+ case 'g': // Clear tab stop
+ switch (getArg0(0)) {
+ case 0:
+ mTabStop[mCursorCol] = false;
+ break;
+
+ case 3:
+ for (int i = 0; i < mColumns; i++) {
+ mTabStop[i] = false;
+ }
+ break;
+
+ default:
+ // Specified to have no effect.
+ break;
+ }
+ break;
+
+ case 'h': // Set Mode
+ doSetMode(true);
+ break;
+
+ case 'l': // Reset Mode
+ doSetMode(false);
+ break;
+
+ case 'm': // Esc [ Pn m - character attributes.
+ selectGraphicRendition();
+ break;
+
+ case 'r': // Esc [ Pn ; Pn r - set top and bottom margins
+ {
+ // The top margin defaults to 1, the bottom margin
+ // (unusually for arguments) defaults to mRows.
+ //
+ // The escape sequence numbers top 1..23, but we
+ // number top 0..22.
+ // The escape sequence numbers bottom 2..24, and
+ // so do we (because we use a zero based numbering
+ // scheme, but we store the first line below the
+ // bottom-most scrolling line.
+ // As a result, we adjust the top line by -1, but
+ // we leave the bottom line alone.
+ //
+ // Also require that top + 2 <= bottom
+
+ int top = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
+ int bottom = Math.max(top + 2, Math.min(getArg1(mRows), mRows));
+ mTopMargin = top;
+ mBottomMargin = bottom;
+
+ // The cursor is placed in the home position
+ setCursorRowCol(mTopMargin, 0);
+ }
+ break;
+
+ default:
+ parseArg(b);
+ break;
+ }
+ }
+
+ private void selectGraphicRendition() {
+ for (int i = 0; i <= mArgIndex; i++) {
+ int code = mArgs[i];
+ if ( code < 0) {
+ if (mArgIndex > 0) {
+ continue;
+ } else {
+ code = 0;
+ }
+ }
+
+ // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
+
+ if (code == 0) { // reset
+ mInverseColors = false;
+ mForeColor = 7;
+ mBackColor = 0;
+ } else if (code == 1) { // bold
+ mForeColor |= 0x8;
+ } else if (code == 4) { // underscore
+ mBackColor |= 0x8;
+ } else if (code == 7) { // inverse
+ mInverseColors = true;
+ } else if (code == 22) { // Normal color or intensity, neither bright, bold nor faint
+ mForeColor &= 0x7;
+ } else if (code == 24) { // underline: none
+ mBackColor &= 0x7;
+ } else if (code == 27) { // image: positive
+ mInverseColors = false;
+ } else if (code >= 30 && code <= 37) { // foreground color
+ mForeColor = (mForeColor & 0x8) | (code - 30);
+ } else if (code == 39) { // set default text color
+ mForeColor = 7;
+ mBackColor = mBackColor & 0x7;
+ } else if (code >= 40 && code <= 47) { // background color
+ mBackColor = (mBackColor & 0x8) | (code - 40);
+ } else if (code == 49) { // set default background color
+ mBackColor = mBackColor & 0x8; // color 0, but preserve underscore.
+ } else {
+ if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+ Log.w(Term.LOG_TAG, String.format("SGR unknown code %d", code));
+ }
+ }
+ }
+ }
+
+ private void blockClear(int sx, int sy, int w) {
+ blockClear(sx, sy, w, 1);
+ }
+
+ private void blockClear(int sx, int sy, int w, int h) {
+ mScreen.blockSet(sx, sy, w, h, ' ', getForeColor(), getBackColor());
+ }
+
+ private int getForeColor() {
+ return mInverseColors ?
+ ((mBackColor & 0x7) | (mForeColor & 0x8)) : mForeColor;
+ }
+
+ private int getBackColor() {
+ return mInverseColors ?
+ ((mForeColor & 0x7) | (mBackColor & 0x8)) : mBackColor;
+ }
+
+ private void doSetMode(boolean newValue) {
+ int modeBit = getArg0(0);
+ switch (modeBit) {
+ case 4:
+ mInsertMode = newValue;
+ break;
+
+ case 20:
+ mAutomaticNewlineMode = newValue;
+ break;
+
+ default:
+ unknownParameter(modeBit);
+ break;
+ }
+ }
+
+ private void setHorizontalVerticalPosition() {
+
+ // Parameters are Row ; Column
+
+ setCursorPosition(getArg1(1) - 1, getArg0(1) - 1);
+ }
+
+ private void setCursorPosition(int x, int y) {
+ int effectiveTopMargin = 0;
+ int effectiveBottomMargin = mRows;
+ if ((mDecFlags & K_ORIGIN_MODE_MASK) != 0) {
+ effectiveTopMargin = mTopMargin;
+ effectiveBottomMargin = mBottomMargin;
+ }
+ int newRow =
+ Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y,
+ effectiveBottomMargin - 1));
+ int newCol = Math.max(0, Math.min(x, mColumns - 1));
+ setCursorRowCol(newRow, newCol);
+ }
+
+ private void sendDeviceAttributes() {
+ // This identifies us as a DEC vt100 with advanced
+ // video options. This is what the xterm terminal
+ // emulator sends.
+ byte[] attributes =
+ {
+ /* VT100 */
+ (byte) 27, (byte) '[', (byte) '?', (byte) '1',
+ (byte) ';', (byte) '2', (byte) 'c'
+
+ /* VT220
+ (byte) 27, (byte) '[', (byte) '?', (byte) '6',
+ (byte) '0', (byte) ';',
+ (byte) '1', (byte) ';',
+ (byte) '2', (byte) ';',
+ (byte) '6', (byte) ';',
+ (byte) '8', (byte) ';',
+ (byte) '9', (byte) ';',
+ (byte) '1', (byte) '5', (byte) ';',
+ (byte) 'c'
+ */
+ };
+
+ write(attributes);
+ }
+
+ /**
+ * Send data to the shell process
+ * @param data
+ */
+ private void write(byte[] data) {
+ try {
+ mTermOut.write(data);
+ mTermOut.flush();
+ } catch (IOException e) {
+ // Ignore exception
+ // We don't really care if the receiver isn't listening.
+ // We just make a best effort to answer the query.
+ }
+ }
+
+ private void scroll() {
+ //System.out.println("Scroll(): mTopMargin " + mTopMargin + " mBottomMargin " + mBottomMargin);
+ mScrollCounter ++;
+ mScreen.scroll(mTopMargin, mBottomMargin,
+ getForeColor(), getBackColor());
+ }
+
+ /**
+ * Process the next ASCII character of a parameter.
+ *
+ * @param b The next ASCII character of the paramater sequence.
+ */
+ private void parseArg(byte b) {
+ if (b >= '0' && b <= '9') {
+ if (mArgIndex < mArgs.length) {
+ int oldValue = mArgs[mArgIndex];
+ int thisDigit = b - '0';
+ int value;
+ if (oldValue >= 0) {
+ value = oldValue * 10 + thisDigit;
+ } else {
+ value = thisDigit;
+ }
+ mArgs[mArgIndex] = value;
+ }
+ continueSequence();
+ } else if (b == ';') {
+ if (mArgIndex < mArgs.length) {
+ mArgIndex++;
+ }
+ continueSequence();
+ } else {
+ unknownSequence(b);
+ }
+ }
+
+ private int getArg0(int defaultValue) {
+ return getArg(0, defaultValue);
+ }
+
+ private int getArg1(int defaultValue) {
+ return getArg(1, defaultValue);
+ }
+
+ private int getArg(int index, int defaultValue) {
+ int result = mArgs[index];
+ if (result < 0) {
+ result = defaultValue;
+ }
+ return result;
+ }
+
+ private void unimplementedSequence(byte b) {
+ if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+ logError("unimplemented", b);
+ }
+ finishSequence();
+ }
+
+ private void unknownSequence(byte b) {
+ if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+ logError("unknown", b);
+ }
+ finishSequence();
+ }
+
+ private void unknownParameter(int parameter) {
+ if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+ StringBuilder buf = new StringBuilder();
+ buf.append("Unknown parameter");
+ buf.append(parameter);
+ logError(buf.toString());
+ }
+ }
+
+ private void logError(String errorType, byte b) {
+ if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+ StringBuilder buf = new StringBuilder();
+ buf.append(errorType);
+ buf.append(" sequence ");
+ buf.append(" EscapeState: ");
+ buf.append(mEscapeState);
+ buf.append(" char: '");
+ buf.append((char) b);
+ buf.append("' (");
+ buf.append(b);
+ buf.append(")");
+ boolean firstArg = true;
+ for (int i = 0; i <= mArgIndex; i++) {
+ int value = mArgs[i];
+ if (value >= 0) {
+ if (firstArg) {
+ firstArg = false;
+ buf.append("args = ");
+ }
+ buf.append(String.format("%d; ", value));
+ }
+ }
+ logError(buf.toString());
+ }
+ }
+
+ private void logError(String error) {
+ if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+ Log.e(Term.LOG_TAG, error);
+ }
+ finishSequence();
+ }
+
+ private void finishSequence() {
+ mEscapeState = ESC_NONE;
+ }
+
+ private boolean autoWrapEnabled() {
+ // Always enable auto wrap, because it's useful on a small screen
+ return true;
+ // return (mDecFlags & K_WRAPAROUND_MODE_MASK) != 0;
+ }
+
+ /**
+ * Send an ASCII character to the screen.
+ *
+ * @param b the ASCII character to display.
+ */
+ private void emit(byte b) {
+ boolean autoWrap = autoWrapEnabled();
+
+ if (autoWrap) {
+ if (mCursorCol == mColumns - 1 && mAboutToAutoWrap) {
+ mScreen.setLineWrap(mCursorRow);
+ mCursorCol = 0;
+ if (mCursorRow + 1 < mBottomMargin) {
+ mCursorRow++;
+ } else {
+ scroll();
+ }
+ }
+ }
+
+ if (mInsertMode) { // Move character to right one space
+ int destCol = mCursorCol + 1;
+ if (destCol < mColumns) {
+ mScreen.blockCopy(mCursorCol, mCursorRow, mColumns - destCol,
+ 1, destCol, mCursorRow);
+ }
+ }
+
+ mScreen.set(mCursorCol, mCursorRow, b, getForeColor(), getBackColor());
+
+ if (autoWrap) {
+ mAboutToAutoWrap = (mCursorCol == mColumns - 1);
+ }
+
+ mCursorCol = Math.min(mCursorCol + 1, mColumns - 1);
+ }
+
+ private void setCursorRow(int row) {
+ mCursorRow = row;
+ mAboutToAutoWrap = false;
+ }
+
+ private void setCursorCol(int col) {
+ mCursorCol = col;
+ mAboutToAutoWrap = false;
+ }
+
+ private void setCursorRowCol(int row, int col) {
+ mCursorRow = Math.min(row, mRows-1);
+ mCursorCol = Math.min(col, mColumns-1);
+ mAboutToAutoWrap = false;
+ }
+
+ public int getScrollCounter() {
+ return mScrollCounter;
+ }
+
+ public void clearScrollCounter() {
+ mScrollCounter = 0;
+ }
+
+ /**
+ * Reset the terminal emulator to its initial state.
+ */
+ public void reset() {
+ mCursorRow = 0;
+ mCursorCol = 0;
+ mArgIndex = 0;
+ mContinueSequence = false;
+ mEscapeState = ESC_NONE;
+ mSavedCursorRow = 0;
+ mSavedCursorCol = 0;
+ mDecFlags = 0;
+ mSavedDecFlags = 0;
+ mInsertMode = false;
+ mAutomaticNewlineMode = false;
+ mTopMargin = 0;
+ mBottomMargin = mRows;
+ mAboutToAutoWrap = false;
+ mForeColor = 7;
+ mBackColor = 0;
+ mInverseColors = false;
+ mbKeypadApplicationMode = false;
+ mAlternateCharSet = false;
+ // mProcessedCharCount is preserved unchanged.
+ setDefaultTabStops();
+ blockClear(0, 0, mColumns, mRows);
+ }
+
+ public String getTranscriptText() {
+ return mScreen.getTranscriptText();
+ }
+ public String getSelectedText(int x1, int y1, int x2, int y2) {
+ return mScreen.getSelectedText(x1, y1, x2, y2);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jackpal.androidterm.session;
+
+import android.graphics.Canvas;
+import android.util.Log;
+
+import jackpal.androidterm.model.Screen;
+import jackpal.androidterm.model.TextRenderer;
+
+/**
+ * A TranscriptScreen is a screen that remembers data that's been scrolled. The
+ * old data is stored in a ring buffer to minimize the amount of copying that
+ * needs to be done. The transcript does its own drawing, to avoid having to
+ * expose its internal data structures.
+ */
+public class TranscriptScreen implements Screen {
+ private static final String TAG = "TranscriptScreen";
+
+ /**
+ * The width of the transcript, in characters. Fixed at initialization.
+ */
+ private int mColumns;
+
+ /**
+ * The total number of rows in the transcript and the screen. Fixed at
+ * initialization.
+ */
+ private int mTotalRows;
+
+ /**
+ * The number of rows in the active portion of the transcript. Doesn't
+ * include the screen.
+ */
+ private int mActiveTranscriptRows;
+
+ /**
+ * Which row is currently the topmost line of the transcript. Used to
+ * implement a circular buffer.
+ */
+ private int mHead;
+
+ /**
+ * The number of active rows, includes both the transcript and the screen.
+ */
+ private int mActiveRows;
+
+ /**
+ * The number of rows in the screen.
+ */
+ private int mScreenRows;
+
+ /**
+ * The data for both the screen and the transcript. The first mScreenRows *
+ * mLineWidth characters are the screen, the rest are the transcript.
+ * The low byte encodes the ASCII character, the high byte encodes the
+ * foreground and background colors, plus underline and bold.
+ */
+ private char[] mData;
+
+ /**
+ * The data's stored as color-encoded chars, but the drawing routines require chars, so we
+ * need a temporary buffer to hold a row's worth of characters.
+ */
+ private char[] mRowBuffer;
+
+ /**
+ * Flags that keep track of whether the current line logically wraps to the
+ * next line. This is used when resizing the screen and when copying to the
+ * clipboard or an email attachment
+ */
+
+ private boolean[] mLineWrap;
+
+ /**
+ * Create a transcript screen.
+ *
+ * @param columns the width of the screen in characters.
+ * @param totalRows the height of the entire text area, in rows of text.
+ * @param screenRows the height of just the screen, not including the
+ * transcript that holds lines that have scrolled off the top of the
+ * screen.
+ */
+ public TranscriptScreen(int columns, int totalRows, int screenRows,
+ int foreColor, int backColor) {
+ init(columns, totalRows, screenRows, foreColor, backColor);
+ }
+
+ private void init(int columns, int totalRows, int screenRows, int foreColor, int backColor) {
+ mColumns = columns;
+ mTotalRows = totalRows;
+ mActiveTranscriptRows = 0;
+ mHead = 0;
+ mActiveRows = screenRows;
+ mScreenRows = screenRows;
+ int totalSize = columns * totalRows;
+ mData = new char[totalSize];
+ blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor);
+ mRowBuffer = new char[columns];
+ mLineWrap = new boolean[totalRows];
+ consistencyCheck();
+ }
+
+ /**
+ * Convert a row value from the public external coordinate system to our
+ * internal private coordinate system. External coordinate system:
+ * -mActiveTranscriptRows to mScreenRows-1, with the screen being
+ * 0..mScreenRows-1 Internal coordinate system: 0..mScreenRows-1 rows of
+ * mData are the visible rows. mScreenRows..mActiveRows - 1 are the
+ * transcript, stored as a circular buffer.
+ *
+ * @param row a row in the external coordinate system.
+ * @return The row corresponding to the input argument in the private
+ * coordinate system.
+ */
+ private int externalToInternalRow(int row) {
+ if (row < -mActiveTranscriptRows || row >= mScreenRows) {
+ String errorMessage = "externalToInternalRow "+ row +
+ " " + mActiveTranscriptRows + " " + mScreenRows;
+ Log.e(TAG, errorMessage);
+ throw new IllegalArgumentException(errorMessage);
+ }
+ if (row >= 0) {
+ return row; // This is a visible row.
+ }
+ return mScreenRows
+ + ((mHead + mActiveTranscriptRows + row) % mActiveTranscriptRows);
+ }
+
+ private int getOffset(int externalLine) {
+ return externalToInternalRow(externalLine) * mColumns;
+ }
+
+ private int getOffset(int x, int y) {
+ return getOffset(y) + x;
+ }
+
+ public void setLineWrap(int row) {
+ mLineWrap[externalToInternalRow(row)] = true;
+ }
+
+ /**
+ * Store byte b into the screen at location (x, y)
+ *
+ * @param x X coordinate (also known as column)
+ * @param y Y coordinate (also known as row)
+ * @param b ASCII character to store
+ * @param foreColor the foreground color
+ * @param backColor the background color
+ */
+ public void set(int x, int y, byte b, int foreColor, int backColor) {
+ mData[getOffset(x, y)] = encode(b, foreColor, backColor);
+ }
+
+ private char encode(int b, int foreColor, int backColor) {
+ return (char) ((foreColor << 12) | (backColor << 8) | b);
+ }
+
+ /**
+ * Scroll the screen down one line. To scroll the whole screen of a 24 line
+ * screen, the arguments would be (0, 24).
+ *
+ * @param topMargin First line that is scrolled.
+ * @param bottomMargin One line after the last line that is scrolled.
+ */
+ public void scroll(int topMargin, int bottomMargin, int foreColor,
+ int backColor) {
+ // Separate out reasons so that stack crawls help us
+ // figure out which condition was violated.
+ if (topMargin > bottomMargin - 1) {
+ throw new IllegalArgumentException();
+ }
+
+ if (topMargin > mScreenRows - 1) {
+ throw new IllegalArgumentException();
+ }
+
+ if (bottomMargin > mScreenRows) {
+ throw new IllegalArgumentException();
+ }
+
+ // Adjust the transcript so that the last line of the transcript
+ // is ready to receive the newly scrolled data
+ consistencyCheck();
+ int expansionRows = Math.min(1, mTotalRows - mActiveRows);
+ int rollRows = 1 - expansionRows;
+ mActiveRows += expansionRows;
+ mActiveTranscriptRows += expansionRows;
+ if (mActiveTranscriptRows > 0) {
+ mHead = (mHead + rollRows) % mActiveTranscriptRows;
+ }
+ consistencyCheck();
+
+ // Block move the scroll line to the transcript
+ int topOffset = getOffset(topMargin);
+ int destOffset = getOffset(-1);
+ System.arraycopy(mData, topOffset, mData, destOffset, mColumns);
+
+ int topLine = externalToInternalRow(topMargin);
+ int destLine = externalToInternalRow(-1);
+ System.arraycopy(mLineWrap, topLine, mLineWrap, destLine, 1);
+
+ // Block move the scrolled data up
+ int numScrollChars = (bottomMargin - topMargin - 1) * mColumns;
+ System.arraycopy(mData, topOffset + mColumns, mData, topOffset,
+ numScrollChars);
+ int numScrollLines = (bottomMargin - topMargin - 1);
+ System.arraycopy(mLineWrap, topLine + 1, mLineWrap, topLine,
+ numScrollLines);
+
+ // Erase the bottom line of the scroll region
+ blockSet(0, bottomMargin - 1, mColumns, 1, ' ', foreColor, backColor);
+ mLineWrap[externalToInternalRow(bottomMargin-1)] = false;
+ }
+
+ private void consistencyCheck() {
+ checkPositive(mColumns);
+ checkPositive(mTotalRows);
+ checkRange(0, mActiveTranscriptRows, mTotalRows);
+ if (mActiveTranscriptRows == 0) {
+ checkEqual(mHead, 0);
+ } else {
+ checkRange(0, mHead, mActiveTranscriptRows-1);
+ }
+ checkEqual(mScreenRows + mActiveTranscriptRows, mActiveRows);
+ checkRange(0, mScreenRows, mTotalRows);
+
+ checkEqual(mTotalRows, mLineWrap.length);
+ checkEqual(mTotalRows*mColumns, mData.length);
+ checkEqual(mColumns, mRowBuffer.length);
+ }
+
+ private void checkPositive(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("checkPositive " + n);
+ }
+ }
+
+ private void checkRange(int a, int b, int c) {
+ if (a > b || b > c) {
+ throw new IllegalArgumentException("checkRange " + a + " <= " + b + " <= " + c);
+ }
+ }
+
+ private void checkEqual(int a, int b) {
+ if (a != b) {
+ throw new IllegalArgumentException("checkEqual " + a + " == " + b);
+ }
+ }
+
+ /**
+ * Block copy characters from one position in the screen to another. The two
+ * positions can overlap. All characters of the source and destination must
+ * be within the bounds of the screen, or else an InvalidParemeterException
+ * will be thrown.
+ *
+ * @param sx source X coordinate
+ * @param sy source Y coordinate
+ * @param w width
+ * @param h height
+ * @param dx destination X coordinate
+ * @param dy destination Y coordinate
+ */
+ public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
+ if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows
+ || dx < 0 || dx + w > mColumns || dy < 0
+ || dy + h > mScreenRows) {
+ throw new IllegalArgumentException();
+ }
+ if (sy > dy) {
+ // Move in increasing order
+ for (int y = 0; y < h; y++) {
+ int srcOffset = getOffset(sx, sy + y);
+ int dstOffset = getOffset(dx, dy + y);
+ System.arraycopy(mData, srcOffset, mData, dstOffset, w);
+ }
+ } else {
+ // Move in decreasing order
+ for (int y = 0; y < h; y++) {
+ int y2 = h - (y + 1);
+ int srcOffset = getOffset(sx, sy + y2);
+ int dstOffset = getOffset(dx, dy + y2);
+ System.arraycopy(mData, srcOffset, mData, dstOffset, w);
+ }
+ }
+ }
+
+ /**
+ * Block set characters. All characters must be within the bounds of the
+ * screen, or else and InvalidParemeterException will be thrown. Typically
+ * this is called with a "val" argument of 32 to clear a block of
+ * characters.
+ *
+ * @param sx source X
+ * @param sy source Y
+ * @param w width
+ * @param h height
+ * @param val value to set.
+ */
+ public void blockSet(int sx, int sy, int w, int h, int val,
+ int foreColor, int backColor) {
+ if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
+ throw new IllegalArgumentException();
+ }
+ char[] data = mData;
+ char encodedVal = encode(val, foreColor, backColor);
+ for (int y = 0; y < h; y++) {
+ int offset = getOffset(sx, sy + y);
+ for (int x = 0; x < w; x++) {
+ data[offset + x] = encodedVal;
+ }
+ }
+ }
+
+ /**
+ * Draw a row of text. Out-of-bounds rows are blank, not errors.
+ *
+ * @param row The row of text to draw.
+ * @param canvas The canvas to draw to.
+ * @param x The x coordinate origin of the drawing
+ * @param y The y coordinate origin of the drawing
+ * @param renderer The renderer to use to draw the text
+ * @param cx the cursor X coordinate, -1 means don't draw it
+ * @param selx1 the text selection start X coordinate
+ * @param selx2 the text selection end X coordinate, if equals to selx1 don't draw selection
+ * @param imeText current IME text, to be rendered at cursor
+ */
+ public final void drawText(int row, Canvas canvas, float x, float y,
+ TextRenderer renderer, int cx, int selx1, int selx2, String imeText) {
+
+ // Out-of-bounds rows are blank.
+ if (row < -mActiveTranscriptRows || row >= mScreenRows) {
+ return;
+ }
+
+ // Copy the data from the byte array to a char array so they can
+ // be drawn.
+
+ int offset = getOffset(row);
+ char[] rowBuffer = mRowBuffer;
+ char[] data = mData;
+ int columns = mColumns;
+ int lastColors = 0;
+ int lastRunStart = -1;
+ final int CURSOR_MASK = 0x10000;
+ for (int i = 0; i < columns; i++) {
+ char c = data[offset + i];
+ int colors = (char) (c & 0xff00);
+ if (cx == i || (i >= selx1 && i <= selx2)) {
+ // Set cursor background color:
+ colors |= CURSOR_MASK;
+ }
+ rowBuffer[i] = (char) (c & 0x00ff);
+ if (colors != lastColors) {
+ if (lastRunStart >= 0) {
+ renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
+ lastRunStart, i - lastRunStart,
+ (lastColors & CURSOR_MASK) != 0,
+ 0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
+ }
+ lastColors = colors;
+ lastRunStart = i;
+ }
+ }
+ if (lastRunStart >= 0) {
+ renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
+ lastRunStart, columns - lastRunStart,
+ (lastColors & CURSOR_MASK) != 0,
+ 0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
+ }
+
+ if (cx >= 0 && imeText.length() > 0) {
+ int imeLength = Math.min(columns, imeText.length());
+ int imeOffset = imeText.length() - imeLength;
+ int imePosition = Math.min(cx, columns - imeLength);
+ renderer.drawTextRun(canvas, x, y, imePosition, imeText.toCharArray(),
+ imeOffset, imeLength, true, 0x0f, 0x00);
+ }
+ }
+
+ /**
+ * Get the count of active rows.
+ *
+ * @return the count of active rows.
+ */
+ public int getActiveRows() {
+ return mActiveRows;
+ }
+
+ /**
+ * Get the count of active transcript rows.
+ *
+ * @return the count of active transcript rows.
+ */
+ public int getActiveTranscriptRows() {
+ return mActiveTranscriptRows;
+ }
+
+ public String getTranscriptText() {
+ return internalGetTranscriptText(true, 0, -mActiveTranscriptRows, mColumns, mScreenRows);
+ }
+
+ public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
+ return internalGetTranscriptText(true, selX1, selY1, selX2, selY2);
+ }
+
+ private String internalGetTranscriptText(boolean stripColors, int selX1, int selY1, int selX2, int selY2) {
+ StringBuilder builder = new StringBuilder();
+ char[] rowBuffer = mRowBuffer;
+ char[] data = mData;
+ int columns = mColumns;
+ for (int row = -mActiveTranscriptRows; row < mScreenRows; row++) {
+ int offset = getOffset(row);
+ int lastPrintingChar = -1;
+ for (int column = 0; column < columns; column++) {
+ char c = data[offset + column];
+ if (stripColors) {
+ c = (char) (c & 0xff);
+ }
+ if ((c & 0xff) != ' ') {
+ lastPrintingChar = column;
+ }
+ rowBuffer[column] = c;
+ }
+ if ( row >= selY1 && row <= selY2 ) {
+ int x1 = 0;
+ int x2 = 0;
+ if ( row == selY1 ) {
+ x1 = selX1;
+ }
+ if ( row == selY2 ) {
+ x2 = selX2;
+ } else {
+ x2 = columns;
+ }
+ if (mLineWrap[externalToInternalRow(row)]) {
+ builder.append(rowBuffer, x1, x2 - x1);
+ } else {
+ builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1)));
+ builder.append('\n');
+ }
+ }
+ }
+ return builder.toString();
+ }
+
+ public void resize(int columns, int rows, int foreColor, int backColor) {
+ init(columns, mTotalRows, rows, foreColor, backColor);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jackpal.androidterm.util;
+
+/**
+ * A multi-thread-safe produce-consumer byte array.
+ * Only allows one producer and one consumer.
+ */
+
+public class ByteQueue {
+ public ByteQueue(int size) {
+ mBuffer = new byte[size];
+ }
+
+ public int getBytesAvailable() {
+ synchronized(this) {
+ return mStoredBytes;
+ }
+ }
+
+ public int read(byte[] buffer, int offset, int length)
+ throws InterruptedException {
+ if (length + offset > buffer.length) {
+ throw
+ new IllegalArgumentException("length + offset > buffer.length");
+ }
+ if (length < 0) {
+ throw
+ new IllegalArgumentException("length < 0");
+
+ }
+ if (length == 0) {
+ return 0;
+ }
+ synchronized(this) {
+ while (mStoredBytes == 0) {
+ wait();
+ }
+ int totalRead = 0;
+ int bufferLength = mBuffer.length;
+ boolean wasFull = bufferLength == mStoredBytes;
+ while (length > 0 && mStoredBytes > 0) {
+ int oneRun = Math.min(bufferLength - mHead, mStoredBytes);
+ int bytesToCopy = Math.min(length, oneRun);
+ System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy);
+ mHead += bytesToCopy;
+ if (mHead >= bufferLength) {
+ mHead = 0;
+ }
+ mStoredBytes -= bytesToCopy;
+ length -= bytesToCopy;
+ offset += bytesToCopy;
+ totalRead += bytesToCopy;
+ }
+ if (wasFull) {
+ notify();
+ }
+ return totalRead;
+ }
+ }
+
+ public void write(byte[] buffer, int offset, int length)
+ throws InterruptedException {
+ if (length + offset > buffer.length) {
+ throw
+ new IllegalArgumentException("length + offset > buffer.length");
+ }
+ if (length < 0) {
+ throw
+ new IllegalArgumentException("length < 0");
+
+ }
+ if (length == 0) {
+ return;
+ }
+ synchronized(this) {
+ int bufferLength = mBuffer.length;
+ boolean wasEmpty = mStoredBytes == 0;
+ while (length > 0) {
+ while(bufferLength == mStoredBytes) {
+ wait();
+ }
+ int tail = mHead + mStoredBytes;
+ int oneRun;
+ if (tail >= bufferLength) {
+ tail = tail - bufferLength;
+ oneRun = mHead - tail;
+ } else {
+ oneRun = bufferLength - tail;
+ }
+ int bytesToCopy = Math.min(oneRun, length);
+ System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy);
+ offset += bytesToCopy;
+ mStoredBytes += bytesToCopy;
+ length -= bytesToCopy;
+ }
+ if (wasEmpty) {
+ notify();
+ }
+ }
+ }
+
+ private byte[] mBuffer;
+ private int mHead;
+ private int mStoredBytes;
+}
* limitations under the License.
*/
-package jackpal.androidterm;
+package jackpal.androidterm.util;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;