2 * Copyright (C) 2007 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package jackpal.androidterm;
19 import java.io.FileDescriptor;
20 import java.io.FileInputStream;
21 import java.io.FileOutputStream;
22 import java.io.IOException;
23 import java.io.OutputStream;
24 import java.io.UnsupportedEncodingException;
25 import java.util.ArrayList;
27 import android.app.Activity;
28 import android.app.AlertDialog;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.SharedPreferences;
32 import android.content.res.Configuration;
33 import android.content.res.Resources;
34 import android.graphics.Bitmap;
35 import android.graphics.BitmapFactory;
36 import android.graphics.Canvas;
37 import android.graphics.ColorMatrixColorFilter;
38 import android.graphics.Paint;
39 import android.graphics.PorterDuff;
40 import android.graphics.PorterDuffXfermode;
41 import android.graphics.Rect;
42 import android.graphics.Typeface;
43 import android.net.Uri;
44 import android.os.Bundle;
45 import android.os.Handler;
46 import android.os.Message;
47 import android.preference.PreferenceManager;
48 import android.text.ClipboardManager;
49 import android.util.AttributeSet;
50 import android.util.DisplayMetrics;
51 import android.util.Log;
52 import android.view.ContextMenu;
53 import android.view.ContextMenu.ContextMenuInfo;
54 import android.view.GestureDetector;
55 import android.view.KeyEvent;
56 import android.view.Menu;
57 import android.view.MenuItem;
58 import android.view.MotionEvent;
59 import android.view.View;
60 import android.view.Window;
61 import android.view.WindowManager;
62 import android.view.inputmethod.BaseInputConnection;
63 import android.view.inputmethod.EditorInfo;
64 import android.view.inputmethod.InputConnection;
65 import android.view.inputmethod.InputMethodManager;
68 * A terminal emulator activity.
71 public class Term extends Activity {
73 * Set to true to add debugging code and logging.
75 public static final boolean DEBUG = false;
78 * Set to true to log each character received from the remote process to the
79 * android log, which makes it easier to debug some kinds of problems with
80 * emulating escape sequences and control codes.
82 public static final boolean LOG_CHARACTERS_FLAG = DEBUG && false;
85 * Set to true to log unknown escape sequences.
87 public static final boolean LOG_UNKNOWN_ESCAPE_SEQUENCES = DEBUG && false;
90 * The tag we use when logging, so that our messages can be distinguished
91 * from other messages in the log. Public because it's used by several
94 public static final String LOG_TAG = "Term";
97 * Our main view. Displays the emulated terminal screen.
99 private EmulatorView mEmulatorView;
102 * The pseudo-teletype (pty) file descriptor that we use to communicate with
103 * another process, typically a shell.
105 private FileDescriptor mTermFd;
108 * Used to send data to the remote process.
110 private FileOutputStream mTermOut;
113 * A key listener that tracks the modifier keys and allows the full ASCII
114 * character set to be entered.
116 private TermKeyListener mKeyListener;
119 * The name of our emulator view in the view resource.
121 private static final int EMULATOR_VIEW = R.id.emulatorView;
123 private int mStatusBar = 0;
124 private int mCursorStyle = 0;
125 private int mCursorBlink = 0;
126 private int mFontSize = 9;
127 private int mColorId = 2;
128 private int mControlKeyId = 0;
129 private int mUseCookedIME = 0;
131 private static final String STATUSBAR_KEY = "statusbar";
132 private static final String CURSORSTYLE_KEY = "cursorstyle";
133 private static final String CURSORBLINK_KEY = "cursorblink";
134 private static final String FONTSIZE_KEY = "fontsize";
135 private static final String COLOR_KEY = "color";
136 private static final String CONTROLKEY_KEY = "controlkey";
137 private static final String IME_KEY = "ime";
138 private static final String SHELL_KEY = "shell";
139 private static final String INITIALCOMMAND_KEY = "initialcommand";
141 public static final int WHITE = 0xffffffff;
142 public static final int BLACK = 0xff000000;
143 public static final int BLUE = 0xff344ebd;
144 public static final int GREEN = 0xff00ff00;
145 public static final int AMBER = 0xffffb651;
146 public static final int RED = 0xffff0113;
148 private static final int[][] COLOR_SCHEMES = {
149 {BLACK, WHITE}, {WHITE, BLACK}, {WHITE, BLUE}, {GREEN, BLACK}, {AMBER, BLACK}, {RED, BLACK}};
151 private static final int[] CONTROL_KEY_SCHEMES = {
152 KeyEvent.KEYCODE_DPAD_CENTER,
154 KeyEvent.KEYCODE_ALT_LEFT,
155 KeyEvent.KEYCODE_ALT_RIGHT,
156 KeyEvent.KEYCODE_VOLUME_UP,
157 KeyEvent.KEYCODE_VOLUME_DOWN
159 private static final String[] CONTROL_KEY_NAME = {
160 "Ball", "@", "Left-Alt", "Right-Alt", "Vol-Up", "Vol-Dn"
163 private int mControlKeyCode;
165 private final static String DEFAULT_SHELL = "/system/bin/sh -";
166 private String mShell;
168 private final static String DEFAULT_INITIAL_COMMAND =
169 "export PATH=/data/local/bin:$PATH";
170 private String mInitialCommand;
172 private SharedPreferences mPrefs;
174 private final static int SELECT_TEXT_ID = 0;
175 private final static int COPY_ALL_ID = 1;
176 private final static int PASTE_ID = 2;
178 private boolean mAlreadyStarted = false;
181 public void onCreate(Bundle icicle) {
182 super.onCreate(icicle);
183 Log.e(Term.LOG_TAG, "onCreate");
184 mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
187 setContentView(R.layout.term_activity);
189 mEmulatorView = (EmulatorView) findViewById(EMULATOR_VIEW);
191 DisplayMetrics metrics = new DisplayMetrics();
192 getWindowManager().getDefaultDisplay().getMetrics(metrics);
193 mEmulatorView.setScaledDensity(metrics.scaledDensity);
197 mKeyListener = new TermKeyListener();
199 mEmulatorView.setFocusable(true);
200 mEmulatorView.setFocusableInTouchMode(true);
201 mEmulatorView.requestFocus();
202 mEmulatorView.register(this, mKeyListener);
204 registerForContextMenu(mEmulatorView);
207 mAlreadyStarted = true;
211 public void onDestroy() {
213 if (mTermFd != null) {
219 private void startListening() {
220 int[] processId = new int[1];
222 createSubprocess(processId);
223 final int procId = processId[0];
225 final Handler handler = new Handler() {
227 public void handleMessage(Message msg) {
231 Runnable watchForDeath = new Runnable() {
234 Log.i(Term.LOG_TAG, "waiting for: " + procId);
235 int result = Exec.waitFor(procId);
236 Log.i(Term.LOG_TAG, "Subprocess exited: " + result);
237 handler.sendEmptyMessage(result);
241 Thread watcher = new Thread(watchForDeath);
244 mTermOut = new FileOutputStream(mTermFd);
246 mEmulatorView.initialize(mTermFd, mTermOut);
248 sendInitialCommand();
251 private void sendInitialCommand() {
252 String initialCommand = mInitialCommand;
253 if (initialCommand == null || initialCommand.equals("")) {
254 initialCommand = DEFAULT_INITIAL_COMMAND;
256 if (initialCommand.length() > 0) {
257 write(initialCommand + '\r');
261 private void restart() {
262 startActivity(getIntent());
266 private void write(String data) {
268 mTermOut.write(data.getBytes());
270 } catch (IOException e) {
272 // We don't really care if the receiver isn't listening.
273 // We just make a best effort to answer the query.
277 private void createSubprocess(int[] processId) {
278 String shell = mShell;
279 if (shell == null || shell.equals("")) {
280 shell = DEFAULT_SHELL;
282 ArrayList<String> args = parse(shell);
283 String arg0 = args.get(0);
286 if (args.size() >= 2) {
289 if (args.size() >= 3) {
292 mTermFd = Exec.createSubprocess(arg0, arg1, arg2, processId);
295 private ArrayList<String> parse(String cmd) {
297 final int WHITESPACE = 1;
298 final int INQUOTE = 2;
299 int state = WHITESPACE;
300 ArrayList<String> result = new ArrayList<String>();
301 int cmdLen = cmd.length();
302 StringBuilder builder = new StringBuilder();
303 for (int i = 0; i < cmdLen; i++) {
304 char c = cmd.charAt(i);
305 if (state == PLAIN) {
306 if (Character.isWhitespace(c)) {
307 result.add(builder.toString());
308 builder.delete(0,builder.length());
310 } else if (c == '"') {
315 } else if (state == WHITESPACE) {
316 if (Character.isWhitespace(c)) {
318 } else if (c == '"') {
324 } else if (state == INQUOTE) {
326 if (i + 1 < cmdLen) {
328 builder.append(cmd.charAt(i));
330 } else if (c == '"') {
337 if (builder.length() > 0) {
338 result.add(builder.toString());
343 private void readPrefs() {
344 mStatusBar = readIntPref(STATUSBAR_KEY, mStatusBar, 1);
345 // mCursorStyle = readIntPref(CURSORSTYLE_KEY, mCursorStyle, 2);
346 // mCursorBlink = readIntPref(CURSORBLINK_KEY, mCursorBlink, 1);
347 mFontSize = readIntPref(FONTSIZE_KEY, mFontSize, 20);
348 mColorId = readIntPref(COLOR_KEY, mColorId, COLOR_SCHEMES.length - 1);
349 mControlKeyId = readIntPref(CONTROLKEY_KEY, mControlKeyId,
350 CONTROL_KEY_SCHEMES.length - 1);
351 mUseCookedIME = readIntPref(IME_KEY, mUseCookedIME, 1);
353 String newShell = readStringPref(SHELL_KEY, mShell);
354 if ((newShell == null) || ! newShell.equals(mShell)) {
355 if (mShell != null) {
356 Log.i(Term.LOG_TAG, "New shell set. Restarting.");
363 String newInitialCommand = readStringPref(INITIALCOMMAND_KEY,
365 if ((newInitialCommand == null)
366 || ! newInitialCommand.equals(mInitialCommand)) {
367 if (mInitialCommand != null) {
368 Log.i(Term.LOG_TAG, "New initial command set. Restarting.");
371 mInitialCommand = newInitialCommand;
376 private void updatePrefs() {
377 DisplayMetrics metrics = new DisplayMetrics();
378 getWindowManager().getDefaultDisplay().getMetrics(metrics);
379 mEmulatorView.setTextSize((int) (mFontSize * metrics.density));
380 mEmulatorView.setCursorStyle(mCursorStyle, mCursorBlink);
381 mEmulatorView.setUseCookedIME(mUseCookedIME != 0);
383 mControlKeyCode = CONTROL_KEY_SCHEMES[mControlKeyId];
385 Window win = getWindow();
386 WindowManager.LayoutParams params = win.getAttributes();
387 final int FULLSCREEN = WindowManager.LayoutParams.FLAG_FULLSCREEN;
388 int desiredFlag = mStatusBar != 0 ? 0 : FULLSCREEN;
389 if (desiredFlag != (params.flags & FULLSCREEN)) {
390 if (mAlreadyStarted) {
391 // Can't switch to/from fullscreen after
392 // starting the activity.
395 win.setFlags(desiredFlag, FULLSCREEN);
401 private int readIntPref(String key, int defaultValue, int maxValue) {
404 val = Integer.parseInt(
405 mPrefs.getString(key, Integer.toString(defaultValue)));
406 } catch (NumberFormatException e) {
409 val = Math.max(0, Math.min(val, maxValue));
413 private String readStringPref(String key, String defaultValue) {
414 return mPrefs.getString(key, defaultValue);
417 public int getControlKeyCode() {
418 return mControlKeyCode;
422 public void onResume() {
426 mEmulatorView.onResume();
430 public void onPause() {
432 mEmulatorView.onPause();
436 public void onConfigurationChanged(Configuration newConfig) {
437 super.onConfigurationChanged(newConfig);
439 mEmulatorView.updateSize(true);
443 public boolean onCreateOptionsMenu(Menu menu) {
444 getMenuInflater().inflate(R.menu.main, menu);
449 public boolean onOptionsItemSelected(MenuItem item) {
450 int id = item.getItemId();
451 if (id == R.id.menu_preferences) {
453 } else if (id == R.id.menu_reset) {
455 } else if (id == R.id.menu_send_email) {
457 } else if (id == R.id.menu_special_keys) {
459 } else if (id == R.id.menu_toggle_soft_keyboard) {
460 doToggleSoftKeyboard();
462 return super.onOptionsItemSelected(item);
466 public void onCreateContextMenu(ContextMenu menu, View v,
467 ContextMenuInfo menuInfo) {
468 super.onCreateContextMenu(menu, v, menuInfo);
469 menu.setHeaderTitle(R.string.edit_text);
470 menu.add(0, SELECT_TEXT_ID, 0, R.string.select_text);
471 menu.add(0, COPY_ALL_ID, 0, R.string.copy_all);
472 menu.add(0, PASTE_ID, 0, R.string.paste);
474 menu.getItem(PASTE_ID).setEnabled(false);
479 public boolean onContextItemSelected(MenuItem item) {
480 switch (item.getItemId()) {
482 mEmulatorView.toggleSelectingText();
491 return super.onContextItemSelected(item);
495 private boolean canPaste() {
496 ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
497 if (clip.hasText()) {
503 private void doPreferences() {
504 startActivity(new Intent(this, TermPreferences.class));
507 private void setColors() {
508 int[] scheme = COLOR_SCHEMES[mColorId];
509 mEmulatorView.setColors(scheme[0], scheme[1]);
512 private void doResetTerminal() {
516 private void doEmailTranscript() {
517 // Don't really want to supply an address, but
518 // currently it's required, otherwise we get an
520 String addr = "user@example.com";
522 new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"
525 intent.putExtra("body", mEmulatorView.getTranscriptText().trim());
526 startActivity(intent);
529 private void doCopyAll() {
530 ClipboardManager clip = (ClipboardManager)
531 getSystemService(Context.CLIPBOARD_SERVICE);
532 clip.setText(mEmulatorView.getTranscriptText().trim());
535 private void doPaste() {
536 ClipboardManager clip = (ClipboardManager)
537 getSystemService(Context.CLIPBOARD_SERVICE);
538 CharSequence paste = clip.getText();
541 utf8 = paste.toString().getBytes("UTF-8");
542 } catch (UnsupportedEncodingException e) {
543 Log.e(Term.LOG_TAG, "UTF-8 encoding not found.");
547 mTermOut.write(utf8);
548 } catch (IOException e) {
549 Log.e(Term.LOG_TAG, "could not write paste text to terminal.");
553 private void doDocumentKeys() {
554 String controlKey = CONTROL_KEY_NAME[mControlKeyId];
555 new AlertDialog.Builder(this).
556 setTitle("Press " + controlKey + " and Key").
557 setMessage(controlKey + " Space ==> Control-@ (NUL)\n"
558 + controlKey + " A..Z ==> Control-A..Z\n"
559 + controlKey + " 1 ==> Control-[ (ESC)\n"
560 + controlKey + " 5 ==> Control-_\n"
561 + controlKey + " . ==> Control-\\\n"
562 + controlKey + " 0 ==> Control-]\n"
563 + controlKey + " 6 ==> Control-^").
567 private void doToggleSoftKeyboard() {
568 InputMethodManager imm = (InputMethodManager)
569 getSystemService(Context.INPUT_METHOD_SERVICE);
570 imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,0);
577 * An abstract screen interface. A terminal screen stores lines of text. (The
578 * reason to abstract it is to allow different implementations, and to hide
579 * implementation details from clients.)
584 * Set line wrap flag for a given row. Affects how lines are logically
585 * wrapped when changing screen size or converting to a transcript.
587 void setLineWrap(int row);
590 * Store byte b into the screen at location (x, y)
592 * @param x X coordinate (also known as column)
593 * @param y Y coordinate (also known as row)
594 * @param b ASCII character to store
595 * @param foreColor the foreground color
596 * @param backColor the background color
598 void set(int x, int y, byte b, int foreColor, int backColor);
601 * Scroll the screen down one line. To scroll the whole screen of a 24 line
602 * screen, the arguments would be (0, 24).
604 * @param topMargin First line that is scrolled.
605 * @param bottomMargin One line after the last line that is scrolled.
607 void scroll(int topMargin, int bottomMargin, int foreColor, int backColor);
610 * Block copy characters from one position in the screen to another. The two
611 * positions can overlap. All characters of the source and destination must
612 * be within the bounds of the screen, or else an InvalidParemeterException
615 * @param sx source X coordinate
616 * @param sy source Y coordinate
619 * @param dx destination X coordinate
620 * @param dy destination Y coordinate
622 void blockCopy(int sx, int sy, int w, int h, int dx, int dy);
625 * Block set characters. All characters must be within the bounds of the
626 * screen, or else and InvalidParemeterException will be thrown. Typically
627 * this is called with a "val" argument of 32 to clear a block of
634 * @param val value to set.
635 * @param foreColor the foreground color
636 * @param backColor the background color
638 void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int
642 * Get the contents of the transcript buffer as a text string.
644 * @return the contents of the transcript buffer.
646 String getTranscriptText();
649 * Get the selected text inside transcript buffer as a text string.
650 * @param x1 Selection start
651 * @param y1 Selection start
652 * @param x2 Selection end
653 * @param y2 Selection end
654 * @return the contents of the transcript buffer.
656 String getSelectedText(int x1, int y1, int x2, int y2);
663 void resize(int columns, int rows, int foreColor, int backColor);
668 * A TranscriptScreen is a screen that remembers data that's been scrolled. The
669 * old data is stored in a ring buffer to minimize the amount of copying that
670 * needs to be done. The transcript does its own drawing, to avoid having to
671 * expose its internal data structures.
673 class TranscriptScreen implements Screen {
674 private static final String TAG = "TranscriptScreen";
677 * The width of the transcript, in characters. Fixed at initialization.
679 private int mColumns;
682 * The total number of rows in the transcript and the screen. Fixed at
685 private int mTotalRows;
688 * The number of rows in the active portion of the transcript. Doesn't
689 * include the screen.
691 private int mActiveTranscriptRows;
694 * Which row is currently the topmost line of the transcript. Used to
695 * implement a circular buffer.
700 * The number of active rows, includes both the transcript and the screen.
702 private int mActiveRows;
705 * The number of rows in the screen.
707 private int mScreenRows;
710 * The data for both the screen and the transcript. The first mScreenRows *
711 * mLineWidth characters are the screen, the rest are the transcript.
712 * The low byte encodes the ASCII character, the high byte encodes the
713 * foreground and background colors, plus underline and bold.
715 private char[] mData;
718 * The data's stored as color-encoded chars, but the drawing routines require chars, so we
719 * need a temporary buffer to hold a row's worth of characters.
721 private char[] mRowBuffer;
724 * Flags that keep track of whether the current line logically wraps to the
725 * next line. This is used when resizing the screen and when copying to the
726 * clipboard or an email attachment
729 private boolean[] mLineWrap;
732 * Create a transcript screen.
734 * @param columns the width of the screen in characters.
735 * @param totalRows the height of the entire text area, in rows of text.
736 * @param screenRows the height of just the screen, not including the
737 * transcript that holds lines that have scrolled off the top of the
740 public TranscriptScreen(int columns, int totalRows, int screenRows,
741 int foreColor, int backColor) {
742 init(columns, totalRows, screenRows, foreColor, backColor);
745 private void init(int columns, int totalRows, int screenRows, int foreColor, int backColor) {
747 mTotalRows = totalRows;
748 mActiveTranscriptRows = 0;
750 mActiveRows = screenRows;
751 mScreenRows = screenRows;
752 int totalSize = columns * totalRows;
753 mData = new char[totalSize];
754 blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor);
755 mRowBuffer = new char[columns];
756 mLineWrap = new boolean[totalRows];
761 * Convert a row value from the public external coordinate system to our
762 * internal private coordinate system. External coordinate system:
763 * -mActiveTranscriptRows to mScreenRows-1, with the screen being
764 * 0..mScreenRows-1 Internal coordinate system: 0..mScreenRows-1 rows of
765 * mData are the visible rows. mScreenRows..mActiveRows - 1 are the
766 * transcript, stored as a circular buffer.
768 * @param row a row in the external coordinate system.
769 * @return The row corresponding to the input argument in the private
772 private int externalToInternalRow(int row) {
773 if (row < -mActiveTranscriptRows || row >= mScreenRows) {
774 String errorMessage = "externalToInternalRow "+ row +
775 " " + mActiveTranscriptRows + " " + mScreenRows;
776 Log.e(TAG, errorMessage);
777 throw new IllegalArgumentException(errorMessage);
780 return row; // This is a visible row.
783 + ((mHead + mActiveTranscriptRows + row) % mActiveTranscriptRows);
786 private int getOffset(int externalLine) {
787 return externalToInternalRow(externalLine) * mColumns;
790 private int getOffset(int x, int y) {
791 return getOffset(y) + x;
794 public void setLineWrap(int row) {
795 mLineWrap[externalToInternalRow(row)] = true;
799 * Store byte b into the screen at location (x, y)
801 * @param x X coordinate (also known as column)
802 * @param y Y coordinate (also known as row)
803 * @param b ASCII character to store
804 * @param foreColor the foreground color
805 * @param backColor the background color
807 public void set(int x, int y, byte b, int foreColor, int backColor) {
808 mData[getOffset(x, y)] = encode(b, foreColor, backColor);
811 private char encode(int b, int foreColor, int backColor) {
812 return (char) ((foreColor << 12) | (backColor << 8) | b);
816 * Scroll the screen down one line. To scroll the whole screen of a 24 line
817 * screen, the arguments would be (0, 24).
819 * @param topMargin First line that is scrolled.
820 * @param bottomMargin One line after the last line that is scrolled.
822 public void scroll(int topMargin, int bottomMargin, int foreColor,
824 // Separate out reasons so that stack crawls help us
825 // figure out which condition was violated.
826 if (topMargin > bottomMargin - 1) {
827 throw new IllegalArgumentException();
830 if (topMargin > mScreenRows - 1) {
831 throw new IllegalArgumentException();
834 if (bottomMargin > mScreenRows) {
835 throw new IllegalArgumentException();
838 // Adjust the transcript so that the last line of the transcript
839 // is ready to receive the newly scrolled data
841 int expansionRows = Math.min(1, mTotalRows - mActiveRows);
842 int rollRows = 1 - expansionRows;
843 mActiveRows += expansionRows;
844 mActiveTranscriptRows += expansionRows;
845 if (mActiveTranscriptRows > 0) {
846 mHead = (mHead + rollRows) % mActiveTranscriptRows;
850 // Block move the scroll line to the transcript
851 int topOffset = getOffset(topMargin);
852 int destOffset = getOffset(-1);
853 System.arraycopy(mData, topOffset, mData, destOffset, mColumns);
855 int topLine = externalToInternalRow(topMargin);
856 int destLine = externalToInternalRow(-1);
857 System.arraycopy(mLineWrap, topLine, mLineWrap, destLine, 1);
859 // Block move the scrolled data up
860 int numScrollChars = (bottomMargin - topMargin - 1) * mColumns;
861 System.arraycopy(mData, topOffset + mColumns, mData, topOffset,
863 int numScrollLines = (bottomMargin - topMargin - 1);
864 System.arraycopy(mLineWrap, topLine + 1, mLineWrap, topLine,
867 // Erase the bottom line of the scroll region
868 blockSet(0, bottomMargin - 1, mColumns, 1, ' ', foreColor, backColor);
869 mLineWrap[externalToInternalRow(bottomMargin-1)] = false;
872 private void consistencyCheck() {
873 checkPositive(mColumns);
874 checkPositive(mTotalRows);
875 checkRange(0, mActiveTranscriptRows, mTotalRows);
876 if (mActiveTranscriptRows == 0) {
877 checkEqual(mHead, 0);
879 checkRange(0, mHead, mActiveTranscriptRows-1);
881 checkEqual(mScreenRows + mActiveTranscriptRows, mActiveRows);
882 checkRange(0, mScreenRows, mTotalRows);
884 checkEqual(mTotalRows, mLineWrap.length);
885 checkEqual(mTotalRows*mColumns, mData.length);
886 checkEqual(mColumns, mRowBuffer.length);
889 private void checkPositive(int n) {
891 throw new IllegalArgumentException("checkPositive " + n);
895 private void checkRange(int a, int b, int c) {
896 if (a > b || b > c) {
897 throw new IllegalArgumentException("checkRange " + a + " <= " + b + " <= " + c);
901 private void checkEqual(int a, int b) {
903 throw new IllegalArgumentException("checkEqual " + a + " == " + b);
908 * Block copy characters from one position in the screen to another. The two
909 * positions can overlap. All characters of the source and destination must
910 * be within the bounds of the screen, or else an InvalidParemeterException
913 * @param sx source X coordinate
914 * @param sy source Y coordinate
917 * @param dx destination X coordinate
918 * @param dy destination Y coordinate
920 public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
921 if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows
922 || dx < 0 || dx + w > mColumns || dy < 0
923 || dy + h > mScreenRows) {
924 throw new IllegalArgumentException();
927 // Move in increasing order
928 for (int y = 0; y < h; y++) {
929 int srcOffset = getOffset(sx, sy + y);
930 int dstOffset = getOffset(dx, dy + y);
931 System.arraycopy(mData, srcOffset, mData, dstOffset, w);
934 // Move in decreasing order
935 for (int y = 0; y < h; y++) {
936 int y2 = h - (y + 1);
937 int srcOffset = getOffset(sx, sy + y2);
938 int dstOffset = getOffset(dx, dy + y2);
939 System.arraycopy(mData, srcOffset, mData, dstOffset, w);
945 * Block set characters. All characters must be within the bounds of the
946 * screen, or else and InvalidParemeterException will be thrown. Typically
947 * this is called with a "val" argument of 32 to clear a block of
954 * @param val value to set.
956 public void blockSet(int sx, int sy, int w, int h, int val,
957 int foreColor, int backColor) {
958 if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
959 throw new IllegalArgumentException();
962 char encodedVal = encode(val, foreColor, backColor);
963 for (int y = 0; y < h; y++) {
964 int offset = getOffset(sx, sy + y);
965 for (int x = 0; x < w; x++) {
966 data[offset + x] = encodedVal;
972 * Draw a row of text. Out-of-bounds rows are blank, not errors.
974 * @param row The row of text to draw.
975 * @param canvas The canvas to draw to.
976 * @param x The x coordinate origin of the drawing
977 * @param y The y coordinate origin of the drawing
978 * @param renderer The renderer to use to draw the text
979 * @param cx the cursor X coordinate, -1 means don't draw it
980 * @param selx1 the text selection start X coordinate
981 * @param selx2 the text selection end X coordinate, if equals to selx1 don't draw selection
983 public final void drawText(int row, Canvas canvas, float x, float y,
984 TextRenderer renderer, int cx, int selx1, int selx2) {
986 // Out-of-bounds rows are blank.
987 if (row < -mActiveTranscriptRows || row >= mScreenRows) {
991 // Copy the data from the byte array to a char array so they can
994 int offset = getOffset(row);
995 char[] rowBuffer = mRowBuffer;
997 int columns = mColumns;
999 int lastRunStart = -1;
1000 final int CURSOR_MASK = 0x10000;
1001 for (int i = 0; i < columns; i++) {
1002 char c = data[offset + i];
1003 int colors = (char) (c & 0xff00);
1004 if (cx == i || (i >= selx1 && i <= selx2)) {
1005 // Set cursor background color:
1006 colors |= CURSOR_MASK;
1008 rowBuffer[i] = (char) (c & 0x00ff);
1009 if (colors != lastColors) {
1010 if (lastRunStart >= 0) {
1011 renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
1012 lastRunStart, i - lastRunStart,
1013 (lastColors & CURSOR_MASK) != 0,
1014 0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
1016 lastColors = colors;
1020 if (lastRunStart >= 0) {
1021 renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
1022 lastRunStart, columns - lastRunStart,
1023 (lastColors & CURSOR_MASK) != 0,
1024 0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
1029 * Get the count of active rows.
1031 * @return the count of active rows.
1033 public int getActiveRows() {
1038 * Get the count of active transcript rows.
1040 * @return the count of active transcript rows.
1042 public int getActiveTranscriptRows() {
1043 return mActiveTranscriptRows;
1046 public String getTranscriptText() {
1047 return internalGetTranscriptText(true, 0, -mActiveTranscriptRows, mColumns, mScreenRows);
1050 public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
1051 return internalGetTranscriptText(true, selX1, selY1, selX2, selY2);
1054 private String internalGetTranscriptText(boolean stripColors, int selX1, int selY1, int selX2, int selY2) {
1055 StringBuilder builder = new StringBuilder();
1056 char[] rowBuffer = mRowBuffer;
1057 char[] data = mData;
1058 int columns = mColumns;
1059 for (int row = -mActiveTranscriptRows; row < mScreenRows; row++) {
1060 int offset = getOffset(row);
1061 int lastPrintingChar = -1;
1062 for (int column = 0; column < columns; column++) {
1063 char c = data[offset + column];
1065 c = (char) (c & 0xff);
1067 if ((c & 0xff) != ' ') {
1068 lastPrintingChar = column;
1070 rowBuffer[column] = c;
1072 if ( row >= selY1 && row <= selY2 ) {
1075 if ( row == selY1 ) {
1078 if ( row == selY2 ) {
1083 if (mLineWrap[externalToInternalRow(row)]) {
1084 builder.append(rowBuffer, x1, x2 - x1);
1086 builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1)));
1087 builder.append('\n');
1091 return builder.toString();
1094 public void resize(int columns, int rows, int foreColor, int backColor) {
1095 init(columns, mTotalRows, rows, foreColor, backColor);
1100 * Renders text into a screen. Contains all the terminal-specific knowlege and
1101 * state. Emulates a subset of the X Window System xterm terminal, which in turn
1102 * is an emulator for a subset of the Digital Equipment Corporation vt100
1103 * terminal. Missing functionality: text attributes (bold, underline, reverse
1104 * video, color) alternate screen cursor key and keypad escape sequences.
1106 class TerminalEmulator {
1109 * The cursor row. Numbered 0..mRows-1.
1111 private int mCursorRow;
1114 * The cursor column. Numbered 0..mColumns-1.
1116 private int mCursorCol;
1119 * The number of character rows in the terminal screen.
1124 * The number of character columns in the terminal screen.
1126 private int mColumns;
1129 * Used to send data to the remote process. Needed to implement the various
1130 * "report" escape sequences.
1132 private FileOutputStream mTermOut;
1135 * Stores the characters that appear on the screen of the emulated terminal.
1137 private Screen mScreen;
1140 * Keeps track of the current argument of the current escape sequence.
1141 * Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. (Typically just 0 or 1.)
1143 private int mArgIndex;
1146 * The number of parameter arguments. This name comes from the ANSI standard
1147 * for terminal escape codes.
1149 private static final int MAX_ESCAPE_PARAMETERS = 16;
1152 * Holds the arguments of the current escape sequence.
1154 private int[] mArgs = new int[MAX_ESCAPE_PARAMETERS];
1156 // Escape processing states:
1159 * Escape processing state: Not currently in an escape sequence.
1161 private static final int ESC_NONE = 0;
1164 * Escape processing state: Have seen an ESC character
1166 private static final int ESC = 1;
1169 * Escape processing state: Have seen ESC POUND
1171 private static final int ESC_POUND = 2;
1174 * Escape processing state: Have seen ESC and a character-set-select char
1176 private static final int ESC_SELECT_LEFT_PAREN = 3;
1179 * Escape processing state: Have seen ESC and a character-set-select char
1181 private static final int ESC_SELECT_RIGHT_PAREN = 4;
1184 * Escape processing state: ESC [
1186 private static final int ESC_LEFT_SQUARE_BRACKET = 5;
1189 * Escape processing state: ESC [ ?
1191 private static final int ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK = 6;
1194 * True if the current escape sequence should continue, false if the current
1195 * escape sequence should be terminated. Used when parsing a single
1198 private boolean mContinueSequence;
1201 * The current state of the escape sequence state machine.
1203 private int mEscapeState;
1206 * Saved state of the cursor row, Used to implement the save/restore cursor
1207 * position escape sequences.
1209 private int mSavedCursorRow;
1212 * Saved state of the cursor column, Used to implement the save/restore
1213 * cursor position escape sequences.
1215 private int mSavedCursorCol;
1220 * This mask indicates 132-column mode is set. (As opposed to 80-column
1223 private static final int K_132_COLUMN_MODE_MASK = 1 << 3;
1226 * This mask indicates that origin mode is set. (Cursor addressing is
1227 * relative to the absolute screen size, rather than the currently set top
1228 * and bottom margins.)
1230 private static final int K_ORIGIN_MODE_MASK = 1 << 6;
1233 * This mask indicates that wraparound mode is set. (As opposed to
1234 * stop-at-right-column mode.)
1236 private static final int K_WRAPAROUND_MODE_MASK = 1 << 7;
1239 * Holds multiple DECSET flags. The data is stored this way, rather than in
1240 * separate booleans, to make it easier to implement the save-and-restore
1241 * semantics. The various k*ModeMask masks can be used to extract and modify
1242 * the individual flags current states.
1244 private int mDecFlags;
1247 * Saves away a snapshot of the DECSET flags. Used to implement save and
1248 * restore escape sequences.
1250 private int mSavedDecFlags;
1252 // Modes set with Set Mode / Reset Mode
1255 * True if insert mode (as opposed to replace mode) is active. In insert
1256 * mode new characters are inserted, pushing existing text to the right.
1258 private boolean mInsertMode;
1261 * Automatic newline mode. Configures whether pressing return on the
1262 * keyboard automatically generates a return as well. Not currently
1265 private boolean mAutomaticNewlineMode;
1268 * An array of tab stops. mTabStop[i] is true if there is a tab stop set for
1271 private boolean[] mTabStop;
1273 // The margins allow portions of the screen to be locked.
1276 * The top margin of the screen, for scrolling purposes. Ranges from 0 to
1279 private int mTopMargin;
1282 * The bottom margin of the screen, for scrolling purposes. Ranges from
1283 * mTopMargin + 2 to mRows. (Defines the first row after the scrolling
1286 private int mBottomMargin;
1289 * True if the next character to be emitted will be automatically wrapped to
1290 * the next line. Used to disambiguate the case where the cursor is
1291 * positioned on column mColumns-1.
1293 private boolean mAboutToAutoWrap;
1296 * Used for debugging, counts how many chars have been processed.
1298 private int mProcessedCharCount;
1301 * Foreground color, 0..7, mask with 8 for bold
1303 private int mForeColor;
1306 * Background color, 0..7, mask with 8 for underline
1308 private int mBackColor;
1310 private boolean mInverseColors;
1312 private boolean mbKeypadApplicationMode;
1314 private boolean mAlternateCharSet;
1317 * Used for moving selection up along with the scrolling text
1319 private int mScrollCounter = 0;
1322 * Construct a terminal emulator that uses the supplied screen
1324 * @param screen the screen to render characters into.
1325 * @param columns the number of columns to emulate
1326 * @param rows the number of rows to emulate
1327 * @param termOut the output file descriptor that talks to the pseudo-tty.
1329 public TerminalEmulator(Screen screen, int columns, int rows,
1330 FileOutputStream termOut) {
1334 mTabStop = new boolean[mColumns];
1339 public void updateSize(int columns, int rows) {
1340 if (mRows == rows && mColumns == columns) {
1344 throw new IllegalArgumentException("rows:" + columns);
1348 throw new IllegalArgumentException("rows:" + rows);
1351 String transcriptText = mScreen.getTranscriptText();
1353 mScreen.resize(columns, rows, mForeColor, mBackColor);
1355 if (mRows != rows) {
1358 mBottomMargin = mRows;
1360 if (mColumns != columns) {
1361 int oldColumns = mColumns;
1363 boolean[] oldTabStop = mTabStop;
1364 mTabStop = new boolean[mColumns];
1365 int toTransfer = Math.min(oldColumns, columns);
1366 System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer);
1367 while (mCursorCol >= columns) {
1368 mCursorCol -= columns;
1369 mCursorRow = Math.min(mBottomMargin-1, mCursorRow + 1);
1374 mAboutToAutoWrap = false;
1376 int end = transcriptText.length()-1;
1377 while ((end >= 0) && transcriptText.charAt(end) == '\n') {
1380 for(int i = 0; i <= end; i++) {
1381 byte c = (byte) transcriptText.charAt(i);
1392 * Get the cursor's current row.
1394 * @return the cursor's current row.
1396 public final int getCursorRow() {
1401 * Get the cursor's current column.
1403 * @return the cursor's current column.
1405 public final int getCursorCol() {
1409 public final boolean getKeypadApplicationMode() {
1410 return mbKeypadApplicationMode;
1413 private void setDefaultTabStops() {
1414 for (int i = 0; i < mColumns; i++) {
1415 mTabStop[i] = (i & 7) == 0 && i != 0;
1420 * Accept bytes (typically from the pseudo-teletype) and process them.
1422 * @param buffer a byte array containing the bytes to be processed
1423 * @param base the first index of the array to process
1424 * @param length the number of bytes in the array to process
1426 public void append(byte[] buffer, int base, int length) {
1427 for (int i = 0; i < length; i++) {
1428 byte b = buffer[base + i];
1430 if (Term.LOG_CHARACTERS_FLAG) {
1431 char printableB = (char) b;
1432 if (b < 32 || b > 126) {
1435 Log.w(Term.LOG_TAG, "'" + Character.toString(printableB)
1436 + "' (" + Integer.toString(b) + ")");
1439 mProcessedCharCount++;
1440 } catch (Exception e) {
1441 Log.e(Term.LOG_TAG, "Exception while processing character "
1442 + Integer.toString(mProcessedCharCount) + " code "
1443 + Integer.toString(b), e);
1448 private void process(byte b) {
1459 setCursorCol(Math.max(0, mCursorCol - 1));
1463 // Move to next tab stop, but not past edge of screen
1464 setCursorCol(nextTabStop(mCursorCol));
1478 setAltCharSet(true);
1482 setAltCharSet(false);
1488 if (mEscapeState != ESC_NONE) {
1489 mEscapeState = ESC_NONE;
1495 // Always starts an escape sequence
1496 startEscapeSequence(ESC);
1499 case (byte) 0x9b: // CSI
1500 startEscapeSequence(ESC_LEFT_SQUARE_BRACKET);
1504 mContinueSequence = false;
1505 switch (mEscapeState) {
1520 case ESC_SELECT_LEFT_PAREN:
1521 doEscSelectLeftParen(b);
1524 case ESC_SELECT_RIGHT_PAREN:
1525 doEscSelectRightParen(b);
1528 case ESC_LEFT_SQUARE_BRACKET:
1529 doEscLeftSquareBracket(b);
1532 case ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK:
1540 if (!mContinueSequence) {
1541 mEscapeState = ESC_NONE;
1547 private void setAltCharSet(boolean alternateCharSet) {
1548 mAlternateCharSet = alternateCharSet;
1551 private int nextTabStop(int cursorCol) {
1552 for (int i = cursorCol; i < mColumns; i++) {
1557 return mColumns - 1;
1560 private void doEscLSBQuest(byte b) {
1561 int mask = getDecFlagsMask(getArg0(0));
1563 case 'h': // Esc [ ? Pn h - DECSET
1567 case 'l': // Esc [ ? Pn l - DECRST
1571 case 'r': // Esc [ ? Pn r - restore
1572 mDecFlags = (mDecFlags & ~mask) | (mSavedDecFlags & mask);
1575 case 's': // Esc [ ? Pn s - save
1576 mSavedDecFlags = (mSavedDecFlags & ~mask) | (mDecFlags & mask);
1585 if ((mask & K_132_COLUMN_MODE_MASK) != 0) {
1586 // We don't actually set 132 cols, but we do want the
1587 // side effect of clearing the screen and homing the cursor.
1588 blockClear(0, 0, mColumns, mRows);
1589 setCursorRowCol(0, 0);
1593 if ((mask & K_ORIGIN_MODE_MASK) != 0) {
1595 setCursorPosition(0, 0);
1599 private int getDecFlagsMask(int argument) {
1600 if (argument >= 1 && argument <= 9) {
1601 return (1 << argument);
1607 private void startEscapeSequence(int escapeState) {
1608 mEscapeState = escapeState;
1610 for (int j = 0; j < MAX_ESCAPE_PARAMETERS; j++) {
1615 private void doLinefeed() {
1616 int newCursorRow = mCursorRow + 1;
1617 if (newCursorRow >= mBottomMargin) {
1619 newCursorRow = mBottomMargin - 1;
1621 setCursorRow(newCursorRow);
1624 private void continueSequence() {
1625 mContinueSequence = true;
1628 private void continueSequence(int state) {
1629 mEscapeState = state;
1630 mContinueSequence = true;
1633 private void doEscSelectLeftParen(byte b) {
1634 doSelectCharSet(true, b);
1637 private void doEscSelectRightParen(byte b) {
1638 doSelectCharSet(false, b);
1641 private void doSelectCharSet(boolean isG0CharSet, byte b) {
1643 case 'A': // United Kingdom character set
1645 case 'B': // ASCII set
1647 case '0': // Special Graphics
1649 case '1': // Alternate character set
1658 private void doEscPound(byte b) {
1660 case '8': // Esc # 8 - DECALN alignment test
1661 mScreen.blockSet(0, 0, mColumns, mRows, 'E',
1662 getForeColor(), getBackColor());
1671 private void doEsc(byte b) {
1674 continueSequence(ESC_POUND);
1678 continueSequence(ESC_SELECT_LEFT_PAREN);
1682 continueSequence(ESC_SELECT_RIGHT_PAREN);
1685 case '7': // DECSC save cursor
1686 mSavedCursorRow = mCursorRow;
1687 mSavedCursorCol = mCursorCol;
1690 case '8': // DECRC restore cursor
1691 setCursorRowCol(mSavedCursorRow, mSavedCursorCol);
1703 case 'F': // Cursor to lower-left corner of screen
1704 setCursorRowCol(0, mBottomMargin - 1);
1707 case 'H': // Tab set
1708 mTabStop[mCursorCol] = true;
1711 case 'M': // Reverse index
1712 if (mCursorRow == 0) {
1713 mScreen.blockCopy(0, mTopMargin + 1, mColumns, mBottomMargin
1714 - (mTopMargin + 1), 0, mTopMargin);
1715 blockClear(0, mBottomMargin - 1, mColumns);
1723 unimplementedSequence(b);
1727 unimplementedSequence(b);
1730 case 'P': // Device control string
1731 unimplementedSequence(b);
1734 case 'Z': // return terminal ID
1735 sendDeviceAttributes();
1739 continueSequence(ESC_LEFT_SQUARE_BRACKET);
1742 case '=': // DECKPAM
1743 mbKeypadApplicationMode = true;
1746 case '>' : // DECKPNM
1747 mbKeypadApplicationMode = false;
1756 private void doEscLeftSquareBracket(byte b) {
1758 case '@': // ESC [ Pn @ - ICH Insert Characters
1760 int charsAfterCursor = mColumns - mCursorCol;
1761 int charsToInsert = Math.min(getArg0(1), charsAfterCursor);
1762 int charsToMove = charsAfterCursor - charsToInsert;
1763 mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1,
1764 mCursorCol + charsToInsert, mCursorRow);
1765 blockClear(mCursorCol, mCursorRow, charsToInsert);
1769 case 'A': // ESC [ Pn A - Cursor Up
1770 setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1)));
1773 case 'B': // ESC [ Pn B - Cursor Down
1774 setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1)));
1777 case 'C': // ESC [ Pn C - Cursor Right
1778 setCursorCol(Math.min(mColumns - 1, mCursorCol + getArg0(1)));
1781 case 'D': // ESC [ Pn D - Cursor Left
1782 setCursorCol(Math.max(0, mCursorCol - getArg0(1)));
1785 case 'G': // ESC [ Pn G - Cursor Horizontal Absolute
1786 setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1);
1789 case 'H': // ESC [ Pn ; H - Cursor Position
1790 setHorizontalVerticalPosition();
1793 case 'J': // ESC [ Pn J - Erase in Display
1794 switch (getArg0(0)) {
1795 case 0: // Clear below
1796 blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
1797 blockClear(0, mCursorRow + 1, mColumns,
1798 mBottomMargin - (mCursorRow + 1));
1801 case 1: // Erase from the start of the screen to the cursor.
1802 blockClear(0, mTopMargin, mColumns, mCursorRow - mTopMargin);
1803 blockClear(0, mCursorRow, mCursorCol + 1);
1806 case 2: // Clear all
1807 blockClear(0, mTopMargin, mColumns, mBottomMargin - mTopMargin);
1816 case 'K': // ESC [ Pn K - Erase in Line
1817 switch (getArg0(0)) {
1818 case 0: // Clear to right
1819 blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
1822 case 1: // Erase start of line to cursor (including cursor)
1823 blockClear(0, mCursorRow, mCursorCol + 1);
1826 case 2: // Clear whole line
1827 blockClear(0, mCursorRow, mColumns);
1836 case 'L': // Insert Lines
1838 int linesAfterCursor = mBottomMargin - mCursorRow;
1839 int linesToInsert = Math.min(getArg0(1), linesAfterCursor);
1840 int linesToMove = linesAfterCursor - linesToInsert;
1841 mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0,
1842 mCursorRow + linesToInsert);
1843 blockClear(0, mCursorRow, mColumns, linesToInsert);
1847 case 'M': // Delete Lines
1849 int linesAfterCursor = mBottomMargin - mCursorRow;
1850 int linesToDelete = Math.min(getArg0(1), linesAfterCursor);
1851 int linesToMove = linesAfterCursor - linesToDelete;
1852 mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns,
1853 linesToMove, 0, mCursorRow);
1854 blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete);
1858 case 'P': // Delete Characters
1860 int charsAfterCursor = mColumns - mCursorCol;
1861 int charsToDelete = Math.min(getArg0(1), charsAfterCursor);
1862 int charsToMove = charsAfterCursor - charsToDelete;
1863 mScreen.blockCopy(mCursorCol + charsToDelete, mCursorRow,
1864 charsToMove, 1, mCursorCol, mCursorRow);
1865 blockClear(mCursorCol + charsToMove, mCursorRow, charsToDelete);
1869 case 'T': // Mouse tracking
1870 unimplementedSequence(b);
1873 case '?': // Esc [ ? -- start of a private mode set
1874 continueSequence(ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK);
1877 case 'c': // Send device attributes
1878 sendDeviceAttributes();
1881 case 'd': // ESC [ Pn d - Vert Position Absolute
1882 setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1);
1885 case 'f': // Horizontal and Vertical Position
1886 setHorizontalVerticalPosition();
1889 case 'g': // Clear tab stop
1890 switch (getArg0(0)) {
1892 mTabStop[mCursorCol] = false;
1896 for (int i = 0; i < mColumns; i++) {
1897 mTabStop[i] = false;
1902 // Specified to have no effect.
1907 case 'h': // Set Mode
1911 case 'l': // Reset Mode
1915 case 'm': // Esc [ Pn m - character attributes.
1916 selectGraphicRendition();
1919 case 'r': // Esc [ Pn ; Pn r - set top and bottom margins
1921 // The top margin defaults to 1, the bottom margin
1922 // (unusually for arguments) defaults to mRows.
1924 // The escape sequence numbers top 1..23, but we
1925 // number top 0..22.
1926 // The escape sequence numbers bottom 2..24, and
1927 // so do we (because we use a zero based numbering
1928 // scheme, but we store the first line below the
1929 // bottom-most scrolling line.
1930 // As a result, we adjust the top line by -1, but
1931 // we leave the bottom line alone.
1933 // Also require that top + 2 <= bottom
1935 int top = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
1936 int bottom = Math.max(top + 2, Math.min(getArg1(mRows), mRows));
1938 mBottomMargin = bottom;
1940 // The cursor is placed in the home position
1941 setCursorRowCol(mTopMargin, 0);
1951 private void selectGraphicRendition() {
1952 for (int i = 0; i <= mArgIndex; i++) {
1953 int code = mArgs[i];
1955 if (mArgIndex > 0) {
1961 if (code == 0) { // reset
1962 mInverseColors = false;
1965 } else if (code == 1) { // bold
1967 } else if (code == 4) { // underscore
1969 } else if (code == 7) { // inverse
1970 mInverseColors = true;
1971 } else if (code >= 30 && code <= 37) { // foreground color
1972 mForeColor = (mForeColor & 0x8) | (code - 30);
1973 } else if (code >= 40 && code <= 47) { // background color
1974 mBackColor = (mBackColor & 0x8) | (code - 40);
1976 if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
1977 Log.w(Term.LOG_TAG, String.format("SGR unknown code %d", code));
1983 private void blockClear(int sx, int sy, int w) {
1984 blockClear(sx, sy, w, 1);
1987 private void blockClear(int sx, int sy, int w, int h) {
1988 mScreen.blockSet(sx, sy, w, h, ' ', getForeColor(), getBackColor());
1991 private int getForeColor() {
1992 return mInverseColors ?
1993 ((mBackColor & 0x7) | (mForeColor & 0x8)) : mForeColor;
1996 private int getBackColor() {
1997 return mInverseColors ?
1998 ((mForeColor & 0x7) | (mBackColor & 0x8)) : mBackColor;
2001 private void doSetMode(boolean newValue) {
2002 int modeBit = getArg0(0);
2005 mInsertMode = newValue;
2009 mAutomaticNewlineMode = newValue;
2013 unknownParameter(modeBit);
2018 private void setHorizontalVerticalPosition() {
2020 // Parameters are Row ; Column
2022 setCursorPosition(getArg1(1) - 1, getArg0(1) - 1);
2025 private void setCursorPosition(int x, int y) {
2026 int effectiveTopMargin = 0;
2027 int effectiveBottomMargin = mRows;
2028 if ((mDecFlags & K_ORIGIN_MODE_MASK) != 0) {
2029 effectiveTopMargin = mTopMargin;
2030 effectiveBottomMargin = mBottomMargin;
2033 Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y,
2034 effectiveBottomMargin - 1));
2035 int newCol = Math.max(0, Math.min(x, mColumns - 1));
2036 setCursorRowCol(newRow, newCol);
2039 private void sendDeviceAttributes() {
2040 // This identifies us as a DEC vt100 with advanced
2041 // video options. This is what the xterm terminal
2046 (byte) 27, (byte) '[', (byte) '?', (byte) '1',
2047 (byte) ';', (byte) '2', (byte) 'c'
2050 (byte) 27, (byte) '[', (byte) '?', (byte) '6',
2051 (byte) '0', (byte) ';',
2052 (byte) '1', (byte) ';',
2053 (byte) '2', (byte) ';',
2054 (byte) '6', (byte) ';',
2055 (byte) '8', (byte) ';',
2056 (byte) '9', (byte) ';',
2057 (byte) '1', (byte) '5', (byte) ';',
2066 * Send data to the shell process
2069 private void write(byte[] data) {
2071 mTermOut.write(data);
2073 } catch (IOException e) {
2075 // We don't really care if the receiver isn't listening.
2076 // We just make a best effort to answer the query.
2080 private void scroll() {
2081 //System.out.println("Scroll(): mTopMargin " + mTopMargin + " mBottomMargin " + mBottomMargin);
2083 mScreen.scroll(mTopMargin, mBottomMargin,
2084 getForeColor(), getBackColor());
2088 * Process the next ASCII character of a parameter.
2090 * @param b The next ASCII character of the paramater sequence.
2092 private void parseArg(byte b) {
2093 if (b >= '0' && b <= '9') {
2094 if (mArgIndex < mArgs.length) {
2095 int oldValue = mArgs[mArgIndex];
2096 int thisDigit = b - '0';
2098 if (oldValue >= 0) {
2099 value = oldValue * 10 + thisDigit;
2103 mArgs[mArgIndex] = value;
2106 } else if (b == ';') {
2107 if (mArgIndex < mArgs.length) {
2116 private int getArg0(int defaultValue) {
2117 return getArg(0, defaultValue);
2120 private int getArg1(int defaultValue) {
2121 return getArg(1, defaultValue);
2124 private int getArg(int index, int defaultValue) {
2125 int result = mArgs[index];
2127 result = defaultValue;
2132 private void unimplementedSequence(byte b) {
2133 if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
2134 logError("unimplemented", b);
2139 private void unknownSequence(byte b) {
2140 if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
2141 logError("unknown", b);
2146 private void unknownParameter(int parameter) {
2147 if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
2148 StringBuilder buf = new StringBuilder();
2149 buf.append("Unknown parameter");
2150 buf.append(parameter);
2151 logError(buf.toString());
2155 private void logError(String errorType, byte b) {
2156 if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
2157 StringBuilder buf = new StringBuilder();
2158 buf.append(errorType);
2159 buf.append(" sequence ");
2160 buf.append(" EscapeState: ");
2161 buf.append(mEscapeState);
2162 buf.append(" char: '");
2163 buf.append((char) b);
2167 boolean firstArg = true;
2168 for (int i = 0; i <= mArgIndex; i++) {
2169 int value = mArgs[i];
2173 buf.append("args = ");
2175 buf.append(String.format("%d; ", value));
2178 logError(buf.toString());
2182 private void logError(String error) {
2183 if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
2184 Log.e(Term.LOG_TAG, error);
2189 private void finishSequence() {
2190 mEscapeState = ESC_NONE;
2193 private boolean autoWrapEnabled() {
2194 // Always enable auto wrap, because it's useful on a small screen
2196 // return (mDecFlags & K_WRAPAROUND_MODE_MASK) != 0;
2200 * Send an ASCII character to the screen.
2202 * @param b the ASCII character to display.
2204 private void emit(byte b) {
2205 boolean autoWrap = autoWrapEnabled();
2208 if (mCursorCol == mColumns - 1 && mAboutToAutoWrap) {
2209 mScreen.setLineWrap(mCursorRow);
2211 if (mCursorRow + 1 < mBottomMargin) {
2219 if (mInsertMode) { // Move character to right one space
2220 int destCol = mCursorCol + 1;
2221 if (destCol < mColumns) {
2222 mScreen.blockCopy(mCursorCol, mCursorRow, mColumns - destCol,
2223 1, destCol, mCursorRow);
2227 mScreen.set(mCursorCol, mCursorRow, b, getForeColor(), getBackColor());
2230 mAboutToAutoWrap = (mCursorCol == mColumns - 1);
2233 mCursorCol = Math.min(mCursorCol + 1, mColumns - 1);
2236 private void setCursorRow(int row) {
2238 mAboutToAutoWrap = false;
2241 private void setCursorCol(int col) {
2243 mAboutToAutoWrap = false;
2246 private void setCursorRowCol(int row, int col) {
2247 mCursorRow = Math.min(row, mRows-1);
2248 mCursorCol = Math.min(col, mColumns-1);
2249 mAboutToAutoWrap = false;
2252 public int getScrollCounter() {
2253 return mScrollCounter;
2256 public void clearScrollCounter() {
2261 * Reset the terminal emulator to its initial state.
2263 public void reset() {
2267 mContinueSequence = false;
2268 mEscapeState = ESC_NONE;
2269 mSavedCursorRow = 0;
2270 mSavedCursorCol = 0;
2273 mInsertMode = false;
2274 mAutomaticNewlineMode = false;
2276 mBottomMargin = mRows;
2277 mAboutToAutoWrap = false;
2280 mInverseColors = false;
2281 mbKeypadApplicationMode = false;
2282 mAlternateCharSet = false;
2283 // mProcessedCharCount is preserved unchanged.
2284 setDefaultTabStops();
2285 blockClear(0, 0, mColumns, mRows);
2288 public String getTranscriptText() {
2289 return mScreen.getTranscriptText();
2291 public String getSelectedText(int x1, int y1, int x2, int y2) {
2292 return mScreen.getSelectedText(x1, y1, x2, y2);
2297 * Text renderer interface
2300 interface TextRenderer {
2301 int getCharacterWidth();
2302 int getCharacterHeight();
2303 void drawTextRun(Canvas canvas, float x, float y,
2304 int lineOffset, char[] text,
2305 int index, int count, boolean cursor, int foreColor, int backColor);
2308 abstract class BaseTextRenderer implements TextRenderer {
2309 protected int[] mForePaint = {
2310 0xff000000, // Black
2312 0xff00ff00, // green
2313 0xffffff00, // yellow
2315 0xffff00ff, // magenta
2317 0xffffffff // white -- is overridden by constructor
2319 protected int[] mBackPaint = {
2320 0xff000000, // Black -- is overridden by constructor
2322 0xff00cc00, // green
2323 0xffcccc00, // yellow
2325 0xffff00cc, // magenta
2329 protected final static int mCursorPaint = 0xff808080;
2331 public BaseTextRenderer(int forePaintColor, int backPaintColor) {
2332 mForePaint[7] = forePaintColor;
2333 mBackPaint[0] = backPaintColor;
2338 class Bitmap4x8FontRenderer extends BaseTextRenderer {
2339 private final static int kCharacterWidth = 4;
2340 private final static int kCharacterHeight = 8;
2341 private Bitmap mFont;
2342 private int mCurrentForeColor;
2343 private int mCurrentBackColor;
2344 private float[] mColorMatrix;
2345 private Paint mPaint;
2346 private static final float BYTE_SCALE = 1.0f / 255.0f;
2348 public Bitmap4x8FontRenderer(Resources resources,
2349 int forePaintColor, int backPaintColor) {
2350 super(forePaintColor, backPaintColor);
2351 mFont = BitmapFactory.decodeResource(resources,
2352 R.drawable.atari_small);
2353 mPaint = new Paint();
2354 mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
2357 public int getCharacterWidth() {
2358 return kCharacterWidth;
2361 public int getCharacterHeight() {
2362 return kCharacterHeight;
2365 public void drawTextRun(Canvas canvas, float x, float y,
2366 int lineOffset, char[] text, int index, int count,
2367 boolean cursor, int foreColor, int backColor) {
2368 setColorMatrix(mForePaint[foreColor & 7],
2369 cursor ? mCursorPaint : mBackPaint[backColor & 7]);
2370 int destX = (int) x + kCharacterWidth * lineOffset;
2371 int destY = (int) y;
2372 Rect srcRect = new Rect();
2373 Rect destRect = new Rect();
2374 destRect.top = (destY - kCharacterHeight);
2375 destRect.bottom = destY;
2376 for(int i = 0; i < count; i++) {
2377 char c = text[i + index];
2378 if ((cursor || (c != 32)) && (c < 128)) {
2380 int cellY = (c >> 5) & 3;
2381 int srcX = cellX * kCharacterWidth;
2382 int srcY = cellY * kCharacterHeight;
2383 srcRect.set(srcX, srcY,
2384 srcX + kCharacterWidth, srcY + kCharacterHeight);
2385 destRect.left = destX;
2386 destRect.right = destX + kCharacterWidth;
2387 canvas.drawBitmap(mFont, srcRect, destRect, mPaint);
2389 destX += kCharacterWidth;
2393 private void setColorMatrix(int foreColor, int backColor) {
2394 if ((foreColor != mCurrentForeColor)
2395 || (backColor != mCurrentBackColor)
2396 || (mColorMatrix == null)) {
2397 mCurrentForeColor = foreColor;
2398 mCurrentBackColor = backColor;
2399 if (mColorMatrix == null) {
2400 mColorMatrix = new float[20];
2401 mColorMatrix[18] = 1.0f; // Just copy Alpha
2403 for (int component = 0; component < 3; component++) {
2404 int rightShift = (2 - component) << 3;
2405 int fore = 0xff & (foreColor >> rightShift);
2406 int back = 0xff & (backColor >> rightShift);
2407 int delta = back - fore;
2408 mColorMatrix[component * 6] = delta * BYTE_SCALE;
2409 mColorMatrix[component * 5 + 4] = fore;
2411 mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
2416 class PaintRenderer extends BaseTextRenderer {
2417 public PaintRenderer(int fontSize, int forePaintColor, int backPaintColor) {
2418 super(forePaintColor, backPaintColor);
2419 mTextPaint = new Paint();
2420 mTextPaint.setTypeface(Typeface.MONOSPACE);
2421 mTextPaint.setAntiAlias(true);
2422 mTextPaint.setTextSize(fontSize);
2424 mCharHeight = (int) Math.ceil(mTextPaint.getFontSpacing());
2425 mCharAscent = (int) Math.ceil(mTextPaint.ascent());
2426 mCharDescent = mCharHeight + mCharAscent;
2427 mCharWidth = (int) mTextPaint.measureText(EXAMPLE_CHAR, 0, 1);
2430 public void drawTextRun(Canvas canvas, float x, float y, int lineOffset,
2431 char[] text, int index, int count,
2432 boolean cursor, int foreColor, int backColor) {
2434 mTextPaint.setColor(mCursorPaint);
2436 mTextPaint.setColor(mBackPaint[backColor & 0x7]);
2438 float left = x + lineOffset * mCharWidth;
2439 canvas.drawRect(left, y + mCharAscent,
2440 left + count * mCharWidth, y + mCharDescent,
2442 boolean bold = ( foreColor & 0x8 ) != 0;
2443 boolean underline = (backColor & 0x8) != 0;
2445 mTextPaint.setFakeBoldText(true);
2448 mTextPaint.setUnderlineText(true);
2450 mTextPaint.setColor(mForePaint[foreColor & 0x7]);
2451 canvas.drawText(text, index, count, left, y, mTextPaint);
2453 mTextPaint.setFakeBoldText(false);
2456 mTextPaint.setUnderlineText(false);
2460 public int getCharacterHeight() {
2464 public int getCharacterWidth() {
2469 private Paint mTextPaint;
2470 private int mCharWidth;
2471 private int mCharHeight;
2472 private int mCharAscent;
2473 private int mCharDescent;
2474 private static final char[] EXAMPLE_CHAR = {'X'};
2478 * A multi-thread-safe produce-consumer byte array.
2479 * Only allows one producer and one consumer.
2483 public ByteQueue(int size) {
2484 mBuffer = new byte[size];
2487 public int getBytesAvailable() {
2488 synchronized(this) {
2489 return mStoredBytes;
2493 public int read(byte[] buffer, int offset, int length)
2494 throws InterruptedException {
2495 if (length + offset > buffer.length) {
2497 new IllegalArgumentException("length + offset > buffer.length");
2501 new IllegalArgumentException("length < 0");
2507 synchronized(this) {
2508 while (mStoredBytes == 0) {
2512 int bufferLength = mBuffer.length;
2513 boolean wasFull = bufferLength == mStoredBytes;
2514 while (length > 0 && mStoredBytes > 0) {
2515 int oneRun = Math.min(bufferLength - mHead, mStoredBytes);
2516 int bytesToCopy = Math.min(length, oneRun);
2517 System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy);
2518 mHead += bytesToCopy;
2519 if (mHead >= bufferLength) {
2522 mStoredBytes -= bytesToCopy;
2523 length -= bytesToCopy;
2524 offset += bytesToCopy;
2525 totalRead += bytesToCopy;
2534 public void write(byte[] buffer, int offset, int length)
2535 throws InterruptedException {
2536 if (length + offset > buffer.length) {
2538 new IllegalArgumentException("length + offset > buffer.length");
2542 new IllegalArgumentException("length < 0");
2548 synchronized(this) {
2549 int bufferLength = mBuffer.length;
2550 boolean wasEmpty = mStoredBytes == 0;
2551 while (length > 0) {
2552 while(bufferLength == mStoredBytes) {
2555 int tail = mHead + mStoredBytes;
2557 if (tail >= bufferLength) {
2558 tail = tail - bufferLength;
2559 oneRun = mHead - tail;
2561 oneRun = bufferLength - tail;
2563 int bytesToCopy = Math.min(oneRun, length);
2564 System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy);
2565 offset += bytesToCopy;
2566 mStoredBytes += bytesToCopy;
2567 length -= bytesToCopy;
2575 private byte[] mBuffer;
2577 private int mStoredBytes;
2580 * A view on a transcript and a terminal emulator. Displays the text of the
2581 * transcript and the current cursor position of the terminal emulator.
2583 class EmulatorView extends View implements GestureDetector.OnGestureListener {
2585 private final String TAG = "EmulatorView";
2586 private final boolean LOG_KEY_EVENTS = Term.DEBUG && false;
2591 * We defer some initialization until we have been layed out in the view
2592 * hierarchy. The boolean tracks when we know what our size is.
2594 private boolean mKnownSize;
2596 private int mVisibleWidth;
2597 private int mVisibleHeight;
2598 private Rect mVisibleRect = new Rect();
2601 * Our transcript. Contains the screen and the transcript.
2603 private TranscriptScreen mTranscriptScreen;
2606 * Number of rows in the transcript.
2608 private static final int TRANSCRIPT_ROWS = 10000;
2611 * Total width of each character, in pixels
2613 private int mCharacterWidth;
2616 * Total height of each character, in pixels
2618 private int mCharacterHeight;
2621 * Used to render text
2623 private TextRenderer mTextRenderer;
2626 * Text size. Zero means 4 x 8 font.
2628 private int mTextSize;
2630 private int mCursorStyle;
2631 private int mCursorBlink;
2636 private int mForeground;
2641 private int mBackground;
2644 * Used to paint the cursor
2646 private Paint mCursorPaint;
2648 private Paint mBackgroundPaint;
2650 private boolean mUseCookedIme;
2653 * Our terminal emulator. We use this to get the current cursor position.
2655 private TerminalEmulator mEmulator;
2658 * The number of rows of text to display.
2663 * The number of columns of text to display.
2665 private int mColumns;
2668 * The number of columns that are visible on the display.
2671 private int mVisibleColumns;
2674 * The top row of text to display. Ranges from -activeTranscriptRows to 0
2676 private int mTopRow;
2678 private int mLeftColumn;
2680 private FileDescriptor mTermFd;
2682 * Used to receive data from the remote process.
2684 private FileInputStream mTermIn;
2686 private FileOutputStream mTermOut;
2688 private ByteQueue mByteQueue;
2691 * Used to temporarily hold data received from the remote process. Allocated
2692 * once and used permanently to minimize heap thrashing.
2694 private byte[] mReceiveBuffer;
2697 * Our private message id, which we use to receive new input from the
2700 private static final int UPDATE = 1;
2702 private static final int SCREEN_CHECK_PERIOD = 1000;
2703 private static final int CURSOR_BLINK_PERIOD = 1000;
2705 private boolean mCursorVisible = true;
2707 private boolean mIsSelectingText = false;
2710 private float mScaledDensity;
2711 private static final int SELECT_TEXT_OFFSET_Y = -40;
2712 private int mSelXAnchor = -1;
2713 private int mSelYAnchor = -1;
2714 private int mSelX1 = -1;
2715 private int mSelY1 = -1;
2716 private int mSelX2 = -1;
2717 private int mSelY2 = -1;
2720 * Used to poll if the view has changed size. Wish there was a better way to do this.
2722 private Runnable mCheckSize = new Runnable() {
2726 mHandler.postDelayed(this, SCREEN_CHECK_PERIOD);
2730 private Runnable mBlinkCursor = new Runnable() {
2732 if (mCursorBlink != 0) {
2733 mCursorVisible = ! mCursorVisible;
2734 mHandler.postDelayed(this, CURSOR_BLINK_PERIOD);
2736 mCursorVisible = true;
2738 // Perhaps just invalidate the character with the cursor.
2744 * Thread that polls for input from the remote process
2747 private Thread mPollingThread;
2749 private GestureDetector mGestureDetector;
2750 private float mScrollRemainder;
2751 private TermKeyListener mKeyListener;
2754 * Our message handler class. Implements a periodic callback.
2756 private final Handler mHandler = new Handler() {
2758 * Handle the callback message. Call our enclosing class's update
2761 * @param msg The callback message.
2764 public void handleMessage(Message msg) {
2765 if (msg.what == UPDATE) {
2771 public EmulatorView(Context context) {
2773 commonConstructor();
2776 public void setScaledDensity(float scaledDensity) {
2777 mScaledDensity = scaledDensity;
2780 public void onResume() {
2782 mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD);
2783 if (mCursorBlink != 0) {
2784 mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD);
2788 public void onPause() {
2789 mHandler.removeCallbacks(mCheckSize);
2790 if (mCursorBlink != 0) {
2791 mHandler.removeCallbacks(mBlinkCursor);
2795 public void register(Term term, TermKeyListener listener) {
2797 mKeyListener = listener;
2800 public void setColors(int foreground, int background) {
2801 mForeground = foreground;
2802 mBackground = background;
2806 public String getTranscriptText() {
2807 return mEmulator.getTranscriptText();
2810 public void resetTerminal() {
2816 public boolean onCheckIsTextEditor() {
2821 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
2822 outAttrs.inputType = mUseCookedIme ?
2823 EditorInfo.TYPE_CLASS_TEXT :
2824 EditorInfo.TYPE_NULL;
2825 return new BaseInputConnection(this, false) {
2828 public boolean commitText(CharSequence text, int newCursorPosition) {
2834 public boolean performEditorAction(int actionCode) {
2835 if(actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) {
2836 // The "return" key has been pressed on the IME.
2844 public boolean sendKeyEvent(KeyEvent event) {
2845 if (event.getAction() == KeyEvent.ACTION_DOWN) {
2846 // Some keys are sent here rather than to commitText.
2847 // In particular, del and the digit keys are sent here.
2848 // (And I have reports that the HTC Magic also sends Return here.)
2849 // As a bit of defensive programming, handle every
2850 // key with an ASCII meaning.
2851 int keyCode = event.getKeyCode();
2852 if (keyCode >= 0 && keyCode < KEYCODE_CHARS.length()) {
2853 char c = KEYCODE_CHARS.charAt(keyCode);
2857 // Handle IME arrow key events
2859 case KeyEvent.KEYCODE_DPAD_UP: // Up Arrow
2860 case KeyEvent.KEYCODE_DPAD_DOWN: // Down Arrow
2861 case KeyEvent.KEYCODE_DPAD_LEFT: // Left Arrow
2862 case KeyEvent.KEYCODE_DPAD_RIGHT: // Right Arrow
2863 super.sendKeyEvent(event);
2867 } // switch (keyCode)
2874 private final String KEYCODE_CHARS =
2875 "\000\000\000\000\000\000\000" + "0123456789*#"
2876 + "\000\000\000\000\000\000\000\000\000\000"
2877 + "abcdefghijklmnopqrstuvwxyz,."
2878 + "\000\000\000\000"
2879 + "\011 " // tab, space
2880 + "\000\000\000" // sym .. envelope
2881 + "\015\177" // enter, del
2887 public boolean setComposingText(CharSequence text, int newCursorPosition) {
2892 public boolean setSelection(int start, int end) {
2897 public boolean deleteSurroundingText(int leftLength, int rightLength) {
2898 if (leftLength > 0) {
2899 for (int i = 0; i < leftLength; i++) {
2901 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
2903 } else if ((leftLength == 0) && (rightLength == 0)) {
2904 // Delete key held down / repeating
2906 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
2908 // TODO: handle forward deletes.
2912 private void sendChar(int c) {
2915 } catch (IOException ex) {
2919 private void sendText(CharSequence text) {
2920 int n = text.length();
2922 for(int i = 0; i < n; i++) {
2923 char c = text.charAt(i);
2926 } catch (IOException e) {
2930 private void mapAndSend(int c) throws IOException {
2932 mKeyListener.mapControlChar(c));
2937 public boolean getKeypadApplicationMode() {
2938 return mEmulator.getKeypadApplicationMode();
2941 public EmulatorView(Context context, AttributeSet attrs) {
2942 this(context, attrs, 0);
2945 public EmulatorView(Context context, AttributeSet attrs,
2947 super(context, attrs, defStyle);
2949 // context.obtainStyledAttributes(android.R.styleable.View);
2950 // initializeScrollbars(a);
2952 commonConstructor();
2955 private void commonConstructor() {
2956 mTextRenderer = null;
2957 mCursorPaint = new Paint();
2958 mCursorPaint.setARGB(255,128,128,128);
2959 mBackgroundPaint = new Paint();
2962 mGestureDetector = new GestureDetector(this);
2963 // mGestureDetector.setIsLongpressEnabled(false);
2964 setVerticalScrollBarEnabled(true);
2968 protected int computeVerticalScrollRange() {
2969 return mTranscriptScreen.getActiveRows();
2973 protected int computeVerticalScrollExtent() {
2978 protected int computeVerticalScrollOffset() {
2979 return mTranscriptScreen.getActiveRows() + mTopRow - mRows;
2983 * Call this to initialize the view.
2985 * @param termFd the file descriptor
2986 * @param termOut the output stream for the pseudo-teletype
2988 public void initialize(FileDescriptor termFd, FileOutputStream termOut) {
2992 mForeground = Term.WHITE;
2993 mBackground = Term.BLACK;
2995 mTermIn = new FileInputStream(mTermFd);
2996 mReceiveBuffer = new byte[4 * 1024];
2997 mByteQueue = new ByteQueue(4 * 1024);
3001 * Accept a sequence of bytes (typically from the pseudo-tty) and process
3004 * @param buffer a byte array containing bytes to be processed
3005 * @param base the index of the first byte in the buffer to process
3006 * @param length the number of bytes to process
3008 public void append(byte[] buffer, int base, int length) {
3009 mEmulator.append(buffer, base, length);
3010 if ( mIsSelectingText ) {
3011 int rowShift = mEmulator.getScrollCounter();
3014 mSelYAnchor -= rowShift;
3016 mEmulator.clearScrollCounter();
3017 ensureCursorVisible();
3022 * Page the terminal view (scroll it up or down by delta screenfulls.)
3024 * @param delta the number of screens to scroll. Positive means scroll down,
3025 * negative means scroll up.
3027 public void page(int delta) {
3029 Math.min(0, Math.max(-(mTranscriptScreen
3030 .getActiveTranscriptRows()), mTopRow + mRows * delta));
3035 * Page the terminal view horizontally.
3037 * @param deltaColumns the number of columns to scroll. Positive scrolls to
3040 public void pageHorizontal(int deltaColumns) {
3042 Math.max(0, Math.min(mLeftColumn + deltaColumns, mColumns
3043 - mVisibleColumns));
3048 * Sets the text size, which in turn sets the number of rows and columns
3050 * @param fontSize the new font size, in pixels.
3052 public void setTextSize(int fontSize) {
3053 mTextSize = fontSize;
3057 public void setCursorStyle(int style, int blink) {
3058 mCursorStyle = style;
3059 if (blink != 0 && mCursorBlink == 0) {
3060 mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD);
3061 } else if (blink == 0 && mCursorBlink != 0) {
3062 mHandler.removeCallbacks(mBlinkCursor);
3064 mCursorBlink = blink;
3067 public void setUseCookedIME(boolean useRawIME) {
3068 mUseCookedIme = useRawIME;
3071 // Begin GestureDetector.OnGestureListener methods
3073 public boolean onSingleTapUp(MotionEvent e) {
3077 public void onLongPress(MotionEvent e) {
3081 public boolean onScroll(MotionEvent e1, MotionEvent e2,
3082 float distanceX, float distanceY) {
3083 distanceY += mScrollRemainder;
3084 int deltaRows = (int) (distanceY / mCharacterHeight);
3085 mScrollRemainder = distanceY - deltaRows * mCharacterHeight;
3087 Math.min(0, Math.max(-(mTranscriptScreen
3088 .getActiveTranscriptRows()), mTopRow + deltaRows));
3094 public void onSingleTapConfirmed(MotionEvent e) {
3097 public boolean onJumpTapDown(MotionEvent e1, MotionEvent e2) {
3104 public boolean onJumpTapUp(MotionEvent e1, MotionEvent e2) {
3106 mTopRow = -mTranscriptScreen.getActiveTranscriptRows();
3111 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
3113 // TODO: add animation man's (non animated) fling
3114 mScrollRemainder = 0.0f;
3115 onScroll(e1, e2, 2 * velocityX, -2 * velocityY);
3119 public void onShowPress(MotionEvent e) {
3122 public boolean onDown(MotionEvent e) {
3123 mScrollRemainder = 0.0f;
3127 // End GestureDetector.OnGestureListener methods
3129 @Override public boolean onTouchEvent(MotionEvent ev) {
3130 if (mIsSelectingText) {
3131 return onTouchEventWhileSelectingText(ev);
3133 return mGestureDetector.onTouchEvent(ev);
3137 private boolean onTouchEventWhileSelectingText(MotionEvent ev) {
3138 int action = ev.getAction();
3139 int cx = (int)(ev.getX() / mCharacterWidth);
3140 int cy = Math.max(0,
3141 (int)((ev.getY() + SELECT_TEXT_OFFSET_Y * mScaledDensity)
3142 / mCharacterHeight) + mTopRow);
3144 case MotionEvent.ACTION_DOWN:
3152 case MotionEvent.ACTION_MOVE:
3153 case MotionEvent.ACTION_UP:
3154 int minx = Math.min(mSelXAnchor, cx);
3155 int maxx = Math.max(mSelXAnchor, cx);
3156 int miny = Math.min(mSelYAnchor, cy);
3157 int maxy = Math.max(mSelYAnchor, cy);
3162 if (action == MotionEvent.ACTION_UP) {
3163 ClipboardManager clip = (ClipboardManager)
3164 getContext().getApplicationContext()
3165 .getSystemService(Context.CLIPBOARD_SERVICE);
3166 clip.setText(getSelectedText().trim());
3167 toggleSelectingText();
3172 toggleSelectingText();
3180 public boolean onKeyDown(int keyCode, KeyEvent event) {
3181 if (LOG_KEY_EVENTS) {
3182 Log.w(TAG, "onKeyDown " + keyCode);
3184 if (handleControlKey(keyCode, true)) {
3186 } else if (isSystemKey(keyCode, event)) {
3187 // Don't intercept the system keys
3188 return super.onKeyDown(keyCode, event);
3191 // Translate the keyCode into an ASCII character.
3194 mKeyListener.keyDown(keyCode, event, mTermOut,
3195 getKeypadApplicationMode());
3196 } catch (IOException e) {
3197 // Ignore I/O exceptions
3203 public boolean onKeyUp(int keyCode, KeyEvent event) {
3204 if (LOG_KEY_EVENTS) {
3205 Log.w(TAG, "onKeyUp " + keyCode);
3207 if (handleControlKey(keyCode, false)) {
3209 } else if (isSystemKey(keyCode, event)) {
3210 // Don't intercept the system keys
3211 return super.onKeyUp(keyCode, event);
3214 mKeyListener.keyUp(keyCode);
3219 private boolean handleControlKey(int keyCode, boolean down) {
3220 if (keyCode == mTerm.getControlKeyCode()) {
3221 if (LOG_KEY_EVENTS) {
3222 Log.w(TAG, "handleControlKey " + keyCode);
3224 mKeyListener.handleControlKey(down);
3230 private boolean isSystemKey(int keyCode, KeyEvent event) {
3231 return event.isSystem();
3234 private void updateText() {
3235 if (mTextSize > 0) {
3236 mTextRenderer = new PaintRenderer(mTextSize, mForeground,
3240 mTextRenderer = new Bitmap4x8FontRenderer(getResources(),
3241 mForeground, mBackground);
3243 mBackgroundPaint.setColor(mBackground);
3244 mCharacterWidth = mTextRenderer.getCharacterWidth();
3245 mCharacterHeight = mTextRenderer.getCharacterHeight();
3251 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
3252 boolean oldKnownSize = mKnownSize;
3257 if (!oldKnownSize) {
3258 // Set up a thread to read input from the
3261 mPollingThread = new Thread(new Runnable() {
3266 int read = mTermIn.read(mBuffer);
3267 mByteQueue.write(mBuffer, 0, read);
3268 mHandler.sendMessage(
3269 mHandler.obtainMessage(UPDATE));
3271 } catch (IOException e) {
3272 } catch (InterruptedException e) {
3275 private byte[] mBuffer = new byte[4096];
3277 mPollingThread.setName("Input reader");
3278 mPollingThread.start();
3282 private void updateSize(int w, int h) {
3283 mColumns = Math.max(1, w / mCharacterWidth);
3284 mRows = Math.max(1, h / mCharacterHeight);
3285 mVisibleColumns = mVisibleWidth / mCharacterWidth;
3287 // Inform the attached pty of our new size:
3288 Exec.setPtyWindowSize(mTermFd, mRows, mColumns, w, h);
3291 if (mTranscriptScreen != null) {
3292 mEmulator.updateSize(mColumns, mRows);
3295 new TranscriptScreen(mColumns, TRANSCRIPT_ROWS, mRows, 0, 7);
3297 new TerminalEmulator(mTranscriptScreen, mColumns, mRows,
3301 // Reset our paging:
3308 void updateSize(boolean force) {
3310 getWindowVisibleDisplayFrame(mVisibleRect);
3311 int w = mVisibleRect.width();
3312 int h = mVisibleRect.height();
3313 // Log.w("Term", "(" + w + ", " + h + ")");
3314 if (force || w != mVisibleWidth || h != mVisibleHeight) {
3317 updateSize(mVisibleWidth, mVisibleHeight);
3323 * Look for new input from the ptty, send it to the terminal emulator.
3325 private void update() {
3326 int bytesAvailable = mByteQueue.getBytesAvailable();
3327 int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length);
3329 int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead);
3330 append(mReceiveBuffer, 0, bytesRead);
3331 } catch (InterruptedException e) {
3336 protected void onDraw(Canvas canvas) {
3339 int h = getHeight();
3340 canvas.drawRect(0, 0, w, h, mBackgroundPaint);
3341 float x = -mLeftColumn * mCharacterWidth;
3342 float y = mCharacterHeight;
3343 int endLine = mTopRow + mRows;
3344 int cx = mEmulator.getCursorCol();
3345 int cy = mEmulator.getCursorRow();
3346 for (int i = mTopRow; i < endLine; i++) {
3348 if (i == cy && mCursorVisible) {
3353 if ( i >= mSelY1 && i <= mSelY2 ) {
3354 if ( i == mSelY1 ) {
3357 if ( i == mSelY2 ) {
3363 mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2);
3364 y += mCharacterHeight;
3368 private void ensureCursorVisible() {
3370 if (mVisibleColumns > 0) {
3371 int cx = mEmulator.getCursorCol();
3372 int visibleCursorX = mEmulator.getCursorCol() - mLeftColumn;
3373 if (visibleCursorX < 0) {
3375 } else if (visibleCursorX >= mVisibleColumns) {
3376 mLeftColumn = (cx - mVisibleColumns) + 1;
3381 public void toggleSelectingText() {
3382 mIsSelectingText = ! mIsSelectingText;
3383 setVerticalScrollBarEnabled( ! mIsSelectingText );
3384 if ( ! mIsSelectingText ) {
3392 public boolean getSelectingText() {
3393 return mIsSelectingText;
3396 public String getSelectedText() {
3397 return mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2);
3403 * An ASCII key listener. Supports control characters and escape. Keeps track of
3404 * the current state of the alt, shift, and control keys.
3406 class TermKeyListener {
3408 * Android key codes that are defined in the Android 2.3 API.
3409 * We want to recognize these codes, because they will be sent to our
3410 * app when we run on Android 2.3 systems.
3411 * But we don't want to accidentally use 2.3-specific APIs.
3412 * So we compile against the Android 1.6 APIs, and have a copy of the codes here.
3415 /** Key code constant: Unknown key code. */
3416 public static final int KEYCODE_UNKNOWN = 0;
3417 /** Key code constant: Soft Left key.
3418 * Usually situated below the display on phones and used as a multi-function
3419 * feature key for selecting a software defined function shown on the bottom left
3420 * of the display. */
3421 public static final int KEYCODE_SOFT_LEFT = 1;
3422 /** Key code constant: Soft Right key.
3423 * Usually situated below the display on phones and used as a multi-function
3424 * feature key for selecting a software defined function shown on the bottom right
3425 * of the display. */
3426 public static final int KEYCODE_SOFT_RIGHT = 2;
3427 /** Key code constant: Home key.
3428 * This key is handled by the framework and is never delivered to applications. */
3429 public static final int KEYCODE_HOME = 3;
3430 /** Key code constant: Back key. */
3431 public static final int KEYCODE_BACK = 4;
3432 /** Key code constant: Call key. */
3433 public static final int KEYCODE_CALL = 5;
3434 /** Key code constant: End Call key. */
3435 public static final int KEYCODE_ENDCALL = 6;
3436 /** Key code constant: '0' key. */
3437 public static final int KEYCODE_0 = 7;
3438 /** Key code constant: '1' key. */
3439 public static final int KEYCODE_1 = 8;
3440 /** Key code constant: '2' key. */
3441 public static final int KEYCODE_2 = 9;
3442 /** Key code constant: '3' key. */
3443 public static final int KEYCODE_3 = 10;
3444 /** Key code constant: '4' key. */
3445 public static final int KEYCODE_4 = 11;
3446 /** Key code constant: '5' key. */
3447 public static final int KEYCODE_5 = 12;
3448 /** Key code constant: '6' key. */
3449 public static final int KEYCODE_6 = 13;
3450 /** Key code constant: '7' key. */
3451 public static final int KEYCODE_7 = 14;
3452 /** Key code constant: '8' key. */
3453 public static final int KEYCODE_8 = 15;
3454 /** Key code constant: '9' key. */
3455 public static final int KEYCODE_9 = 16;
3456 /** Key code constant: '*' key. */
3457 public static final int KEYCODE_STAR = 17;
3458 /** Key code constant: '#' key. */
3459 public static final int KEYCODE_POUND = 18;
3460 /** Key code constant: Directional Pad Up key.
3461 * May also be synthesized from trackball motions. */
3462 public static final int KEYCODE_DPAD_UP = 19;
3463 /** Key code constant: Directional Pad Down key.
3464 * May also be synthesized from trackball motions. */
3465 public static final int KEYCODE_DPAD_DOWN = 20;
3466 /** Key code constant: Directional Pad Left key.
3467 * May also be synthesized from trackball motions. */
3468 public static final int KEYCODE_DPAD_LEFT = 21;
3469 /** Key code constant: Directional Pad Right key.
3470 * May also be synthesized from trackball motions. */
3471 public static final int KEYCODE_DPAD_RIGHT = 22;
3472 /** Key code constant: Directional Pad Center key.
3473 * May also be synthesized from trackball motions. */
3474 public static final int KEYCODE_DPAD_CENTER = 23;
3475 /** Key code constant: Volume Up key.
3476 * Adjusts the speaker volume up. */
3477 public static final int KEYCODE_VOLUME_UP = 24;
3478 /** Key code constant: Volume Down key.
3479 * Adjusts the speaker volume down. */
3480 public static final int KEYCODE_VOLUME_DOWN = 25;
3481 /** Key code constant: Power key. */
3482 public static final int KEYCODE_POWER = 26;
3483 /** Key code constant: Camera key.
3484 * Used to launch a camera application or take pictures. */
3485 public static final int KEYCODE_CAMERA = 27;
3486 /** Key code constant: Clear key. */
3487 public static final int KEYCODE_CLEAR = 28;
3488 /** Key code constant: 'A' key. */
3489 public static final int KEYCODE_A = 29;
3490 /** Key code constant: 'B' key. */
3491 public static final int KEYCODE_B = 30;
3492 /** Key code constant: 'C' key. */
3493 public static final int KEYCODE_C = 31;
3494 /** Key code constant: 'D' key. */
3495 public static final int KEYCODE_D = 32;
3496 /** Key code constant: 'E' key. */
3497 public static final int KEYCODE_E = 33;
3498 /** Key code constant: 'F' key. */
3499 public static final int KEYCODE_F = 34;
3500 /** Key code constant: 'G' key. */
3501 public static final int KEYCODE_G = 35;
3502 /** Key code constant: 'H' key. */
3503 public static final int KEYCODE_H = 36;
3504 /** Key code constant: 'I' key. */
3505 public static final int KEYCODE_I = 37;
3506 /** Key code constant: 'J' key. */
3507 public static final int KEYCODE_J = 38;
3508 /** Key code constant: 'K' key. */
3509 public static final int KEYCODE_K = 39;
3510 /** Key code constant: 'L' key. */
3511 public static final int KEYCODE_L = 40;
3512 /** Key code constant: 'M' key. */
3513 public static final int KEYCODE_M = 41;
3514 /** Key code constant: 'N' key. */
3515 public static final int KEYCODE_N = 42;
3516 /** Key code constant: 'O' key. */
3517 public static final int KEYCODE_O = 43;
3518 /** Key code constant: 'P' key. */
3519 public static final int KEYCODE_P = 44;
3520 /** Key code constant: 'Q' key. */
3521 public static final int KEYCODE_Q = 45;
3522 /** Key code constant: 'R' key. */
3523 public static final int KEYCODE_R = 46;
3524 /** Key code constant: 'S' key. */
3525 public static final int KEYCODE_S = 47;
3526 /** Key code constant: 'T' key. */
3527 public static final int KEYCODE_T = 48;
3528 /** Key code constant: 'U' key. */
3529 public static final int KEYCODE_U = 49;
3530 /** Key code constant: 'V' key. */
3531 public static final int KEYCODE_V = 50;
3532 /** Key code constant: 'W' key. */
3533 public static final int KEYCODE_W = 51;
3534 /** Key code constant: 'X' key. */
3535 public static final int KEYCODE_X = 52;
3536 /** Key code constant: 'Y' key. */
3537 public static final int KEYCODE_Y = 53;
3538 /** Key code constant: 'Z' key. */
3539 public static final int KEYCODE_Z = 54;
3540 /** Key code constant: ',' key. */
3541 public static final int KEYCODE_COMMA = 55;
3542 /** Key code constant: '.' key. */
3543 public static final int KEYCODE_PERIOD = 56;
3544 /** Key code constant: Left Alt modifier key. */
3545 public static final int KEYCODE_ALT_LEFT = 57;
3546 /** Key code constant: Right Alt modifier key. */
3547 public static final int KEYCODE_ALT_RIGHT = 58;
3548 /** Key code constant: Left Shift modifier key. */
3549 public static final int KEYCODE_SHIFT_LEFT = 59;
3550 /** Key code constant: Right Shift modifier key. */
3551 public static final int KEYCODE_SHIFT_RIGHT = 60;
3552 /** Key code constant: Tab key. */
3553 public static final int KEYCODE_TAB = 61;
3554 /** Key code constant: Space key. */
3555 public static final int KEYCODE_SPACE = 62;
3556 /** Key code constant: Symbol modifier key.
3557 * Used to enter alternate symbols. */
3558 public static final int KEYCODE_SYM = 63;
3559 /** Key code constant: Explorer special function key.
3560 * Used to launch a browser application. */
3561 public static final int KEYCODE_EXPLORER = 64;
3562 /** Key code constant: Envelope special function key.
3563 * Used to launch a mail application. */
3564 public static final int KEYCODE_ENVELOPE = 65;
3565 /** Key code constant: Enter key. */
3566 public static final int KEYCODE_ENTER = 66;
3567 /** Key code constant: Backspace key.
3568 * Deletes characters before the insertion point, unlike {@link #KEYCODE_FORWARD_DEL}. */
3569 public static final int KEYCODE_DEL = 67;
3570 /** Key code constant: '`' (backtick) key. */
3571 public static final int KEYCODE_GRAVE = 68;
3572 /** Key code constant: '-'. */
3573 public static final int KEYCODE_MINUS = 69;
3574 /** Key code constant: '=' key. */
3575 public static final int KEYCODE_EQUALS = 70;
3576 /** Key code constant: '[' key. */
3577 public static final int KEYCODE_LEFT_BRACKET = 71;
3578 /** Key code constant: ']' key. */
3579 public static final int KEYCODE_RIGHT_BRACKET = 72;
3580 /** Key code constant: '\' key. */
3581 public static final int KEYCODE_BACKSLASH = 73;
3582 /** Key code constant: ';' key. */
3583 public static final int KEYCODE_SEMICOLON = 74;
3584 /** Key code constant: ''' (apostrophe) key. */
3585 public static final int KEYCODE_APOSTROPHE = 75;
3586 /** Key code constant: '/' key. */
3587 public static final int KEYCODE_SLASH = 76;
3588 /** Key code constant: '@' key. */
3589 public static final int KEYCODE_AT = 77;
3590 /** Key code constant: Number modifier key.
3591 * Used to enter numeric symbols.
3592 * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is
3593 * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */
3594 public static final int KEYCODE_NUM = 78;
3595 /** Key code constant: Headset Hook key.
3596 * Used to hang up calls and stop media. */
3597 public static final int KEYCODE_HEADSETHOOK = 79;
3598 /** Key code constant: Camera Focus key.
3599 * Used to focus the camera. */
3600 public static final int KEYCODE_FOCUS = 80; // *Camera* focus
3601 /** Key code constant: '+' key. */
3602 public static final int KEYCODE_PLUS = 81;
3603 /** Key code constant: Menu key. */
3604 public static final int KEYCODE_MENU = 82;
3605 /** Key code constant: Notification key. */
3606 public static final int KEYCODE_NOTIFICATION = 83;
3607 /** Key code constant: Search key. */
3608 public static final int KEYCODE_SEARCH = 84;
3609 /** Key code constant: Play/Pause media key. */
3610 public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85;
3611 /** Key code constant: Stop media key. */
3612 public static final int KEYCODE_MEDIA_STOP = 86;
3613 /** Key code constant: Play Next media key. */
3614 public static final int KEYCODE_MEDIA_NEXT = 87;
3615 /** Key code constant: Play Previous media key. */
3616 public static final int KEYCODE_MEDIA_PREVIOUS = 88;
3617 /** Key code constant: Rewind media key. */
3618 public static final int KEYCODE_MEDIA_REWIND = 89;
3619 /** Key code constant: Fast Forward media key. */
3620 public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
3621 /** Key code constant: Mute key.
3622 * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */
3623 public static final int KEYCODE_MUTE = 91;
3624 /** Key code constant: Page Up key. */
3625 public static final int KEYCODE_PAGE_UP = 92;
3626 /** Key code constant: Page Down key. */
3627 public static final int KEYCODE_PAGE_DOWN = 93;
3628 /** Key code constant: Picture Symbols modifier key.
3629 * Used to switch symbol sets (Emoji, Kao-moji). */
3630 public static final int KEYCODE_PICTSYMBOLS = 94; // switch symbol-sets (Emoji,Kao-moji)
3631 /** Key code constant: Switch Charset modifier key.
3632 * Used to switch character sets (Kanji, Katakana). */
3633 public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana)
3634 /** Key code constant: A Button key.
3635 * On a game controller, the A button should be either the button labeled A
3636 * or the first button on the upper row of controller buttons. */
3637 public static final int KEYCODE_BUTTON_A = 96;
3638 /** Key code constant: B Button key.
3639 * On a game controller, the B button should be either the button labeled B
3640 * or the second button on the upper row of controller buttons. */
3641 public static final int KEYCODE_BUTTON_B = 97;
3642 /** Key code constant: C Button key.
3643 * On a game controller, the C button should be either the button labeled C
3644 * or the third button on the upper row of controller buttons. */
3645 public static final int KEYCODE_BUTTON_C = 98;
3646 /** Key code constant: X Button key.
3647 * On a game controller, the X button should be either the button labeled X
3648 * or the first button on the lower row of controller buttons. */
3649 public static final int KEYCODE_BUTTON_X = 99;
3650 /** Key code constant: Y Button key.
3651 * On a game controller, the Y button should be either the button labeled Y
3652 * or the second button on the lower row of controller buttons. */
3653 public static final int KEYCODE_BUTTON_Y = 100;
3654 /** Key code constant: Z Button key.
3655 * On a game controller, the Z button should be either the button labeled Z
3656 * or the third button on the lower row of controller buttons. */
3657 public static final int KEYCODE_BUTTON_Z = 101;
3658 /** Key code constant: L1 Button key.
3659 * On a game controller, the L1 button should be either the button labeled L1 (or L)
3660 * or the top left trigger button. */
3661 public static final int KEYCODE_BUTTON_L1 = 102;
3662 /** Key code constant: R1 Button key.
3663 * On a game controller, the R1 button should be either the button labeled R1 (or R)
3664 * or the top right trigger button. */
3665 public static final int KEYCODE_BUTTON_R1 = 103;
3666 /** Key code constant: L2 Button key.
3667 * On a game controller, the L2 button should be either the button labeled L2
3668 * or the bottom left trigger button. */
3669 public static final int KEYCODE_BUTTON_L2 = 104;
3670 /** Key code constant: R2 Button key.
3671 * On a game controller, the R2 button should be either the button labeled R2
3672 * or the bottom right trigger button. */
3673 public static final int KEYCODE_BUTTON_R2 = 105;
3674 /** Key code constant: Left Thumb Button key.
3675 * On a game controller, the left thumb button indicates that the left (or only)
3676 * joystick is pressed. */
3677 public static final int KEYCODE_BUTTON_THUMBL = 106;
3678 /** Key code constant: Right Thumb Button key.
3679 * On a game controller, the right thumb button indicates that the right
3680 * joystick is pressed. */
3681 public static final int KEYCODE_BUTTON_THUMBR = 107;
3682 /** Key code constant: Start Button key.
3683 * On a game controller, the button labeled Start. */
3684 public static final int KEYCODE_BUTTON_START = 108;
3685 /** Key code constant: Select Button key.
3686 * On a game controller, the button labeled Select. */
3687 public static final int KEYCODE_BUTTON_SELECT = 109;
3688 /** Key code constant: Mode Button key.
3689 * On a game controller, the button labeled Mode. */
3690 public static final int KEYCODE_BUTTON_MODE = 110;
3691 /** Key code constant: Escape key. */
3692 public static final int KEYCODE_ESCAPE = 111;
3693 /** Key code constant: Forward Delete key.
3694 * Deletes characters ahead of the insertion point, unlike {@link #KEYCODE_DEL}. */
3695 public static final int KEYCODE_FORWARD_DEL = 112;
3696 /** Key code constant: Left Control modifier key. */
3697 public static final int KEYCODE_CTRL_LEFT = 113;
3698 /** Key code constant: Right Control modifier key. */
3699 public static final int KEYCODE_CTRL_RIGHT = 114;
3700 /** Key code constant: Caps Lock modifier key. */
3701 public static final int KEYCODE_CAPS_LOCK = 115;
3702 /** Key code constant: Scroll Lock key. */
3703 public static final int KEYCODE_SCROLL_LOCK = 116;
3704 /** Key code constant: Left Meta modifier key. */
3705 public static final int KEYCODE_META_LEFT = 117;
3706 /** Key code constant: Right Meta modifier key. */
3707 public static final int KEYCODE_META_RIGHT = 118;
3708 /** Key code constant: Function modifier key. */
3709 public static final int KEYCODE_FUNCTION = 119;
3710 /** Key code constant: System Request / Print Screen key. */
3711 public static final int KEYCODE_SYSRQ = 120;
3712 /** Key code constant: Break / Pause key. */
3713 public static final int KEYCODE_BREAK = 121;
3714 /** Key code constant: Home Movement key.
3715 * Used for scrolling or moving the cursor around to the start of a line
3716 * or to the top of a list. */
3717 public static final int KEYCODE_MOVE_HOME = 122;
3718 /** Key code constant: End Movement key.
3719 * Used for scrolling or moving the cursor around to the end of a line
3720 * or to the bottom of a list. */
3721 public static final int KEYCODE_MOVE_END = 123;
3722 /** Key code constant: Insert key.
3723 * Toggles insert / overwrite edit mode. */
3724 public static final int KEYCODE_INSERT = 124;
3725 /** Key code constant: Forward key.
3726 * Navigates forward in the history stack. Complement of {@link #KEYCODE_BACK}. */
3727 public static final int KEYCODE_FORWARD = 125;
3728 /** Key code constant: Play media key. */
3729 public static final int KEYCODE_MEDIA_PLAY = 126;
3730 /** Key code constant: Pause media key. */
3731 public static final int KEYCODE_MEDIA_PAUSE = 127;
3732 /** Key code constant: Close media key.
3733 * May be used to close a CD tray, for example. */
3734 public static final int KEYCODE_MEDIA_CLOSE = 128;
3735 /** Key code constant: Eject media key.
3736 * May be used to eject a CD tray, for example. */
3737 public static final int KEYCODE_MEDIA_EJECT = 129;
3738 /** Key code constant: Record media key. */
3739 public static final int KEYCODE_MEDIA_RECORD = 130;
3740 /** Key code constant: F1 key. */
3741 public static final int KEYCODE_F1 = 131;
3742 /** Key code constant: F2 key. */
3743 public static final int KEYCODE_F2 = 132;
3744 /** Key code constant: F3 key. */
3745 public static final int KEYCODE_F3 = 133;
3746 /** Key code constant: F4 key. */
3747 public static final int KEYCODE_F4 = 134;
3748 /** Key code constant: F5 key. */
3749 public static final int KEYCODE_F5 = 135;
3750 /** Key code constant: F6 key. */
3751 public static final int KEYCODE_F6 = 136;
3752 /** Key code constant: F7 key. */
3753 public static final int KEYCODE_F7 = 137;
3754 /** Key code constant: F8 key. */
3755 public static final int KEYCODE_F8 = 138;
3756 /** Key code constant: F9 key. */
3757 public static final int KEYCODE_F9 = 139;
3758 /** Key code constant: F10 key. */
3759 public static final int KEYCODE_F10 = 140;
3760 /** Key code constant: F11 key. */
3761 public static final int KEYCODE_F11 = 141;
3762 /** Key code constant: F12 key. */
3763 public static final int KEYCODE_F12 = 142;
3764 /** Key code constant: Num Lock modifier key.
3765 * This is the Num Lock key; it is different from {@link #KEYCODE_NUM}.
3766 * This key generally modifies the behavior of other keys on the numeric keypad. */
3767 public static final int KEYCODE_NUM_LOCK = 143;
3768 /** Key code constant: Numeric keypad '0' key. */
3769 public static final int KEYCODE_NUMPAD_0 = 144;
3770 /** Key code constant: Numeric keypad '1' key. */
3771 public static final int KEYCODE_NUMPAD_1 = 145;
3772 /** Key code constant: Numeric keypad '2' key. */
3773 public static final int KEYCODE_NUMPAD_2 = 146;
3774 /** Key code constant: Numeric keypad '3' key. */
3775 public static final int KEYCODE_NUMPAD_3 = 147;
3776 /** Key code constant: Numeric keypad '4' key. */
3777 public static final int KEYCODE_NUMPAD_4 = 148;
3778 /** Key code constant: Numeric keypad '5' key. */
3779 public static final int KEYCODE_NUMPAD_5 = 149;
3780 /** Key code constant: Numeric keypad '6' key. */
3781 public static final int KEYCODE_NUMPAD_6 = 150;
3782 /** Key code constant: Numeric keypad '7' key. */
3783 public static final int KEYCODE_NUMPAD_7 = 151;
3784 /** Key code constant: Numeric keypad '8' key. */
3785 public static final int KEYCODE_NUMPAD_8 = 152;
3786 /** Key code constant: Numeric keypad '9' key. */
3787 public static final int KEYCODE_NUMPAD_9 = 153;
3788 /** Key code constant: Numeric keypad '/' key (for division). */
3789 public static final int KEYCODE_NUMPAD_DIVIDE = 154;
3790 /** Key code constant: Numeric keypad '*' key (for multiplication). */
3791 public static final int KEYCODE_NUMPAD_MULTIPLY = 155;
3792 /** Key code constant: Numeric keypad '-' key (for subtraction). */
3793 public static final int KEYCODE_NUMPAD_SUBTRACT = 156;
3794 /** Key code constant: Numeric keypad '+' key (for addition). */
3795 public static final int KEYCODE_NUMPAD_ADD = 157;
3796 /** Key code constant: Numeric keypad '.' key (for decimals or digit grouping). */
3797 public static final int KEYCODE_NUMPAD_DOT = 158;
3798 /** Key code constant: Numeric keypad ',' key (for decimals or digit grouping). */
3799 public static final int KEYCODE_NUMPAD_COMMA = 159;
3800 /** Key code constant: Numeric keypad Enter key. */
3801 public static final int KEYCODE_NUMPAD_ENTER = 160;
3802 /** Key code constant: Numeric keypad '=' key. */
3803 public static final int KEYCODE_NUMPAD_EQUALS = 161;
3804 /** Key code constant: Numeric keypad '(' key. */
3805 public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162;
3806 /** Key code constant: Numeric keypad ')' key. */
3807 public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163;
3808 /** Key code constant: Volume Mute key.
3809 * Mutes the speaker, unlike {@link #KEYCODE_MUTE}.
3810 * This key should normally be implemented as a toggle such that the first press
3811 * mutes the speaker and the second press restores the original volume. */
3812 public static final int KEYCODE_VOLUME_MUTE = 164;
3813 /** Key code constant: Info key.
3814 * Common on TV remotes to show additional information related to what is
3815 * currently being viewed. */
3816 public static final int KEYCODE_INFO = 165;
3817 /** Key code constant: Channel up key.
3818 * On TV remotes, increments the television channel. */
3819 public static final int KEYCODE_CHANNEL_UP = 166;
3820 /** Key code constant: Channel down key.
3821 * On TV remotes, decrements the television channel. */
3822 public static final int KEYCODE_CHANNEL_DOWN = 167;
3823 /** Key code constant: Zoom in key. */
3824 public static final int KEYCODE_ZOOM_IN = 168;
3825 /** Key code constant: Zoom out key. */
3826 public static final int KEYCODE_ZOOM_OUT = 169;
3827 /** Key code constant: TV key.
3828 * On TV remotes, switches to viewing live TV. */
3829 public static final int KEYCODE_TV = 170;
3830 /** Key code constant: Window key.
3831 * On TV remotes, toggles picture-in-picture mode or other windowing functions. */
3832 public static final int KEYCODE_WINDOW = 171;
3833 /** Key code constant: Guide key.
3834 * On TV remotes, shows a programming guide. */
3835 public static final int KEYCODE_GUIDE = 172;
3836 /** Key code constant: DVR key.
3837 * On some TV remotes, switches to a DVR mode for recorded shows. */
3838 public static final int KEYCODE_DVR = 173;
3839 /** Key code constant: Bookmark key.
3840 * On some TV remotes, bookmarks content or web pages. */
3841 public static final int KEYCODE_BOOKMARK = 174;
3842 /** Key code constant: Toggle captions key.
3843 * Switches the mode for closed-captioning text, for example during television shows. */
3844 public static final int KEYCODE_CAPTIONS = 175;
3845 /** Key code constant: Settings key.
3846 * Starts the system settings activity. */
3847 public static final int KEYCODE_SETTINGS = 176;
3848 /** Key code constant: TV power key.
3849 * On TV remotes, toggles the power on a television screen. */
3850 public static final int KEYCODE_TV_POWER = 177;
3851 /** Key code constant: TV input key.
3852 * On TV remotes, switches the input on a television screen. */
3853 public static final int KEYCODE_TV_INPUT = 178;
3854 /** Key code constant: Set-top-box power key.
3855 * On TV remotes, toggles the power on an external Set-top-box. */
3856 public static final int KEYCODE_STB_POWER = 179;
3857 /** Key code constant: Set-top-box input key.
3858 * On TV remotes, switches the input mode on an external Set-top-box. */
3859 public static final int KEYCODE_STB_INPUT = 180;
3860 /** Key code constant: A/V Receiver power key.
3861 * On TV remotes, toggles the power on an external A/V Receiver. */
3862 public static final int KEYCODE_AVR_POWER = 181;
3863 /** Key code constant: A/V Receiver input key.
3864 * On TV remotes, switches the input mode on an external A/V Receiver. */
3865 public static final int KEYCODE_AVR_INPUT = 182;
3866 /** Key code constant: Red "programmable" key.
3867 * On TV remotes, acts as a contextual/programmable key. */
3868 public static final int KEYCODE_PROG_RED = 183;
3869 /** Key code constant: Green "programmable" key.
3870 * On TV remotes, actsas a contextual/programmable key. */
3871 public static final int KEYCODE_PROG_GREEN = 184;
3872 /** Key code constant: Yellow "programmable" key.
3873 * On TV remotes, acts as a contextual/programmable key. */
3874 public static final int KEYCODE_PROG_YELLOW = 185;
3875 /** Key code constant: Blue "programmable" key.
3876 * On TV remotes, acts as a contextual/programmable key. */
3877 public static final int KEYCODE_PROG_BLUE = 186;
3879 private static final int LAST_KEYCODE = KEYCODE_PROG_BLUE;
3881 private String[] mKeyCodes = new String[256];
3882 private String[] mAppKeyCodes = new String[256];
3884 private void initKeyCodes() {
3885 mKeyCodes[KEYCODE_DPAD_CENTER] = "\015";
3886 mKeyCodes[KEYCODE_DPAD_UP] = "\033[A";
3887 mKeyCodes[KEYCODE_DPAD_DOWN] = "\033[B";
3888 mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033[C";
3889 mKeyCodes[KEYCODE_DPAD_LEFT] = "\033[D";
3890 mKeyCodes[KEYCODE_F1] = "\033[OP";
3891 mKeyCodes[KEYCODE_F2] = "\033[OQ";
3892 mKeyCodes[KEYCODE_F3] = "\033[OR";
3893 mKeyCodes[KEYCODE_F4] = "\033[OS";
3894 mKeyCodes[KEYCODE_F5] = "\033[15~";
3895 mKeyCodes[KEYCODE_F6] = "\033[17~";
3896 mKeyCodes[KEYCODE_F7] = "\033[18~";
3897 mKeyCodes[KEYCODE_F8] = "\033[19~";
3898 mKeyCodes[KEYCODE_F9] = "\033[20~";
3899 mKeyCodes[KEYCODE_F10] = "\033[21~";
3900 mKeyCodes[KEYCODE_F11] = "\033[23~";
3901 mKeyCodes[KEYCODE_F12] = "\033[24~";
3902 mKeyCodes[KEYCODE_SYSRQ] = "\033[32~"; // Sys Request / Print
3903 // Is this Scroll lock? mKeyCodes[Cancel] = "\033[33~";
3904 mKeyCodes[KEYCODE_BREAK] = "\033[34~"; // Pause/Break
3906 mKeyCodes[KEYCODE_TAB] = "\011";
3907 mKeyCodes[KEYCODE_ENTER] = "\015";
3908 mKeyCodes[KEYCODE_ESCAPE] = "\033";
3910 mKeyCodes[KEYCODE_INSERT] = "\033[2~";
3911 mKeyCodes[KEYCODE_FORWARD_DEL] = "\033[3~";
3912 mKeyCodes[KEYCODE_MOVE_HOME] = "\033[1~";
3913 mKeyCodes[KEYCODE_MOVE_END] = "\033[4~";
3914 mKeyCodes[KEYCODE_PAGE_UP] = "\033[5~";
3915 mKeyCodes[KEYCODE_PAGE_DOWN] = "\033[6~";
3916 mKeyCodes[KEYCODE_DEL]= "\177";
3917 mKeyCodes[KEYCODE_NUM_LOCK] = "\033OP";
3918 mKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "/";
3919 mKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "*";
3920 mKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "-";
3921 mKeyCodes[KEYCODE_NUMPAD_ADD] = "+";
3922 mKeyCodes[KEYCODE_NUMPAD_ENTER] = "\015";
3923 mKeyCodes[KEYCODE_NUMPAD_EQUALS] = "=";
3924 mKeyCodes[KEYCODE_NUMPAD_DOT] = ".";
3925 mKeyCodes[KEYCODE_NUMPAD_COMMA] = ",";
3926 mKeyCodes[KEYCODE_NUMPAD_0] = "0";
3927 mKeyCodes[KEYCODE_NUMPAD_1] = "1";
3928 mKeyCodes[KEYCODE_NUMPAD_2] = "2";
3929 mKeyCodes[KEYCODE_NUMPAD_3] = "3";
3930 mKeyCodes[KEYCODE_NUMPAD_4] = "4";
3931 mKeyCodes[KEYCODE_NUMPAD_5] = "5";
3932 mKeyCodes[KEYCODE_NUMPAD_6] = "6";
3933 mKeyCodes[KEYCODE_NUMPAD_7] = "7";
3934 mKeyCodes[KEYCODE_NUMPAD_8] = "8";
3935 mKeyCodes[KEYCODE_NUMPAD_9] = "9";
3937 mAppKeyCodes[KEYCODE_DPAD_UP] = "\033OA";
3938 mAppKeyCodes[KEYCODE_DPAD_DOWN] = "\033OB";
3939 mAppKeyCodes[KEYCODE_DPAD_RIGHT] = "\033OC";
3940 mAppKeyCodes[KEYCODE_DPAD_LEFT] = "\033OD";
3941 mAppKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "\033Oo";
3942 mAppKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "\033Oj";
3943 mAppKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "\033Om";
3944 mAppKeyCodes[KEYCODE_NUMPAD_ADD] = "\033Ok";
3945 mAppKeyCodes[KEYCODE_NUMPAD_ENTER] = "\033OM";
3946 mAppKeyCodes[KEYCODE_NUMPAD_EQUALS] = "\033OX";
3947 mAppKeyCodes[KEYCODE_NUMPAD_DOT] = "\033On";
3948 mAppKeyCodes[KEYCODE_NUMPAD_COMMA] = "\033Ol";
3949 mAppKeyCodes[KEYCODE_NUMPAD_0] = "\033Op";
3950 mAppKeyCodes[KEYCODE_NUMPAD_1] = "\033Oq";
3951 mAppKeyCodes[KEYCODE_NUMPAD_2] = "\033Or";
3952 mAppKeyCodes[KEYCODE_NUMPAD_3] = "\033Os";
3953 mAppKeyCodes[KEYCODE_NUMPAD_4] = "\033Ot";
3954 mAppKeyCodes[KEYCODE_NUMPAD_5] = "\033Ou";
3955 mAppKeyCodes[KEYCODE_NUMPAD_6] = "\033Ov";
3956 mAppKeyCodes[KEYCODE_NUMPAD_7] = "\033Ow";
3957 mAppKeyCodes[KEYCODE_NUMPAD_8] = "\033Ox";
3958 mAppKeyCodes[KEYCODE_NUMPAD_9] = "\033Oy";
3962 * The state engine for a modifier key. Can be pressed, released, locked,
3966 private class ModifierKey {
3970 private static final int UNPRESSED = 0;
3972 private static final int PRESSED = 1;
3974 private static final int RELEASED = 2;
3976 private static final int USED = 3;
3978 private static final int LOCKED = 4;
3981 * Construct a modifier key. UNPRESSED by default.
3984 public ModifierKey() {
3988 public void onPress() {
3991 // This is a repeat before use
3997 // This is a repeat after use
4008 public void onRelease() {
4017 // Leave state alone
4022 public void adjustAfterKeypress() {
4031 // Leave state alone
4036 public boolean isActive() {
4037 return mState != UNPRESSED;
4041 private ModifierKey mAltKey = new ModifierKey();
4043 private ModifierKey mCapKey = new ModifierKey();
4045 private ModifierKey mControlKey = new ModifierKey();
4047 private boolean mCapsLock;
4050 * Construct a term key listener.
4053 public TermKeyListener() {
4057 public void handleControlKey(boolean down) {
4059 mControlKey.onPress();
4061 mControlKey.onRelease();
4065 public int mapControlChar(int ch) {
4067 if (mControlKey.isActive()) {
4068 // Search is the control key.
4069 if (result >= 'a' && result <= 'z') {
4070 result = (char) (result - 'a' + '\001');
4071 } else if (result == ' ') {
4073 } else if ((result == '[') || (result == '1')) {
4075 } else if ((result == '\\') || (result == '.')) {
4077 } else if ((result == ']') || (result == '0')) {
4079 } else if ((result == '^') || (result == '6')) {
4080 result = 30; // control-^
4081 } else if ((result == '_') || (result == '5')) {
4087 mAltKey.adjustAfterKeypress();
4088 mCapKey.adjustAfterKeypress();
4089 mControlKey.adjustAfterKeypress();
4095 * Handle a keyDown event.
4097 * @param keyCode the keycode of the keyDown event
4100 public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMode) throws IOException {
4101 if (keyCode >= 0 && keyCode < mKeyCodes.length) {
4104 code = mAppKeyCodes[keyCode];
4107 code = mKeyCodes[keyCode];
4110 int length = code.length();
4111 for (int i = 0; i < length; i++) {
4112 out.write(code.charAt(i));
4119 case KeyEvent.KEYCODE_ALT_RIGHT:
4120 case KeyEvent.KEYCODE_ALT_LEFT:
4124 case KeyEvent.KEYCODE_SHIFT_LEFT:
4125 case KeyEvent.KEYCODE_SHIFT_RIGHT:
4129 case KEYCODE_CTRL_LEFT:
4130 case KEYCODE_CTRL_RIGHT:
4131 mControlKey.onPress();
4134 case KEYCODE_CAPS_LOCK:
4135 if (event.getRepeatCount() == 0) {
4136 mCapsLock = !mCapsLock;
4141 result = event.getUnicodeChar(
4142 (mCapKey.isActive() || mCapsLock ? KeyEvent.META_SHIFT_ON : 0) |
4143 (mAltKey.isActive() ? KeyEvent.META_ALT_ON : 0));
4148 result = mapControlChar(result);
4156 * Handle a keyUp event.
4158 * @param keyCode the keyCode of the keyUp event
4160 public void keyUp(int keyCode) {
4162 case KeyEvent.KEYCODE_ALT_LEFT:
4163 case KeyEvent.KEYCODE_ALT_RIGHT:
4164 mAltKey.onRelease();
4166 case KeyEvent.KEYCODE_SHIFT_LEFT:
4167 case KeyEvent.KEYCODE_SHIFT_RIGHT:
4168 mCapKey.onRelease();
4171 case KEYCODE_CTRL_LEFT:
4172 case KEYCODE_CTRL_RIGHT:
4173 mControlKey.onRelease();
4177 // Ignore other keyUps